Why isn't ActiveRecord's autosave working on my association?
The problem here is that autosave: true
simply sets up a normal before_save
callback, and before_save
callbacks are run in the order that they're created.**
Therefore, it tries to save the bar
, which has no changes, then it calls modify_bar
.
The solution is to ensure that the modify_bar
callback runs before the autosave.
One way to do that is with the prepend
option.
class Foo belongs_to :bar, autosave: true before_save :modify_bar, prepend: true ...end
Another way would be to put the before_save
statement before the belongs_to
.
Another way would be to explicitly save bar
at the end of the modify_bar
method and not use the autosave
option at all.
Thanks to Danny Burkes for the helpful blog post.
** Also, they're run after all after_validation
callbacks and before any before_create
callbacks - see the docs.
Update
Here's one way to check the order of such callbacks.
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
Using this supporting class:
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 endend
The above answer is (clearly) your solution. However:
I am fine using :autosave
, but I don't think changing external associations is a job for callbacks. I'm talking about your :modify_bar
. As brilliantly explained in this post I prefer to use another object to save multiple models at once. It really simplifies your life when things get more complex and makes tests much easier.
In this situation this might be accomplished in the controller or from a service object.