Always present association

… and check why 5600+ Rails engineers read also this

Always present association

Recently my colleague showed my a little trick that I found to be very useful in some situations. It’s nothing fancy or mind-blowing or unusual in terms of using Ruby. It’s just applied in a way that I haven’t seen before. It kind of even seems obvious after seeing it :)

The trick

class Order < ActiveRecord::Base
  has_one :meta_data, dependent: :destroy, autosave: true

  def meta_data
    super || build_meta_data
  end

  delegate :ip_address,  :ip_address=
           :user_agent,  :user_agent=
           to: :meta_data,
           prefix: false
end

Nice

Now you can just do:

order.ip_address = request.remote_ip
order.save!

without wondering if order.meta_data is nil because if this associated record was never saved then build_meta_data will create a new one for you.

Same goes with reading such attributes. You can get nil but you won’t get NoMethodError from calling ip_address on an empty association (nil).

Not so nice

It has some downsides, however. Reading (event an empty) ip_address can trigger a side-effect in saving the meta_data.

ip = order.ip_address
order.save!

MetaData can not have non-null columns unless you set all of them at the same time. Otherwise, when ip_address can be null but user_agent cannot, setting only one of them will cause troubles.

order.ip_address = request.remote_ip
order.save! # Exception

The same problem can occur with validations on MetaData.

Summary

But if you don’t have such situations in your code and just have multiple attributes that are either optional or all set at the same time, then why not.

P.S.

Check out more patterns that can help you in maintaining Rails apps in our Fearless Refactoring: Rails controllers ebook

You might also like