Using ActiveRecord’s delegate method
Let’s suppose that you have the following user class:
class User < ActiveRecord::Base
has_one :address
end
We can diplay the user’s zipcode this way:
user.address.zipcode
This doesn’t seem that bad, however, we have the following issues:
- Address can be nil, so this code can fail at some point
- It breaks the law of demeter
The solution for the first point is simple (but not much elegant):
user.try(:address).try(:zipcode) || 'No zipcode'
This code is still highly coupled, we can to make it better. Fortunately, Rails provides us some help with the delegate method:
class User < ActiveRecord::Base
has_one :address delegate :zipcode, to: :address
end
Now, we can refactor our code to look like this:
user.zipcode
This is better, because are not calling directly the address to get the zipcode. However, this code will fail if the address is nil, so we change the delegate part of our class:
delegate :zipcode, to: :address, allow_nil: true
Now we can simplify our view from this:
user.try(:address).try(:zipcode) || 'No zipcode'
To this:
user.zipcode || 'No zipcode'
This is better, right?
We can make our code more readable (or if the method name is causing conflicts with other methods) by using a prefix in our zipcode:
user.address_zipcode || 'No zipcode'
This can be accomplish using the prefix option that delegates receives:
delegate :zipcode, to: :address, allow_nil: true, prefix: true
This allows the delegated method to be called with the name of the class that responds finally to the method, in this case, address. You can also use a custom name for this prefix if you want:
delegate :zipcode, to: :address, allow_nil: true, prefix: :home
So the method changes from address_zipcode to home_zipcode:
user.home_zipcode || 'No zipcode'
Finally, you should know that delegate also works with POROs (Plain Old Ruby Objects):
class MyCustomClass
def hello
'Hello world!'
end
end
And the delegate call:
delegate :hello, to: :my_custom_class, prefix: :true
As you can see, delegate is a powerful tool that Rails provides us, because by using it we reduce the coupling of our code, making the code less error prone and allowing make changes more easily.