Keeping Rails models DRY with concerns

Its over the ninth week in GSoC 2016 and here I am with another of my blog post. Today I will discuss about a pretty nice thing that I learnt while I was working with the notification system. It is related to writing DRY code by using of modules.

My mentor Jeff said that methods in some models were becoming repetitive and they were doing the same kind of task. So it would be better if we could keep the code DRY i.e. avoid redundancy in code. DRY stands for Don’t Repeat Yourself i.e. try to avoid repetitive code in a application. We can do this by  creating methods and modules. Ruby has an awesome concept of modules and mixins  for this purpose.

ActiveSupport Concerns follow the same principle  of Ruby modules and by extending them on Rails models we can include common methods that are shareable across various models.

We had two models named Node and  Answer models. A Node basically served as a container of posts like wikis, questions and research notes in publiclab.org. Answer as the name suggests are answers to questions. Both have some common methods named liked and liked_by. So we made a NodeShared concern and put those in them. Here is how the concern looks like


module NodeShared
extend ActiveSupport::Concern
def likes
self.cached_likes
end
def liked_by(uid)
self.likers.collect(&:uid).include?(uid)
end
end

view raw

node_shared.rb

hosted with ❤ by GitHub

This file goes in a separate app/models/concerns/directory. This is where all concerns reside. To define it as a concern you need to extend the ActiveSupport::Concernmodule as I did above. After that after you have defined the concern you can use it in others models by including the concern like include NodeShared in this case. Here is how the node model would look


class Node < ActiveRecord::Base
include NodeShared # This includes the NodeShared Concern
attr_accessible :title, :uid, :status, :type, :vid, :cached_likes, :comment, :path, :slug
self.table_name = 'node'
self.primary_key = 'nid'
has_many :node_selections, :foreign_key => :nid
has_many :answers, :foreign_key => :nid
def latest
self.revisions
.where(status: 1)
.first
end
def revisions
self.drupal_node_revision
.order("timestamp DESC")
end
end

view raw

node.rb

hosted with ❤ by GitHub

This is a small part of the actual model used. So now the methods in the concern become a part of the model and they can be called with an instance of Node model. Now suppose the Answer model also needs the similar methods. We can simply add this concern to the model as well instead of writing the methods over again.

DHH the creator of Ruby on Rails explained this in his blog post.

Coming back to my progress in Summer of Code I have started working on UI changes and modified views that will include some good features. I will discuss them in my future posts. You can look at my PR #628 though it is Work in progress now. As the mid of July is over its probably going to be more work in the next couple of weeks as things are nearing the end.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s