May 13, 2026
The twelve Active Record finders I actually use
A reference card of the Active Record query methods that earn their place in my day-to-day Rails code, with one example each.
Active Record’s query interface has dozens of methods. I use about twelve of them on a normal week. The rest — none, unscope, from, lock, the find_or_* family — show up occasionally but don’t earn cheatsheet space.
This is the cheatsheet. One example per method, grouped by what I’m trying to do: look something up, narrow a set, or walk a set. Examples are illustrative.
Lookup: pulling a single record by ID or attribute
1. find
# illustrative
user = User.find(params[:id])
Raises ActiveRecord::RecordNotFound if the record doesn’t exist, which Rails translates to a 404 in controllers. This is what I want 95% of the time when looking up by primary key — the controller should not silently do nothing on a missing record.
2. find_by
# illustrative
user = User.find_by(email: params[:email])
return head :not_found unless user
The non-primary-key counterpart. Returns nil instead of raising. I use this when looking up by a non-primary attribute and “not found” is a normal case the caller has to handle. There’s a find_by! variant that raises if you’d rather have the exception.
3. take
# illustrative
candidate = User.where(active: true).take
Like first, but without the ORDER BY id ASC clause. When I genuinely don’t care which row the database hands me — picking any active user for a smoke test, grabbing one example to inspect — take is faster on large tables because it doesn’t sort. If you find yourself writing first and then realizing you don’t need ordering, switch.
4. sole
# illustrative
admin = User.where(role: "admin").sole
Returns the single matching record, or raises if there are zero or more than one. This is the assertion-style finder: I’m telling the reader (and the test suite) that the query must return exactly one row, and I want to crash immediately if invariants slip. Underused. Anywhere you’ve written .first and silently relied on there being exactly one record, sole is the better expression of intent.
Filter: narrowing a set
5. where.not
# illustrative
Article.where.not(status: "draft")
The negation chain. Generates WHERE status != 'draft' (with the right NULL semantics — where.not(deleted_at: nil) does what you’d expect). Reads cleanly, composes with other scopes, and avoids the temptation to write SQL fragments by hand.
6. where.missing
# illustrative
Author.where.missing(:articles)
Rails 6.1+. Returns the records that don’t have any associated rows — authors with no articles, in this case. Generates a LEFT OUTER JOIN ... WHERE articles.id IS NULL under the hood. I reach for this when looking for orphans (users with no orders, projects with no tasks) and it replaces the awkward LEFT JOIN-and-check-NULL pattern with one method call.
7. or
# illustrative
Article.where(featured: true).or(Article.where("views > ?", 10_000))
Combines two relation queries with SQL OR. The two halves have to be on the same model and otherwise structurally compatible. I use it sparingly because complex OR chains get hard to read fast — but for “either important by editor or important by traffic” filters, it’s the right tool.
Filter: pulling fields, not records
8. pluck
# illustrative
ids = Article.where(published: true).pluck(:id)
Returns an array of values for the named column(s), running a SELECT col FROM ... instead of materializing full Active Record objects. When I need a list of IDs to pass to a background job, or a hash of [id, slug] pairs for a lookup table, pluck is the right query. Don’t use it when you’d then load the records anyway — that’s two queries instead of one.
9. pick
# illustrative
slug = Article.where(id: params[:id]).pick(:slug)
pluck for a single row. Returns the value (or array of values) from the first matching row, or nil. I use it when I need exactly one column from one row and don’t want the noise of .pluck(:slug).first. Faster too, because it adds LIMIT 1.
10. exists?
# illustrative
return unless Article.where(user_id: current_user.id, published: true).exists?
The cheapest “does this exist” query. Generates SELECT 1 AS one FROM ... LIMIT 1. When the answer is yes-or-no and I don’t need the record itself, exists? beats .any? (which loads the relation), .count > 0 (which counts every matching row), and .present? on the relation (same problem as .any?).
Iterate: walking a large set
11. find_each
# illustrative
User.where(active: true).find_each(batch_size: 500) do |user|
WelcomeBack.deliver_later(user)
end
The single most important method on this list for production safety. find_each walks a relation in batches (default 1,000) and yields one record at a time, so a query against a million-row table doesn’t try to load a million Active Record objects into memory. Anywhere you’ve written User.all.each, you wanted find_each. The version of you that gets paged at 3 a.m. when the worker OOMs will thank you.
12. in_batches
# illustrative
User.where(active: true).in_batches(of: 1_000) do |relation|
relation.update_all(last_seen_at: Time.current)
end
find_each’s sibling for when you want the batch itself, not the individual records. The block receives a relation, which means you can call update_all, delete_all, or any other relation method on it. For bulk updates against millions of rows — backfills, soft-delete sweeps, denormalized-column refreshes — this is the pattern. One UPDATE statement per batch, no Ruby-side instantiation, no callbacks firing per record.
The shape I keep seeing
The unifying thread across these twelve: each one expresses a more specific intent than the one you’d reach for by default.
findoverwhere(id:).firstfind_byoverwhere(email:).firstsoleoverfirstwhen you mean “exactly one”pluckover.map(&:id)exists?over.any?find_eachover.eachwhere.missingover the LEFT JOIN dance
Choosing the more specific method does two things. First, it tells the next reader what you actually meant — sole and first look similar but say very different things. Second, it generates the more specific SQL, which is almost always also the faster SQL. The Active Record query interface rewards reading the docs once.
These twelve are mine. Yours might be slightly different — a heavy report-writing app probably reaches for group and having more often than I do, and a CRUD-heavy app might use find_or_create_by more. But the shape — pick the most specific finder, let the SQL match the intent — generalizes.