Rails Refactoring Techniques – Concerns

In this article, we are going to see the overview of Rails refactoring techniques, and focus on one of the techniques – called concerns.

Why do we need to perform

Rails refactoring?

The objectives are as follows:

 

  • DRY(Don’t repeat yourself)
  • Clarity
  • Easy to find
  • Easy to change
  • Easy to test
  • Performance tuning

 

As with all techniques, there are guidelines for the same.

 

The Guidelines are:

 

The four rules of Sandi Metz from the Ruby Rogues episode 87. Here is the transcript of the same:

 

http://rubyrogues.com/087-rr-book-clubpractical-object-oriented-design-in-ruby-with-sandi-metz/

 

  • Your class can be no longer than 100 lines of code.
  • Your methods can be no longer than five lines of code.
  • You can pass no more than four parameters and you can’t just make it one     big hash.
  • When a call comes into your Rails controller, you can only instantiate     one object to do whatever it is that needs to be done. And your view can only know about one instance variable.

 

Patterns & Techniques:

 

There are few patterns and techniques to refactor Rails models, controllers and views. Some main techniques are

 

  • Concerns
  • Decorators
  • Presenters
  • Services objects

 

Note: First, we will focus on “concerns” in this session. We will continue other Rails refactoring techniques in future session/blog.

 

What  is concern?

 

  • Concerns are pieces of code that allow you to better organize the code that     you write.
  • Default part of Rails 4, not an extra gem. But it should apply equally to 3.x.x and earlier.
  • Works not just for models, but also controllers.
  • Not just for code shared across models, but super to simply divide a large model file into logical chunks with associated tests

 

Sample:

 


 

module ModuleName

extend ActiveSupport::Concern

 

included do

# your class macros

end

 

# place your instance methods here

module ClassMethods

# place class methods here, removing self.

end

end

 

 

Why use concern?

 

  • Your model file is huge, and your matching spec is even larger.
  • You’ve got duplicated code in several models
  • You want a super easy and clean way to put this code into a single module containing
    • Instance methods
    • Class methods
    • Class macros (has_many, belongs_to, validates, scope, etc.)

 

How to discover a domain?

 

  • Discover a set of related behavior in a model; possibly, this behavior is in multiple models. The preference should be to discover a “domain” rather than some “technical” aspect. A domain grouping is like Taggable whereas a technical grouping would be like Finder Methods or “Validation Methods”

 

  • Create a file to hold the concern module:
    • In case there is just one concern for a model, or if shared among models, then create the file in app/models/concerns.
    • In case there are multiple concerns for a single model, group them in subdirectory under models, such as app/models/user/.

 

  • In the new module, underneath the module declaration, place extends ActiveSupport::Concern. This will tell Rails that we are creating a concern.

 

  • If you’ve got duplicated code in several models, group them into a domain.

 

Steps:

 

  • Create a file for Concern with skeleton structure in /apps/models/concerns/module_name
  • Next, move instance methods to module.
  • Then, move class macro code to included block.
  • Move class methods to inside of module ClassMethods.
  • Lastly, place include statement in original model class:
    • Include ModuleName (or)
    • include ModelName::ModuleName

Example:

 


 

module Searchable

extend ActiveSupport::Concern

 

included do

include Elasticsearch::Model

include Elasticsearch::Model::Callbacks

index_name 'articles_index' #create index name

 

mapping do

indexes :id, index: :not_analyzed

indexes :title, analyzer: 'snowball'

indexes :description, analyzer: 'snowball'

indexes :created_at, :type => 'date'

end

end

 

module ClassMethods

def search(params)

search_definition = {

sort: [created_at: {order:'desc'}],

}

search_definition[:query] = {

query_string: {

query: params[:q]

}

} if params[:q].present?

 

__elasticsearch__.search(search_definition)

end

end

end

 

 

Advantages:

 

  • Ease and safety of refactoring – Concerns are a great first refactoring step because using concerns involves simply moving the methods and tests into separate files. Also, the code accessing those methods need not change.

 

  • A core part of Rails 4, so one can expect familiarity among Rails developers.

 

  • Simplicity – It is just a code organization which makes it easier to navigate to the right source and test a file; simpler than plain Ruby methods of include and extend.

 

  • Can DRY up code when a concern is shared among multiple models or controllers.

 

Notes: Applies to controllers as well as models, although with controllers, one can break up a controller into several controllers that are referenced in the routes.rb file, so you may not need to use a Concern for simply reducing the size of a controller. In the controller case, concerns are useful for sharing code.

 

Conclusion:

 

This is the Concerns Rails refactoring technique in detail. For more details about our Agira practices, Check our github repo agile-practices.