Tuesday 16 September 2014

Concerns in Rails Part 2 : Using concerns in Routes

With Rails 4, the concept of using concerns has been in highlights. Concerns can be used in routing to DRY up your routes.rb file.
Consider the following example: Consider an Events, a Articles, a Photos and a Comments controller. A event , photo and a article has_many comments. A comment belongs_to either a event, a article or a photo.
Typically, the route.rb file will have nested resourcing and may look like this:
  Rails.application.routes.draw do
      resources :events do
         resources :comments
     end
      resources :articles do
         resources :comments
     end
     resources :photos  do
         resources :comments
     end
   end
Using Concerns you can DRY up you routes.rb  as shown below:

 Rails.application.routes.draw do
   concern :commentable do
     resources :comments
   end
   resources :articles,events, photos, concerns: :commentable 
 end

Concerns in Rails Part 1 : Using concerns in Models

With Rails 4, the concept of using concerns has been in highlights. Concerns provide a easy yet powerful technique to group similar code of a model or across multiple models  in a module (that extends ActiveSupport::Concern ). This concern module is then included in the required models. 

Concerns in models can be used both for 
1) DRYing up model codes (by domain based grouping of common codes between models )
2) Skin-nizing Fat Models (by dividing the code based on domains and creating a concern for each domain.)

1) DRYing up model codes
Consider a Article model, a Event model and a Comment Model. A article or A 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 Model. Using concerns we can extract this common code in a separate module Commentable.
For this create a commentable.rb file in app/model/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

As you can see while using concerns:

     * The included block is executed within the context of the class that is including the module.
     *  All the class methods go into the module ClassMethods of the concern (no need to add
        'self.' to these methods).
     * The instance methods are written directly in the concern module.   

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 notes 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.rd and commentable.rb file in app/model/concern/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 the above Concerns , your Event model reduces to
class Event < ActiveRecord::Base    
    include Commentable
    include Attendable
end


* It is advisable to make concerns domain based rather than technical based. For instance,  concerns like 'Commentable', 'Photoable' are examples of domain based concerns. While concerns like 'FinderMethods', 'ValidationMethods' are examples of technically grouped concerns.


Here are links of some write ups for Model Concerns that I found very useful :

Wednesday 10 September 2014

Sandi Metz Rules For Ruby on Rails Development

Recently I came across a talk by Sandi Metz  on YouTube.  She specified 6 Rules for writing quality ROR code. She describes these rules as norms and constraints rather than laws. I personally found them as a good standard to improvise on my code writing,

Here are the rules:

1)  No more than 100 lines per class

2) No more than 5 lines per method

3) No more than passing 4 parameters to a method ( She specified that you cannot cheat with a hash. Each hash key counts as a parameter.)

4) One Controller action may pass only one instance variable to its corresponding view.

5) 2 class names per controller action: One business and one presenter

6) Break any of the above rules, provided you can convince you pair (another programmer working with you).