diff --git a/README.md b/README.md index 656d311..d9dd7f5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ pairing with smart people at Hashrocket. For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186). -_1657 TILs and counting..._ +_1658 TILs and counting..._ See some of the other learning resources I work on: - [Get Started with Vimium](https://egghead.io/courses/get-started-with-vimium~3t5f7) @@ -1126,6 +1126,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Run Some Code Whenever Rails Console Starts](rails/run-some-code-whenever-rails-console-starts.md) - [Scaffold Auth Functionality With Rails 8 Generator](rails/scaffold-auth-functionality-with-rails-8-generator.md) - [Schedule Sidekiq Jobs Out Into The Future](rails/schedule-sidekiq-jobs-out-into-the-future.md) +- [Scope Records To A Lower Or Upper Bound](rails/scope-records-to-a-lower-or-upper-bound.md) - [Secure Passwords With Rails And Bcrypt](rails/secure-passwords-with-rails-and-bcrypt.md) - [Select A Select By Selector](rails/select-a-select-by-selector.md) - [Select A Specific Rails Version To Install](rails/select-a-specific-rails-version-to-install.md) diff --git a/rails/scope-records-to-a-lower-or-upper-bound.md b/rails/scope-records-to-a-lower-or-upper-bound.md new file mode 100644 index 0000000..89e59ee --- /dev/null +++ b/rails/scope-records-to-a-lower-or-upper-bound.md @@ -0,0 +1,55 @@ +# Scope Records To A Lower Or Upper Bound + +Typically when we use +[`#where`](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-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. + +```ruby +> 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. + +```ruby +> 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. + + +```ruby +> 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. + +```ruby +# Orders under $10 +ten_dollars_in_cents = 10 * 100 +Order.where.not(fulfilled_at: nil).where(amount: ..ten_dollars_in_cents) +```