Ответ 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