Top 5 Active Record Tips to Make Your Code More Efficient

Rupert Maspero Rupert Maspero

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

Let's build something great together.

Book a Free Scoping Workshop arrow-button-right