RoR Querying with ActiveRecord

ActiveRecord gives you a full toolkit for fetching data from your database using Ruby. You never need to write raw SQL for common operations. Every query method returns either a single record, an array of records, or an ActiveRecord::Relation object that you can chain with more conditions.

Basic Finders

Product.all                         # Every product
Product.first                       # First record by id
Product.last                        # Last record by id
Product.find(5)                     # Product with id = 5 (raises error if missing)
Product.find_by(name: "Widget")     # First match (returns nil if missing)
Product.find_by!(name: "Widget")    # Same but raises error if missing
Product.count                       # Total number of products

Filtering with where

Product.where(available: true)
Product.where(category: "electronics")
Product.where("price > ?", 50)
Product.where("price BETWEEN ? AND ?", 10, 100)
Product.where(category: ["books", "electronics"])    # IN clause
Product.where.not(status: "archived")
Product.where("name LIKE ?", "%phone%")

Always use ? placeholders instead of string interpolation. This prevents SQL injection:

SAFE:   Product.where("name = ?", params[:name])
UNSAFE: Product.where("name = '#{params[:name]}'")   ← never do this

Ordering Results

Product.order(:name)                        # A to Z
Product.order(name: :asc)                   # Same
Product.order(name: :desc)                  # Z to A
Product.order(price: :asc, name: :asc)      # Sort by price, then name
Product.order("created_at DESC")            # Raw SQL string

Limiting and Offsetting

Product.limit(10)                           # First 10 records
Product.offset(20)                          # Skip first 20
Product.limit(10).offset(20)                # Records 21–30 (page 3 of 10)

Chaining Queries

Query methods return an ActiveRecord::Relation, which means you chain them together. Rails builds one SQL query and runs it only when the data is actually needed.

Product
  .where(available: true)
  .where("price < ?", 100)
  .order(price: :asc)
  .limit(5)

Generated SQL:
SELECT * FROM products
WHERE available = true AND price < 100
ORDER BY price ASC
LIMIT 5

This lazy evaluation is efficient — Rails waits until you actually use the data (loop through it, call .to_a, etc.) before hitting the database.

Selecting Specific Columns

Product.select(:id, :name, :price)
Product.select("name, price")
Product.pluck(:name)             # Returns array of names only: ["Widget", "Gadget"]
Product.pluck(:id, :name)        # Returns array of pairs: [[1, "Widget"], [2, "Gadget"]]

pluck is faster than select because it skips building Ruby objects and returns raw values directly.

Aggregation Methods

Product.count                              # Total count
Product.count(:category)                   # Count of non-nil categories
Product.sum(:price)                        # Sum of all prices
Product.average(:price)                    # Average price
Product.minimum(:price)                    # Lowest price
Product.maximum(:price)                    # Highest price

Product.where(available: true).average(:price)  # Average price of available products

Grouping Results

Product.group(:category).count
# => { "electronics" => 12, "books" => 8, "clothing" => 20 }

Product.group(:category).sum(:price)
# => { "electronics" => 15000, "books" => 400 }

Product.group(:available).count
# => { true => 35, false => 5 }

Joining Tables

# INNER JOIN — only posts that have a user
Post.joins(:user)

# Filter joined data
Post.joins(:user).where(users: { active: true })

# LEFT OUTER JOIN — all posts, even those without a user
Post.left_outer_joins(:user)

Eager Loading — Fix N+1 Queries

When you display posts and their authors, a common mistake causes one query per post (the N+1 problem). Eager loading fixes it with one extra query upfront.

BAD (N+1 problem):
@posts = Post.all
@posts.each do |post|
  puts post.user.name    ← runs a separate SQL query per post
end
# 1 query for posts + 1 query per post = N+1 queries

GOOD (eager loading):
@posts = Post.includes(:user).all
@posts.each do |post|
  puts post.user.name    ← no extra query; data already loaded
end
# 2 queries total regardless of how many posts exist

Query Diagram

Your Code
  Product.where(available: true).order(:price).limit(10)
        |
        v
ActiveRecord::Relation built (lazy — no DB hit yet)
        |
        v
You loop through it in a view or call .to_a
        |
        v
ActiveRecord sends SQL to the database:
  SELECT * FROM products WHERE available = TRUE ORDER BY price ASC LIMIT 10
        |
        v
Database returns rows
        |
        v
ActiveRecord maps rows to Product objects
        |
        v
Your code has 10 Product objects to work with

Scopes for Reusable Queries

class Product < ApplicationRecord
  scope :available,    -> { where(available: true) }
  scope :affordable,   -> { where("price < 50") }
  scope :by_category,  -> (cat) { where(category: cat) }
  scope :recent,       -> { order(created_at: :desc).limit(10) }
end

# Use them anywhere
Product.available
Product.available.affordable
Product.by_category("books").recent

Existence Checks

Product.exists?(id: 5)               # Does a product with id=5 exist?
Product.exists?(name: "Widget")      # Does a product named Widget exist?
user.posts.exists?                   # Does this user have any posts?
user.posts.any?                      # Same result
user.posts.empty?                    # Opposite
user.posts.none?                     # Same as empty?

Mastering ActiveRecord queries lets you fetch exactly the data you need efficiently. Combine scopes, filters, ordering, and eager loading to write clean, fast, and readable data access code.

Leave a Comment

Your email address will not be published. Required fields are marked *