2010-11-03 85 views
2

我有一個名爲Exam的「父類」,它有很多Score類的實例。我想在保存一個關聯分數時修改考試實例的屬性。我把所有的課都剝離到這個非常簡單的例子中,這看起來很愚蠢,但是卻以最基本的形式來說明問題。這裏是類。Rails:從關聯對象的回調中更新對象

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 

在代碼中的註釋已經說明問題:考試對象的名稱屬性的更新不會發生。請注意,trigger_score_saved proc會被執行,但新設置的值並不是最終保存到數據庫的值。 如果我在檢查對象本身上定義了before_save :trigger_score_saved回調,則名稱屬性的確會正確更新。所以這似乎與這樣一個事實有關,即有一個級聯保存正在進行,並且可能保存開始的父檢查對象與score.exam對象不同,我試圖修改該值。

任何人都可以解釋這裏發生了什麼,以及我如何能夠成功地從「子對象」的回調中更新父對象的屬性?

注:

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

回答

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完全相同的實例作爲exam,因爲:inverse_of屬性告訴Rails使用同一個對象實例在內存!

此外,我希望考試在任何分數上的任何CRUD操作上更新,所以當我從exam.scores集合中刪除分數時也是如此。這是像:after_remove這樣的關聯回調派上用場的地方。所以所有這些工具都在我的腰帶上,即使沒有身份地圖,我也可以向前移動(儘管我確實看到了在Rails中使用其中一種方法的價值)。

2

如你推測,有內存中考試類的不同實例引用相同的DB行。您可以調用#reload來刷新,或者等待身份驗證工作使其在發佈的Rails版本中生成。

的身份地圖的一些參考文獻:

+0

非常感謝François!這正是我期待的見解!您是否也可以告訴我在保存其中一個孩子時如何處理更新父對象的正確方法? – 2010-11-03 18:22:55

+0

您已經在使用「正確」的方式。現在的ActiveRecord沒有辦法抓住你正在尋找的實例。你必須重新加載並且安全地玩。我經常在我的測試中調用#reload,尋找最新鮮的值。這經常發生在控制器測試中,我用來設置數據庫的實例與控制器將使用的實例不同,因爲控制器會調用ActiveRecord :: Base#重新找到並獲取新實例化的實例。 – 2010-11-04 03:22:05