Skip to content

Rails, nested attributes and before_save callbacks

I had a Project model with a many ProjectWell (as in oil) association. Taking advantage of the accepts_nested_attributes, I was able to persist both Project and ProjectWell attributes through a single web form.

Now, the ProjectWell had fields that I wanted to calculate and assign on each ProjectWell save. Naturally a before_save operation (within an observer or such) would’ve been sufficient. The main requirement I had was to have all ProjectWell callbacks fired regardless of the Dirty state of the ProjectWell object.

Given Project p with an associated ProjectWell p_well (both already exist in the database):

This will NOT update the ProjectWell (p_well) object in the database, and hence will NOT fire any of the required callbacks. The main reason is that the p_well attributes didn’t change. The name attribute is the same as the one already in persisted.

My next attempt was to try to disable the partial_updates for ActiveRecord. In an initializer, I’ve placed this configuration:

What this does was disable selective attribute updates for ProjectWells (and all other objects). In other words; it updates all ProjectWell attributes, regardless of whether or not those particular attributes have changed. But this happens ONLY when ActiveRecord detects at least ONE changed attributes.

In my case, there were no changes in any of the attributes, which made Rails skip the update operation altogether.

My work around was to ‘simulate’ an attribute change, in one of the attributes, even if it hasn’t really changed by the user.

A first attempt was to create a virtual (non-persisted) attribute in the model (attr_accessor :force_save). Unfortunately, it changes to the attribute (corresponding to its default value) didn’t trigger Rails dirty check.

A second attempt was by using ‘_will_change!’ dynamic method. The method can force Rails into registering the corresponding attribute as a changed one.

This threw an undefined method error of ‘force_save_will_change!’.

The _will_change! method is only available for DB persisted attributes (defined in the corresponding table).

My next attempt made by selecting a non-virtual attribute to use the will_change! against.

This resolved the issue by always setting the ProjectWell state to dirty update an update to name attribute, regardless if the previous name was the same as the new one.

Now, when executing the update_attributes – passing in the name attribute (whether with a name change or not) will fire off the save operation and consequently the before_save callback

Note: Setting ‘autosave’ and ‘touch’ on the relative association declaration didn’t help.

Any other way of doing this?

Stack: Rails 3.1.0, JRuby 1.63, activerecord-oracle_enhanced-adapter 1.4, Windows XP

Posted in Ruby.

0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

Some HTML is OK

or, reply to this post via trackback.