Extending ActiveRecord associations
Ever found yourself having difficulty to decide if method belongs to parent or child model?
Let’s say we have a Blogger
and Article
models. And profile section of our
“blogging platform” displays list of articles published this current month.
Our option 1 is doing it in Blogger
model:
class Blogger < ApplicationRecord
has_many :articles
def articles_this_month
articles.where('created_at > ?', Time.current.beginning_of_month)
end
end
And somewhere in our controller:
@articles = blogger.articles_this_month
Looks fine, but with domain models getting more complicated we may find our
Blogger
model polluted by articles_this
, articles_that
methods.
Option 2 is implement it in Article
model using class method (or alternatively
scope):
class Article < ApplicationRecord
belongs_to :blogger
def self.this_month
where('created_at > ?', Time.current.beginning_of_month)
end
end
And somewhere in our controller:
@articles = blogger.articles.this_month
Again, nothing wrong with that. Except maybe the fact that Article.this_month
does not make too much sense on its own. In a way it pollutes Article
model
api with methods which don’t make sense without context of parent Blogger
.
It’s almost as if our this_month
method does not fit on both of our models and
belongs to the relationship between them. Here we come to a somewhat little known
ActiveRecord feature and our option 3: extending relationship between the models
with custom methods.
has_many relationship documentation describes option :extend
:
Specifies a module or array of modules that will be extended into the association object returned. Useful for defining methods on associations, especially when they should be shared between multiple association objects.
Our module then:
module BloggerArticles
def this_month
where('created_at > ?', Time.current.beginning_of_month)
end
end
And the Blogger
model:
class Blogger < ApplicationRecord
has_many :articles, extend: BloggerArticles
end
Code somewhere in controller:
@articles = blogger.articles.this_month
Now neither Blogger
nor Article
has any out of place “this month” filter
related methods. Instead, filter is now part of relationship between the models.
So, to summarise: models are not the only places to put your domain code. Relationship can also be a good option for that. And when used well - can be great way to build natural and well scoped domain APIs.