Why isn't ActiveRecord's autosave working on my association? Why isn't ActiveRecord's autosave working on my association? ruby-on-rails ruby-on-rails

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.