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.