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?
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.
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.
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.
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.
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.
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:
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With