Posts Rails, nested attributes and before_save callbacks
Post
Cancel

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.

class Project < ActiveRecord::Base
  has_many :project_wells
  accepts_nested_attributes_for :project_wells
end

class ProjectWell < ActiveRecord::Base
  belongs_to :project
end

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):

p.update_attributes!({"project_well_attributes" => [{'id' => p_well.id, 'name' => p_well.name}]})

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:

ActiveRecord::Base.partial_updates = false

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.

 def force_save= val
    force_save_will_change!
    @force_save= val
  end

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.

 def name= val
    name_will_change!
    @name= val
  end

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

Contents

Search Results