Почему автоматическая работа ActiveRecord не работает с моей ассоциацией?

У меня есть класс ActiveRecord, который выглядит примерно так.

class Foo
  belongs_to :bar, autosave: true
  before_save :modify_bar
  ...
end

Если я выполняю некоторые протоколирования, я вижу, что bar изменяется, но его изменения не сохраняются. Что не так?

Ответ 1

Проблема заключается в том, что autosave: true просто устанавливает нормальный обратный вызов before_save, а обратные вызовы before_save выполняются в том порядке, в котором они созданы. **

Поэтому он пытается сохранить bar, который не имеет никаких изменений, , а затем вызывает modify_bar.

Решение состоит в том, чтобы обеспечить обратный вызов modify_bar перед автосохранением.

Один из способов сделать это с помощью опции prepend.

class Foo
  belongs_to :bar, autosave: true
  before_save :modify_bar, prepend: true
  ...
end

Другим способом было бы поставить оператор before_save перед belongs_to.

Другим способом было бы явно сохранить bar в конце метода modify_bar и вообще не использовать параметр autosave.

Спасибо Danny Burkes за полезный пост в блоге.

** Кроме того, они запускаются после всех обратных вызовов after_validation и перед любыми обратными вызовами before_create - см. документы.


Update

Здесь один из способов проверить порядок таких обратных вызовов.

  describe "sequence of callbacks" do

    let(:sequence_checker) { SequenceChecker.new }

    before :each do
      foo.stub(:bar).and_return(sequence_checker)
    end

    it "modifies bar before saving it" do
      # Run the before_save callbacks and halt before actually saving
      foo.run_callbacks(:save) { false }
      # Test one of the following
      #
      # If only these methods should have been called
      expect(sequence_checker.called_methods).to eq(%w[modify save])
      # If there may be other methods called in between
      expect(sequence_checker.received_in_order?('modify', 'save')).to be_true
    end

  end

Использование этого вспомогательного класса:

class SequenceChecker
  attr_accessor :called_methods

  def initialize
    self.called_methods = []
  end

  def method_missing(method_name, *args)
    called_methods << method_name.to_s
  end

  def received_in_order?(*expected_methods)
    expected_methods.map!(&:to_s)
    called_methods & expected_methods == expected_methods
  end

end

Ответ 2

Вышеупомянутый ответ (понятно) - ваше решение. Однако:

Я отлично использую :autosave, но я не думаю, что изменение внешних связей - это работа для обратных вызовов. Я говорю о твоем :modify_bar. Как блестяще объяснил в этом сообщении Я предпочитаю использовать другой объект для одновременного сохранения нескольких моделей. Это действительно упрощает вашу жизнь, когда ситуация становится более сложной и делает тесты намного проще.

В этой ситуации это может быть выполнено в контроллере или из объекта службы.