Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use concerns in Rails 4

The default Rails 4 project generator now creates the directory "concerns" under controllers and models. I have found some explanations about how to use routing concerns, but nothing about controllers or models.

I am pretty sure it has to do with the current "DCI trend" in the community and would like to give it a try.

The question is, how am I supposed to use this feature, is there a convention on how to define the naming / class hierarchy in order to make it work? How can I include a concern in a model or controller?

like image 466
yagooar Avatar asked Jan 26 '13 21:01

yagooar


People also ask

What is the use of concerns in Rails?

According to the Rails docs, we want to encapsulate the code in a different module to reuse that code in different places when that same functionality is needed. We use concerns when we feel that one file is overwhelmed with the functionality, so we extract some part of the code and save it into another file or module.

Why do we use concern?

1 : to relate to : be about The story concerns a young prince. 2 : to be of interest or importance to : affect This problem concerns us all. 3 : to make worried Her illness concerned my parents. 4 : engage sense 2, occupy She concerns herself with other people's business.

What does ActiveSupport :: concern do?

ActiveSupport's Concern module allows us to mix in callbacks, class and instance methods, and create associations on target objects. This module has an included method, which takes a block, as well as an append_features method and class_methods block, which you can read about in the source code.

What is the difference between module and concern?

Concern can appear somewhere as model, controller and at here you can write module for yourself. And with general module is write in lib folder. Both can be used by way include or extend into a class.


2 Answers

So I found it out by myself. It is actually a pretty simple but powerful concept. It has to do with code reuse as in the example below. Basically, the idea is to extract common and / or context specific chunks of code in order to clean up the models and avoid them getting too fat and messy.

As an example, I'll put one well known pattern, the taggable pattern:

# app/models/product.rb class Product   include Taggable    ... end  # app/models/concerns/taggable.rb # notice that the file name has to match the module name  # (applying Rails conventions for autoloading) module Taggable   extend ActiveSupport::Concern    included do     has_many :taggings, as: :taggable     has_many :tags, through: :taggings      class_attribute :tag_limit   end    def tags_string     tags.map(&:name).join(', ')   end    def tags_string=(tag_string)     tag_names = tag_string.to_s.split(', ')      tag_names.each do |tag_name|       tags.build(name: tag_name)     end   end    # methods defined here are going to extend the class, not the instance of it   module ClassMethods      def tag_limit(value)       self.tag_limit_value = value     end    end  end 

So following the Product sample, you can add Taggable to any class you desire and share its functionality.

This is pretty well explained by DHH:

In Rails 4, we’re going to invite programmers to use concerns with the default app/models/concerns and app/controllers/concerns directories that are automatically part of the load path. Together with the ActiveSupport::Concern wrapper, it’s just enough support to make this light-weight factoring mechanism shine.

like image 83
yagooar Avatar answered Oct 15 '22 06:10

yagooar


I have been reading about using model concerns to skin-nize fat models as well as DRY up your model codes. Here is an explanation with examples:

1) DRYing up model codes

Consider a Article model, a Event model and a Comment model. An article or an event has many comments. A comment belongs to either Article or Event.

Traditionally, the models may look like this:

Comment Model:

class Comment < ActiveRecord::Base   belongs_to :commentable, polymorphic: true end 

Article Model:

class Article < ActiveRecord::Base   has_many :comments, as: :commentable     def find_first_comment     comments.first(created_at DESC)   end    def self.least_commented    #return the article with least number of comments   end end 

Event Model

class Event < ActiveRecord::Base   has_many :comments, as: :commentable     def find_first_comment     comments.first(created_at DESC)   end    def self.least_commented    #returns the event with least number of comments   end end 

As we can notice, there is a significant piece of code common to both Event and Article. Using concerns we can extract this common code in a separate module Commentable.

For this create a commentable.rb file in app/models/concerns.

module Commentable   extend ActiveSupport::Concern    included do     has_many :comments, as: :commentable   end    # for the given article/event returns the first comment   def find_first_comment     comments.first(created_at DESC)   end    module ClassMethods     def least_commented       #returns the article/event which has the least number of comments     end   end end 

And now your models look like this :

Comment Model:

class Comment < ActiveRecord::Base   belongs_to :commentable, polymorphic: true end 

Article Model:

class Article < ActiveRecord::Base   include Commentable end 

Event Model:

class Event < ActiveRecord::Base   include Commentable end 

2) Skin-nizing Fat Models.

Consider a Event model. A event has many attenders and comments.

Typically, the event model might look like this

class Event < ActiveRecord::Base      has_many :comments   has_many :attenders     def find_first_comment     # for the given article/event returns the first comment   end    def find_comments_with_word(word)     # for the given event returns an array of comments which contain the given word   end     def self.least_commented     # finds the event which has the least number of comments   end    def self.most_attended     # returns the event with most number of attendes   end    def has_attendee(attendee_id)     # returns true if the event has the mentioned attendee   end end 

Models with many associations and otherwise have tendency to accumulate more and more code and become unmanageable. Concerns provide a way to skin-nize fat modules making them more modularized and easy to understand.

The above model can be refactored using concerns as below: Create a attendable.rb and commentable.rb file in app/models/concerns/event folder

attendable.rb

module Attendable   extend ActiveSupport::Concern    included do      has_many :attenders   end    def has_attender(attender_id)     # returns true if the event has the mentioned attendee   end    module ClassMethods     def most_attended       # returns the event with most number of attendes     end   end end 

commentable.rb

module Commentable   extend ActiveSupport::Concern    included do      has_many :comments   end    def find_first_comment     # for the given article/event returns the first comment   end    def find_comments_with_word(word)     # for the given event returns an array of comments which contain the given word   end    module ClassMethods     def least_commented       # finds the event which has the least number of comments     end   end end 

And now using Concerns, your Event model reduces to

class Event < ActiveRecord::Base   include Commentable   include Attendable end 

* While using concerns its advisable to go for 'domain' based grouping rather than 'technical' grouping. Domain Based grouping is like 'Commentable', 'Photoable', 'Attendable'. Technical grouping will mean 'ValidationMethods', 'FinderMethods' etc

like image 29
Aaditi Jain Avatar answered Oct 15 '22 06:10

Aaditi Jain