我有一个名为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>
有人可以解释这里发生了什么以及如何在“子对象”的回调中成功更新父对象的属性吗?
注意:
update_attribute(:name => "#{name}!")
代替self.name = "#{name}!"
,但两者都有相同的效果答案 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中使用其中一个的价值)。