How to extend Rails associations
You might have extended classes or instances in Rails, but do you know you can also extend Rails associations?
class Account < ActiveRecord::Base has_many :people, -> { extending FindOrCreateByNameExtension } end
What exactly does extending an association do?
The proxy objects that control access to associations can be extended through anonymous modules. This is especially beneficial for adding new finders, creators, and other factory-type methods that are only used as part of an association.
Let’s look at the following code, Here we are adding a new finder method called find_or_create_by_name
on this association with Person
.
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
This method can only be used as part of this association. if you try to access this method through the Person
object directly you will not get it. It is available only when you access it through the association.
If you have a business requirement that says you want to create a new Person by name when it’s for an existing Account, this provides a straightforward solution.
# Not available as a class method
irb(main):005:0> Person.methods.grep /find_or_create_by_name/
=> []
# Not accessible directly through an instance of that class
irb(main):010:0> Person.first.methods.grep /find_or_create_by_name/
=> []
# Accessible through association
irb(main):011:0> person = Account.first.people.methods.grep /find_or_create_by_name/
=> [:find_or_create_by_name]
If you need to share the same method between many associations, you can use a named extension module.
You can put the finder method inside a module, like this:
module FindOrCreateByNameExtension
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
On Rails 4.0 and above, you can use the following syntax to add your extension module:
class Account < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
class Company < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
On Rails 3.2 and lower versions, use the following syntax:
class Account < ActiveRecord::Base
has_many :people, :extend => FindOrCreateByNameExtension
end
Applying conditions in an extension module
If you have some condition applied to an association, you can chain that condition in an extension module like this:
# before:
belongs_to :clinic, -> { clinic_id: @clinic_id }, primary_key: :person_id, foreign_key: :person_id
# After:
belongs_to :clinic, -> { extending(MultiAssociation::OnTestId).where(clinic_id: @clinic_id) }, primary_key: :person_id, foreign_key: :person_id
Now you know how to use extension modules with associations.
Happy Learning!