0

I would love to understand recursiveness and what I've done to create a circular reference.

class SomeModel 
  # callback to calculate something only if the status_id has been changed
  after_save :calculate_something if: :status_id_changed?

  def calculate_something
    # this unless combined with the callback if means this is only run if not only the status_id has been changed, but that it's been changed to a non-nil value
    unless self.status_id.nil?
      self.run_first_calculation
    end
  end

  def run_first_calculation
    self.save(validate:false)
  end
end

I'm getting a stack level too deep error because of the self.save(validate:false). But I'm not sure why because saving with a validation false does not change the status_id, but when I have a regular self.save, it's fine.

If you take the above, and run in the console:

some_model_instance.update_attribute(:status_id, 2)
# => stack level too deep

I don't get why. Here's what I think is happening:

  1. some_model_instance gets an updated status_id
  2. because of that, the callback runs (now we're in run_calculations)
  3. because the status_id is not nil, we are inside the unless block
  4. the run_first_calculation method is called
  5. we save some_model_instance arbitrarily
  6. but because the status_id is not changed, we shouldn't run into the callback again
sawa
  • 165,429
  • 45
  • 277
  • 381
james
  • 3,989
  • 8
  • 47
  • 102
  • Since you wrote a regular save does not cause the problem, is there any validation callbacks? Also, what are the values of status_id and status_id_was in the second calculate_something call? – tyamagu2 Oct 16 '15 at 01:01
  • Validation callbacks are really basic presence checks and format checks, nothing complex that would cause this, especially since false just skips them, right? Status_id is updated appropriately (i.e., it is 2) – james Oct 16 '15 at 07:56

1 Answers1

0

Your code runs into an stack level too deep exception, because status_id_changed? still returns true in an after_save callback.

after_save callbacks run in the same transaction then the original save. Is is done to allow to rollback the after save changes together with the original changes if something goes wrong. But the dirty attribute flags are resetted after the transaction is finished.

That said your second save in run_first_calculation just triggers the same callback again with status_id_changed? still being true.

spickermann
  • 100,941
  • 9
  • 101
  • 131
  • Oh interesting. Any idea why removing `validate:false` fixes the problem? And if I understand the order of call backs correctly, it seems like an `after_update` might fix this right? – james Oct 16 '15 at 16:29