Top 5 Active Record Tips to Make Your Code More Efficient
Rupert Maspero
Active Record has to be my favourite part of Rails. There is an array of really helpful lesser-known methods you can use to streamline queries, reduce memory bloat, and avoid excessive database calls. Here are some of my go-to’s:
1. Load Async
A recent favourite of mine is .load_async
. This method was added in Rails 7 and allows you to run queries asynchronously in the background. This is great if you are trying to query multiple independent models in one controller.
class BlogController < ApplicationController
def homepage
@accounts = Account.load_async
@posts = Post.load_async
@comments = Comment.load_async
end
end
If you are unsure whether asynchronous querying will benefit you, you can add the .slow
scope to your models to simulate a 1 second delay, then look at your logs to see how long the queries took to complete.
class Account < ApplicationRecord
scope :slow, -> {
where("SELECT true FROM pg_sleep(1)")
}
end
class Post < ApplicationRecord
scope :slow, -> {
where("SELECT true FROM pg_sleep(1)")
}
end
class BlogController < ApplicationController
def homepage
@accounts = Account.slow
@posts = Post.slow
# total db query time - 2s
end
def homepage_load_async
@accounts = Account.slow.load_async
@posts = Post.slow.load_async
# total db query time - 1s
end
end
Note: Rails 7.1 has even more asynchronous database methods e.g. .async_count
and .async_pluck
.
2. Where Not Nils
When using .where.not()
, Active Record excludes any nil values. This can be confusing so don’t forget to re-include them with an .or()
.
class BlogController < ApplicationController
def homepage
# returns all non male accounts excluding nil values
@accounts = Account.where.not(gender: "male")
# returns all non male accounts including nil values
@accounts = Account.where.not(gender: "male").or(Account.where(gender: nil))
end
end
3. Pluck
.pluck
is an extremely powerful method, allowing you to return a simple ruby array instead of loading in a full model.
You can also perform Arel and SQL in a .pluck
to combine fields; this can be very useful when, for example, retrieving full names from a database.
This is a great way to speed up slow queries for specific data.
class BlogController < ApplicationController
def homepage
@accounts = Account.pluck(:id, :first_name, :last_name)
@accounts = Account.pluck(Arel.sql('id, CONCAT_WS(\' \', first_name, last_name)')
end
end
4. Nested Select
.select
can be handy when trying to avoid memory bloat when loading a large number of models — it will only select the chosen data for the records returned.
However, a common refactor is to use .select()
in place of a nested .pluck
. If you nest the .select
, Active Record will build a nested SQL string and perform the request within the database in a single connection — as opposed to performing the .pluck
, loading the array into ruby, and then performing the next database query. This can make a noticeable difference in instances when loading large numbers of models.
class BlogController < Application Controller
def homepage
@posts = Post.where(users: Organisation.user_organisations.where(state: :active).pluck(:user_id))
@posts = Post.where(users: Organisation.user_organisations.where(state: :active).select(:user_id))
end
end
5. Size
.size
, .count
, and .length
all return the number of items in an array or Active Record collection.
An ActiveRecord::Relation
, however, uses them a bit differently. .count
will always run a query in the database, whilst .length
will return the number of items in the collection based on the objects currently in memory.
If you already have the object in memory, you might want to use .length
to avoid another database call. If don’t have them loaded in and just want the numerical value, .count
will output that number without loading in the objects. There are times when making this distinction is not easy, and this is where .size
comes in — it will adapt for you, using .length
if you have objects loaded and .count
if not.
Bonus Tip
To speed up slow counts, use counter caches or the counter culture gem for more complex counter caches.
- Ruby
- Rails
- Software Development
- Efficiency
- Coding
- Active Record