Rails: обновление объекта из обратного вызова связанного объекта - PullRequest
2 голосов
/ 03 ноября 2010

У меня есть «родительский класс» с именем Exam, у которого есть много экземпляров класса Score. Я хочу изменить атрибут в экземпляре экзамена, когда один из связанных баллов сохранен. Я сократил все классы до этого очень простого примера, который выглядит глупо, но иллюстрирует проблему в ее самой простой форме. Вот классы.

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 выполняется, но вновь установленное значение не является тем, которое в конечном итоге сохраняется в базе данных. Если я определю обратный вызов before_save :trigger_score_saved для самого объекта экзамена, атрибут имени действительно будет обновлен правильно. Так что, похоже, что-то связано с тем, что происходит каскадное сохранение и что, возможно, родительский объект экзамена, с которого началось сохранение, отличается от объекта Score.exam, который я пытаюсь изменить. 1010 *

Может кто-нибудь объяснить, что здесь происходит и как я могу успешно обновить атрибут родительского объекта из обратного вызова «дочернего объекта»?

Примечания:

  • Я использую Rails 3 и Ruby 1.9.2
  • Я пробовал update_attribute(:name => "#{name}!") вместо self.name = "#{name}!", но оба имеют одинаковый эффект

Ответы [ 2 ]

2 голосов
/ 06 ноября 2010

Я провел еще несколько исследований, и, как оказалось, мою проблему на самом деле очень просто решить, указав атрибут :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 голосов
/ 03 ноября 2010

Как вы и предполагали, в памяти есть разные экземпляры класса Exam, ссылающиеся на одну и ту же строку БД. Вы можете вызвать #reload, чтобы обновить, или подождать, пока идентификационная работа не сделает это в выпущенной версии Rails.

Некоторые ссылки на карту личности:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...