Rails:从关联对象的回调中更新对象

时间:2010-11-03 15:35:09

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

我有一个名为Exam的“父类”,它有很多Score类的实例。我想在保存其中一个关联分数时修改Exam实例上的属性。我把所有类都剥离到这个非常简单的例子,它看起来很愚蠢,但以最基本的形式说明了问题。这是课程。

class Exam < ActiveRecord::Base
  has_many :scores

  def score_saved
    # self.name is now "Software Engineering"
    self.name = "#{name}!"
    # self.name is now "Software Engineering!"
  end
end

class Score < ActiveRecord::Base
  belongs_to :exam
  belongs_to :course

  before_save :trigger_score_saved

  def trigger_score_saved
    exam.score_saved unless exam.nil?
  end
end

然后我运行以下测试:

class ExamTest < ActiveSupport::TestCase
  test "create new exam" do
    exam = Exam.new(:name => "Software Engineering 1")
    score = exam.scores.build(:grade => 80, :course => courses(:one))
    exam.save

    # self.name is still "Software Engineering" here
    assert_equal "Software Engineering 1!", exam.name 
  end 
end

代码中的注释已经说明了问题:不会更新检查对象的name属性。请注意,执行trigger_score_saved proc,但新设置的值不是最终保存到数据库的值。 如果我在考试对象本身上定义before_save :trigger_score_saved回调,则名称属性正确更新。因此,它似乎与存在级联存储的事实有关,并且可能保存所启动的父检查对象与我正在尝试修改其值的score.exam对象不同。 / p>

有人可以解释这里发生了什么以及如何在“子对象”的回调中成功更新父对象的属性吗?

注意:

  • 我使用Rails 3和Ruby 1.9.2
  • 我尝试过update_attribute(:name => "#{name}!")代替self.name = "#{name}!",但两者都有相同的效果

2 个答案:

答案 0 :(得分:2)

正如您所推测的那样,内存中有不同的Exam类实例引用相同的DB行。您可以调用#reload来刷新,或等待身份工作在已发布的Rails版本中进行。

对身份地图的一些引用:

答案 1 :(得分:2)

我已经做了一些调查,事实证明,我的问题实际上是通过在相关协会上指定:inverse_of属性来解决的:

class Exam < ActiveRecord::Base
  has_many :scores, :inverse_of => :exam
  ...
end

class Score < ActiveRecord::Base
  belongs_to :exam, :inverse_of => :scores
  ...
end

这种方式exam.scores.first.exam 与<{1}}完全相同,因为exam属性告诉Rails在内存中使用相同的对象实例!

此外,我希望在任何分数上更新任何CRUD操作的考试,所以当我从exam.scores集合中删除分数时也是如此。这就是像:inverse_of这样的关联回调派上用场的地方。

所有这一切都有了这些工具,即使没有身份地图,我似乎也可以前进(尽管我确实看到了在Rails中使用其中一个的价值)。