Hello Folks!
Since it's start Ruby on Rails is considered as one of the most productive web development framework. It makes development very fast and make this like a fun. That's why this getting more populer among web developers. But when it comes to performance and best practices few developer missed the most important tricks and that raise some concerns. In this blog I am going to share couple of things that you must notice if you are a Rails developer.
1. Finding an object should handle the not found exception
For every rails developer, there is an easy way in controllers to write a method for finding an object like this:
def find_some_object
@some_object = SomeObject.find(params[:id])
end
The simplest way of finding (or fetching an object from database) right? However this not a suggested way of writing this method. What happen if the object does not existed(there might be two reasons for it. Either the record has been deleted or not created so far) in the databse with the ID you are passing as params?
This will simply break things. You will get an error for "Completed 404 Not Found". With this error you can easly deal in development or test environment but this will be a very bad experience if your application running in production environment(live mode) and customers are facing this. Instead they will prefer to redirect on some working page with a meaningful message like "The resource you are looking for does not exist".
How you will write such method? Is this HARD to wtite? No. Not at all. You just need to rescue from not found exception. Have a look on the method below:
def find_some_object
@some_object = SomeObject.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to root_path, notice: "The resource you are looking for does not exist"
end
You can do following for APIs by just changing the redirect_to
line.
def find_some_object
@some_object = SomeObject.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { errors: "The resource you are looking for does not exist" }, status: :not_found
end
2. Avoid loading all objects into database at once
Consider the following method:
def index
@some_objects = SomeObject.all
end
This method will instantiate all the objects of a model into database at once. And this is very inefficient practice in general as this create great memory consumption.
This will not raise any error but will be very important from performance point of view. The reason behind this is that initially when you start building an application, it exists in your local environment where you have very little data to test things. Like 100 records per resource normally. But when your application is production ready and customers start using this, thier might be huge amount of data in your database over the time. And then loading a model's instances into database at once your application will face some performance issue. So you have to right the method like this:
def index
@some_objects = SomeObject.find_each
end
find_each
is a batch processing method which allow you to work with the records in batches, thereby this greatly reduce the memory consumption.
find_each
method uses find_in_batches
with a default batch size of 1000 records.
3. Avoid N+1 query problem
This query problem take place when there are associations. Consider the following example:
class Parent < ApplicationRecord
has_many :children
end
class Child < ApplicationRecord
belongs_to :parent
end
Now if you want to load parent records and want to use associated child instances for getting some information then you may write code something like this:
parants = Parent.limit(10)
parents.each do |parent|
parent.children.each do |child|
puts child.name
end
end
The number of queries executed by this code will be 1(for loading 10 parent records) + N(number of children for each parent). N + 1 queries in total.
But Rails has already provided solutions for avoiding such cases by using Eager Loading. By eager loading associations you can prevent N + 1 queries in your code. Look at the following snippet:
parents = Parent.includes(:children).limit(10)
parents.each do |parent|
parent.children.each do |child|
puts child.name
end
end
This code will execute just 2 queries, as opposed to N + 1 queries in the previous case. 1 for loading 10 parent records and 1 for loading children of parent.
4. Avoid writing any secret information into code
Many of the rails applications use the secret credentials like third party API keys(like Stripe, Google APIs, Sendgrid, Twilio, etc), tokens, server secrets like AWS, and many more. And you must prevent yourself by providing these kind of information directly into your code. Even if you are just doing testing and planning for removing this from code later. Don't do this.
There are various gems(like figaro
, dot-env
) which provides you the facility to store your secret variables or any other sensitive information as environment(ENV) variables. With the rails versions after rails 5.2
or higher you can store your secrets into config/credentials.yml.ync
user a config/master.key
.
More important is once you separate your secrets into a secific file(mostly application.yml
, .env
, secrets.yml
, config/master.key
) then add these files into applications .gitignore
file.
Conclusion
These are some common practices and also very important when show your work to your clients as code samples. So keep following these practices. Because some clients/recruiters have great technical knowledge indepth and they can raise concern on bad practices.