活动记录,使用实例变量在before_destroy块中定义

时间:2017-07-26 16:02:17

标签: ruby ruby-on-rails-4 activerecord

某些背景信息:

一家公司有很多用户。谁是主人。 除了所有者之外,您可以销毁公司中的所有人。 摧毁主人,就像摧毁公司一样。所以你必须company.destroy 如果他是所有者,我会阻止销毁用户,除非你正在摧毁公司。

我的代码:

class Company < ActiveRecord::Base
  before_destroy { @bool_allow_owner_be_destroyed = true ; p object_id }

  belongs_to  :owner,     class_name: 'User', foreign_key: 'owner_id'
  has_many    :employees, class_name: 'User', dependent: :destroy

  def can_owner_be_destroyed?
    p @bool_allow_owner_be_destroyed
    p object_id
    !!@bool_allow_owner_be_destroyed
  end
end

class User < ActiveRecord::Base
  belongs_to        :company
  before_destroy    :cancel_if_owner

  def cancel_if_owner
    !self.is_owner? || self.company.can_owner_be_destroyed?
  end
end

我的问题:

当我致电company.destroy时,我看到了我的调试。我正在传递before_destroy { @bool_allow_owner_be_destroyed = true ; p object_id },我看到了我的object_id。 当我传递can_owner_be_destroyed?方法时,我有相同的object_id,但我的@bool_allow_owner_be_destroyednil。它是我的代码中唯一触及@bool_allow_owner_be_destroyed变量的地方。

有什么想法吗?

1 个答案:

答案 0 :(得分:0)

根据声明的顺序,在为父对象运行before_destroy回调之前,可以销毁您的依赖对象。

您可以自己测试一下:

class Company < ActiveRecord::Base
  belongs_to  :owner,     class_name: 'User', foreign_key: 'owner_id'
  has_many    :employees, class_name: 'User', dependent: :destroy

  before_destroy do
    @test_variable = "hi"
    puts @test_variable
    puts "Company destroyed"
  end
end

class User < ActiveRecord::Base
  belongs_to        :company

  before_destroy do
    puts company.instance_variable_get(:@test_variable)
    puts "User destroyed"
  end
end
所有"Company destroyed"已经消失后,

users将是最后打印的内容,并且每个用户都会得到一个空字符串puts来代替“hi”,因为@test_variable尚未确定。但是,如果您在声明关联之前声明回调,则会先打印"Company destroyed",并且您应该看到"hi"打印为每个Userdestroy

如果你问我,那是非常意外的。回调可能使事情过于复杂并且经常被过度使用,我们怎样才能保证我们确切地知道在这样复杂的执行流程中发生了什么? Ruby有一个内置的后代对象回调系统,它更容易理解,并且在行为上可以预测:super

class Company
  def destroy(*args, &block)
    @bool_allow_owner_be_destroyed = true
    super
  end
end

在调用super之前执行的任何操作都将保证在任何依赖对象被销毁之前运行或由destroy触发的任何其他对象。使用super时没有歧义。

要取消destroy的{​​{1}},您还可以使用owner以及早期super

return

总的来说,我建议尝试使用实例变量在ActiveRecord对象上携带状态可能会让您在某些时候遇到麻烦,特别是如果关联对象依赖于该状态或者您的应用程序运行的多个实例同一时间。

您可以将其设为db字段并保留更改。

class User < ActiveRecord::Base
  belongs_to :company

  def destroy(*args, &block)
    return unless can_be_destroyed?
    super
  end

  def can_be_destroyed?
    !is_owner? || company.can_owner_be_destroyed?
  end
end
相关问题