Elegant Rails 5 Scopes

Posted on

Rails is great, with each update it’s making working with it even more enjoyable. Active Record in particular has added a bunch of little improvements that help avoid writing raw SQL (Which can be risky when combined them with the joins & includes methods). Here are a few of my favourites:

More/Less or equal to

If you pass an array into the where method with the Float::INFINITY value, it’ll output a more/less or equal to SQL equivalent.

class User < ApplicationRecord
  # Active trial: Where trial_expires_at is a time in the future
  # >= can be mixed with DateTimes.
  scope :active_trial, -> { where(trial_expires_at: [Time.zone.now..Float::INFINITY]) }

  # Has normal amount of cats: Anything less then 4.
  # <= can't be used for DateTimes.
  scope :has_normal_amount_of_cats, -> { where(cats: [Float::INFINITY..4]) }

puts User.active_trial.to_sql
# SELECT "users".* FROM "users" WHERE ("users"."trial_expires_at" >= '2017-10-03 14:26:30.806410')

puts User.has_normal_amount_of_cats.to_sql
# SELECT "users".* FROM "users" WHERE ("users"."cats" <= 4)

Between two values

Passing two integers into into the method will output SQL for “Give me rows where is field is between these values”.

class User < ApplicationRecord
  scope :expired_trial, -> { where(trial_expires_at: [Time.at(0)..Time.zone.now]) }
  scope :millennials, -> { where(dob_year: [1985..2000]) }

puts User.expired_trial.to_sql
# SELECT "users".* FROM "users" WHERE ("users"."trial_expires_at" BETWEEN '1970-01-01 00:00:00' AND '2017-10-03 14:51:43.216361')

puts User.millennials.to_sql
# SELECT "users".* FROM "users" WHERE ("users"."dob_year" BETWEEN 1985 AND 2000)

## Passing Variables

You can also pass variables into your scope.

class User < ApplicationRecord
  scope :with_dob_year, -> (year){ where(dob_year: year) }

puts User.with_dob_year(2001).to_sql
# SELECT "users".* FROM "users" WHERE "users"."dob_year" = 2001


The not method is useful for when you require a field to not match a value.

class User < ApplicationRecord
  scope :confirmed, -> { where.not(confirmed_at: nil) }

puts User.confirmed.to_sql
# SELECT "users".* FROM "users" WHERE ("users"."confirmed_at" IS NOT NULL)