9 Best Practices to Follow While Coding in Rails

Developing in Rails can be a piece of cake for a lot of young developers as not only it is easy in comparison to the other languages but also very flexible. To make the most of this flexibility, it's good to learn the best practices early on. Dipping into the wisdom from www.railscasts.com, www.rails-bestpractices.com and www.codeschool.com and after experimenting with these myself, I’ve put together a short list of some of the good practices to make your code legen – wait for it–dary!! Legendary!

Caching With Instance Variable

Let's take a common scenario of company, projects, and project creators. The models would look like:

class Project < ActiveRecord::Base
    belongs_to :creator, :class_name => “User”
end
class User < ActiveRecord::Base
    belongs_to :company
    has_many :projects
end
class Company < ActiveRecord::Base
    has_many :users
end

To find the company of the project creator you would need to write a method in the company. This can be done in two ways:

## Bad Practice

class Project < ActiveRecord::Base
    belongs_to :creator, :class_name => “User”
    def company
        creator.company
    end
end

This is a bad way because every time you call this method, a query will be made to the database to find out the company of project owner. We can avoid querying the db after the first call by saving the result in an instance variable.

## Good Practice

class Project < ActiveRecord::Base
    belongs_to :creator, :class_name => “User”
    def company
        @company ||= creator.company
    end
end

Using this technique, the database will be queried only the first time when you call this method. After the first invocation, the current user will be assigned to the instance variable @company and next time when you call this method, it will simply return the value of @company.

Note- Use this technique only for those cases where the cached variable value does not change frequently. Otherwise, it could lead to a potential problem of using stale data.

Use Local Variables in Place of Instance Variables in Partials

The purpose of a partial view is to reuse it anywhere but if we use instance variable in partials, it could lead to conflicting results making it hard to reuse. A better approach is to use local variables in partial views

## Bad Practice

<%= render :partial => 'header' %>

## Good Practice

<%= render :partial =>  'header', :locals => {:project => @project}%>

Prevent SQL Injection

You have heard of SQL Injection many times but still, I am discussing this point because it is very important and crucial.

If the user passes a single quote in an input text then the text after the single quote character is considered to be SQL statement. This means that the text considered a SQL statement would have direct access to the database, putting the entire database at risk as the user may have entered malicious content. So never supply user input as a database query without escaping quotes . Rails provide an easy way to do this.

## Bad Practice

User .where(“name = #{params[:name]}“)

## Good Practice

User.where(“name = ?”, params[:name])

or

User.where(:name => params[:name])

Avoid the n+1 Problem

Rails has a (in)famous query problem known as the n+1 query problem i.e eagerloading.

Take the following case where a user has a house:

class User < ActiveRecord::Base
    has_one :house
end
class House < ActiveRecord::Base
    belongs_to :user
end

## Bad Practice

A common mistake made while retrieving the address for house of each user is to do the following in the controller:

@users = User.limit(50)

In view:

<% @users.each do |user|%>
    <%= user.house.address %>
<% end %>

The above code will execute 51 queries, 1 to fetch all users and other 50 to fetch house of each user.

## Good Practice
The retrieval should be made as follows

In the Controller

@users = User.includes(:house).limit(50)

In the view

<% @users.each do |user|%>
    <%= user.house.address %>
<% end %>

The above code will execute 2 queries, 1 to fetch all users and other to fetch house for the user.

Follow The Law of Demeter

According to law of Demeter, a model should only talk to its immediate associated models. If you want to use associated attributes then you should use ‘delegate’.

Using this paradigm, a model delegates its responsibility to the associated object.

## Bad Practice

class Project < ActiveRecord::Base
    belongs_to :creator
end

In the view:

<%= @project.creator.name %>
<%= @project.creator.company %>

## Good Practice

class Project > ActiveRecord::Base
    belongs_to :creator
    delegate :name, :company, :to => :creator, :prefix => true
end

In the view:

<%= @project.creator_name %>
<%= @project.creator_company %>

Declare Instance Variables Inside the Action

As a convention, instance variables should not be hidden in private methods but declared inside the action. Though this is not a very strict guideline, doing otherwise can lead to confusion and reduce the readability of the code.

## Bad Practice

before_filter :get_project
def show
    @tasks = @project.tasks
end
private
def get_project
    @project = Project.find(params[:id])
end

## Good Practice

def show
    @project = get_project
    @tasks = @project.tasks
end
private
def get_project
    Project.find(params[:id])
end

Use Lambda in Scopes

It's important to know how scopes behave or you could be left with unexpected results. When a scope runs for the first time, it creates a method which stores the variable values and returns the result. So subsequent runs of the scope use the previous stale data. This is especially dangerous when you are running dynamic queries on date and/or time.

Take for example a scope where we want to get a result based ‘recent’ time.

### Bad Practice

scope :recent, where('starting_date > ?', 1.day.ago)
where('starting_date > ?', '02-10-2013 20:00')
where('starting_date > ?', '02-10-2013 20:00')

If today is 03/10/2013, then on the first execution it takes the correct time of 1 day ago as 02/10. However, next time the same query will use the same value of the ‘1day ago’ i.e 02/10 even if 4 days have lapsed since then.

When we wrap lambda, it regenerates date and time for even time the scope is called:

scope :recent, lambda{ where('starting_date > ?', 1.day.ago)} => It will create 
where('starting_date > ?', '02-10-2013 20:00')
where('starting_date > ?', '03-10-2013 21:23')

Use ? At the End of Method Name If It Is Returning Boolean Value

Another convention is to use ? at the end of a method returning a boolean value.

## Bad Practice

def exist 
end

## Good Practice

def exist?
end

Make Helper Methods for Views

The MVC paradigm advocates keeping the views as clean as possible from any calculations. Still, sometimes this cannot be avoided, so for those instances do the processing with helpers.

## Bad Practice

<%= f.select( :category, ['Australia', 'Belgium', 'Canada', 'China', 'India', 'Malaysia', 'Switzerland'].collect{|country| [country, country]}) %>

## Good Practice

<%= f.select( :category, country_names.collect{|country|  [country, country]}) %>

Create a Helper:

def  country_names
    ['Australia', 'Belgium', 'Canada', 'China', 'India', 'Malaysia', 'Switzerland']
end

Another common requirement that require processing on the views are conditional displays. The same technique can be applied as follows

## Bad Practice

<% case @filter %>
<% when 'inbox' %>
    <%= render 'inbox'%>
<% when 'sent' %>
    <%= render 'sent' %>
<% when 'draft' %>
    <%= render 'draft' %>
<% when 'trash'%>
    <%= render 'trash' %>
<% end %>

## Good Practice

<%= render filter_templates(@filter) %>

In helper:

def filter_templates(filter)
    case filter
    when 'inbox'
        render 'inbox'
    when 'sent'
        render 'sent'
    when 'draft'
        render 'draft'
    when 'trash'
        render  'trash'
    end
end

This is of course just the tip of the iceberg. I would love to hear from others what they have found useful over the years.

 

 

 

 

Top