1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-02 22:58:01 +00:00
Files
til/rails/scope-records-to-a-lower-or-upper-bound.md

2.0 KiB

Scope Records To A Lower Or Upper Bound

Typically when we use #where to scope queries against ActiveRecord models, we are looking to do a direct "equals" comparison.

Such as auth_codes.user_id = 1 in the example below.

> AuthCode.where(user_id: 1)
  AuthCode Load (0.4ms)  SELECT "auth_codes".* FROM "auth_codes" WHERE "auth_codes"."user_id" = 1 /* loading for pp */ LIMIT 11

We can do more powerful things with #where (assuming your database supports it, in my case PostgreSQL), such as comparing over ranges of dates. Ruby's range syntax gives us an elegant way to express ranges.

> 2..10 # range with lower bound of 2 and upper bound of 10

> 2.. # 'end'less range

> ..10 # 'begin'less range

These latter two examples are ranges that are unbounded on one side or the other. We can use these in ActiveRecord #where queries to do "greater than or equal to" and "less than or equal to" conditionals.

And we can do the same with ranges of dates like in the following queries.

> AuthCode.where(created_at: 10.days.ago..).count
  AuthCode Count (97.1ms)  SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" >= '2025-09-24 00:35:46.937715'

> AuthCode.where(created_at: 10.days.ago..5.days.ago).count
  AuthCode Count (0.6ms)  SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" BETWEEN '2025-09-24 00:35:59.901441' AND '2025-09-29 00:35:59.901512'

> AuthCode.where(created_at: ..5.days.ago).count
  AuthCode Count (0.3ms)  SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" <= '2025-09-29 00:36:09.731444'

Notice in the generated SQL how the simple #where method gets transformed into a >=, a <=, or a between clause.

And while dates are a powerful example of this, there is nothing to stop us from querying against other kinds of ranges like numeric ones.

# Orders under $10
ten_dollars_in_cents = 10 * 100
Order.where.not(fulfilled_at: nil).where(amount: ..ten_dollars_in_cents)