#TECH

A Whistle-Stop Tour of Everyday Ruby On Rails

After coding Ruby On Rails everyday, just got some thoughts to lay down that ‘what is preferred’ or ‘the good opinions’. Ruby on Rails is flexible, it has no hard and fast rules. But still as its ‘On Rails’ then obviously we have ‘The Rails Way’ too.

So here I share with you my own concoction to avoid some everyday goof ups.

  • Everyday mash-up : Models, Views, Controllers and Helpers

Everyday ROR should use most out of the MVC model. The motto “Fat Model, Skinny Controller” refers to how the M and C parts of MVC ideally work together. Remove all database related actions from your Controllers and put it in the model.

Controller should just process the data that comes from model and pass it onto view.

Model should contain the non-response related logic, all the database queries and validations.

View’s work is just to render data and no queries should be written on views. If there is processing more than a loop or single if else condition, then that processing should be moved to Helpers.

Helpers should be used only for processing data and not for data base queries.

We already know the above points, but still sometimes we take the easy way out.

  • Everyday temptation : default_scope is like junk food

Using default_scope for solving one problem can lead to various other problems as well.

Like if we have :

class Testimonial

default_scope where(:status => “approved”).order(“created_at desc”)

end

Now you cannot override it unless u write ‘Testimonial.unscoped’ and then do some operation like change the ordering , you want to get list of unapproved testimonials or you want total testimonials count (including unapproved ones).

#below will lead to wrong result

Testimonial.where(…).order(“title”)

It will still show the result ordered by ‘created_at’ only as its written in default scope.

Even while initializing : ‘Testimonial.new’ will create an object with status = approved.

So I would suggest use default_scope wisely or use named_scopes instead.

  • Everyday Bread & Butter : save, update_attributes, destroy

You must be quite familiar with ‘save’, ‘update_attributes’ and ‘destroy’ methods. But are you doing justice to them ?
Do you check their return values or do u check whether the object is destroyed or not?

When we use save on invalid record, it returns false. So you should always check the return value, otherwise unknowingly it will not save the record.

Or if you are sure that record will never be invalid then use save!.

Same is the case with “update_attributes”. Check its return value or use “update_attribute” instead.

When you use destroy method such as @user.destroy. It either returns false or the object you just destroyed.

So just after this you should check the return value of @user.destroyed? to get surety.

  • Every Day Trying and Rescuing

Avoid rescue unless you don’t know the exceptions you are rescuing because it will blind fold you from those exceptions.

See this example :

school = user.school

Use school.try(:name) over school.name rescue nil.

Here, if user doesn’t have school then it should raise exception rather than rescuing all to nil.

Also, try can be used when converting data types.

 nil.try(:to_i) will return #nil rather than 0.

  • Everyday Performance Challenges

You fire queries daily and they seem similar like :

User.where(……).order(….)

Assume your user table has 20 columns.

So by default your query returns all the fields of User Table for all the records. It might possible that your ‘where’ condition is fulfilled by 100’s or 1000’s or more user records. so 1000’s * 20 resulting to performance downfall.

So we should use ‘select’ with our queries and should retrieve only those fields which are required which will increase the performance.

User.where(……).select(“name, id, age”).order(….)

  • Everyday Susceptibility to SQL Injections

What’s your style of writing code ?

Unsafe :

User.where(“rating = ‘#{params[:rating}'”)

Safe:

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

or

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

In the safe case, ActiveRecord will sanitize the given params.

  • Everyday nil or blank value checks

Often our code is like :

params[:page].present? ? params[:page] : 1

or sometimes we use blank? and inverse conditions.

The cleaner way can be
params[:page].presence || 1        #which is logically same as above.

  • Everyday playing with large data query

If your code is playing with large data sets like : 10,000 or more users to send email to them.

Then you should use batch finder to avoid performance or SQL gone away error.

Unhappy Code: 

User.all.each do |user|

EmailDeliveryChannel.send(user)

end

This will eat too much memory of your server and also there is possibility of SQL gone away error if you are doing many SQL operations for each user.

Happy Code:         🙂

User.find_in_batches(:batch_size => 500) do |users|

users.each{|user| EmailDeliveryChannel.send(user) }

end

or you could also use just find_each instead of find_in_batches.

  • Everyday same old problem : N+1 Queries:

N+1 queries is serious database performance problem. Be careful.

Unhappy Code: 

In Contoller:

@schools = School.where(….).paginate(:page => params[:page, :per_page => 30)

In view:

<% @schools.each do |school| %>

<%= school.name %>

<% school.district.name %>

<% end %>

It cause N+1 queries (N is 30 in this case)

Happy Code: 🙂

In your Controller:

@schools = School.where(….).paginate(:include => :district, :page => params[:page, :per_page => 30)

By adding :include into query, number of queries reduces to only 2.

  • Everyday Caching

Apart from Page, Action , Fragment caching there is Low Level Caching too. Which can be helpful in everyday code.

Some data which we need regularly over multiple pages, like list of categories or list of top schools.

You might want to display them on every page but there is no need for up to date data everytime.

Such type of code can be wrapped under Low Level Caching:

In you controller you can write like this :

Rails.cache.fetch(“all_categories”, expires_in: 7.days) do

Category.all

end

Which will fire the query to get all categories only when it does not find ‘all_categories’ in cache.

  • Everyday Migrations :

Sometimes we add seed data through migrations rather than Rake tasks, or just update the default value of some column and leave self.down section blank.

Purpose of migration is that it should run backward and forward.
i.e.

rake db:migrate

rake db:rollback

Both should work fine without errors, else the purpose of using migration fails.

These were few things I have learned while using Ruby On Rails. Hope this makes sense and helps you to improve everyday coding . Thanks for reading .. 🙂