4 Mistakes to Avoid as a RoR Engineer

Ruby on Rails is a powerful framework that if used properly can speed up development and provide a strong foundation for a scalable application. However, some simple mistakes can quickly make Rails a burden.

Image for post
Image for post
Photo by Sarah Kilian on Unsplash

1. Forgetting to Check for N+1 Queries

N+1 occurs when a record collection is retrieved without loading its required associations. This leads to 1 query to fetch the original collection and N queries to fetch the association of each record. This causes a slowdown due to an unnecessarily large number of DB roundtrips. Instead, we should eager load the first query so we can make a single larger query (with the ID of all elements in association) to fetch the associated records. A great way to check for N+1 queries and get suggestions on how to fix them is the Bullet gem.

class User < ApplicationRecord
has_one :picture
end
class UserSerializer < ApplicationSerializer
identifier :id
fields :name, :username, :created_at
association :picture, blueprint: PictureSerializer
end
# WRONG
def index
@users = User.all
render json: UserSerializer.render(@users)
end
# CORRECT
def index
@users = User.all.includes(:picture)
render json: UserSerializer.render(@users)
end

2. Using Map Vs Pluck

Developers of other languages may use the iterator method map interchangeably with ActiveRecord specific methods such as pluck. However, the former runs once for each record in the association which requires the entire record set to be loaded in memory while the latter runs a query that returns the desired values directly.

# WRONG
MyModel.all.map(&:my_favorite_attribute)
# CORRECT
MyModel.all.pluck(:my_favorite_attribute)

3. Big Controllers

Rails controllers are meant to be small. Model related logic should be moved to the model’s class and shared logic extracted into callbacks.

Use Scopes

# WRONG
class TransactionsController < ActionController::Base
def index
@transactions = current_user.transactions.where(soft_deleted_at: nil, processed: true).order(id: :desc)
end
end
# CORRECT
class TransactionsController < ActionController::Base
def index
@transactions = current_user.transactions.valid.by_recency
end
end
class Transaction < ApplicationRecord
belongs_to :user
scope :valid, -> { where(soft_deleted_at: nil, processed: true) }
scope :by_recency, -> { order(id: :desc) }
end

Use Callbacks and Helper Methods

# WRONG
class MessagesController < ActionController::Base
def update
@message = current_user.messages.find(params[:id])
if @message.update(title: params[:message][:title], body: params[:message][:body])
render json: MessageSerializer.render(@message)
else
render json: @message.errors, status: :unprocessable_entity
end
end
end
# CORRECT
class MessagesController < ActionController::Base
before_action :set_message
def update
if @message.update(safe_params)
render json: MessageSerializer.render(@message)
else
render json: @message.errors, status: :unprocessable_entity
end
end
private def safe_params
params.require(:message).permit(:title, :body)
end
def set_message
@message = current_user.messages.find(params[:id])
end
end

4. Not Following Naming Conventions

The Rails out of the box functionality depends on a strict naming convention across the application. This follows Rails’ principles of convention over configuration. Ian Young created a GIST that outlines some of these conventions here.

Written by

A curious minded engineer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store