为什么在这个has_many协会中没有“before_remove”解雇?

时间:2012-01-17 11:31:01

标签: ruby-on-rails

我希望在从has_many关联中删除对象之前运行一些代码。

我认为我可以使用before_remove回调执行此操作但是由于某种原因,这不会触发,我不明白为什么。

class Person < ActiveRecord::Base
  has_many :limbs, before_remove: :print_message

  def print_message
    puts 'removing a limb'
  end
end

class Limb < ActiveRecord::Base
  belongs_to :person
end

虽然这段代码应该在肢体破坏期间打印“移除肢体”,但事实并非如此。

p = Person.create;
l = Limb.create person: p;
p.limbs.first.destroy
# SQL (2.1ms)  DELETE FROM "limbs" WHERE "limbs"."id" = ?  [["id", 5]]
# => #<Limb id: 5, person_id: 3, created_at: "2012-01-17 11:28:01", updated_at: "2012-01-17 11:28:01"> 

为什么此destroy操作不会导致print_message方法运行?

编辑 - 这个before_remove回调是否存在?

许多人都在询问是否存在此回调。虽然我可以找到很少的进一步引用,但它在Rails文档中有记录:

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Association+callbacks

这是一个关联回调,而不是根ActiveRecord回调

编辑2 - 为什么不在肢体上使用before_destroy

有些人问为什么我没有在Limb上使用before_destroy回调。原因是我希望人检查是否有最小数量的肢体,并且最后一个肢体永远不会被摧毁。这是原始问题: How do you ensure that has_many always "has a minimum"?

3 个答案:

答案 0 :(得分:17)

before_remove回调作为Associations callbacks中的选项存在。它与before_destroy不同,后者是ActiveRecord callback

这是您使用它的方式:

class Person < ActiveRecord::Base
  has_many :limbs, :before_remove => :print_message

  def print_message(limb)
    # limb variable references to a Limb instance you're removing
    # ( do some action here )
    # ...
  end
end

class Limb < ActiveRecord::Base
  belongs_to :person
end

您还错误地调用remove方法。

p = Person.create
l = Limb.create(:person => p)
p.limbs.first.destroy 

这里你在Limb实例上调用它,这就是没有触发任何内容的原因。 在您创建的关联上调用它:

p = Person.create
l = Limb.create(:person => p)
p.limbs.destroy(l)

修改

为了保留最少的相关对象,您可以执行以下操作:

class Person < ActiveRecord::Base
  has_many :limbs, :before_remove => :preserve_mimimum

  def preserve_minimum(limb)
    raise "Minimum must be preserved" if limbs.count == 1
  end
end

class Limb < ActiveRecord::Base
  belongs_to :person
end

然而,这不会在p.limbs.destroy_all上触发,因此你必须做这样的事情p.limbs.each {|l| p.limbs.destroy(l)}

为什么它不会被destroy_all触发?

因此:

def destroy_all(conditions = nil)
   find(:all, :conditions => conditions).each { |object| object.destroy }
end

它迭代关联的每个元素并对对象执行销毁操作而不是关联,这就是原因。

答案 1 :(得分:1)

before_remove替换为before_destroy

编辑 - 处理最少肢数

class Limb < ActiveRecord::Base
  belongs_to :creature
  before_destroy :count_limbs

  def count_limbs
    return false if self.creature.limbs.length <= self.creature.min_limbs
  end
end

我认为,那种回归是假的,会阻止它被摧毁。虽然我错了

答案 2 :(得分:0)

我不能说我之前曾使用before_remove回调,但不确定它是否存在。

之前的destroy回调应该是在Limb模型上,而应该是这样的:

class Limb < ActiveRecord::Base
  belongs_to :person
  before_destroy :print_message
  def print_message
    puts 'removing a limb'
  end
end