[Awesome Ruby Gem] Use type_scopes gem to provide useful scopes based on columns' types (dates, times, strings and numerics)

Type Scopes

Type scopes creates useful semantic scopes based on the type of the columns of your models. It handles dates, times, strings, numerics and booleans.

Installation

You can install it as a gem:

1
$ gem install type_scopes

or add it into a Gemfile (Bundler):

1
2
3
4
5
# Gemfile

# BaseSecrete/type_scopes: Automatic scopes for ActiveRecord models.
# https://github.com/BaseSecrete/type_scopes
gem 'type_scopes', '0.4.0'

Usages

Call TypeScopes.inject from your models:

1
2
3
4
5
6
7
8
# /app/models/transaction.rb
class Transaction < ApplicationRecord
# Creates scope for all supported column types
TypeScopes.inject self

# Or if you prefer to enable scopes for specific columns only
TypeScopes.inject self, :amount, :paid_at
end

In case there is a conflict with a scope name, TypeScopes won’t over write your existing scope. You can safely inject TypeScopes and it won’t break any scope defined previously.

Here are examples for all the available scopes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# paid_at: datetime
# amount: decimal
# description: string
class Transaction < ActiveRecord::Base
TypeScopes.inject self
end

# Time scopes
Transaction.paid_to("2017-09-06") # => where("paid_at <= '2017-09-06'")
Transaction.paid_from("2017-09-06") # => where("paid_at >= '2017-09-06'")
Transaction.paid_after("2017-09-06") # => where("paid_at > '2017-09-06'")
Transaction.paid_before("2017-09-06") #= where("paid_at < '2017-09-06'")
Transaction.paid_between("2017-09-06", "2017-09-07") # => where("paid_at BETWEEN '2017-09-06' AND '2017-09-07'")
Transaction.paid_not_between("2017-09-06", "2017-09-07") # => where("paid_at NOT BETWEEN '2017-09-06' AND '2017-09-07'")
Transaction.paid_within("2017-09-06", "2017-09-07") # => where("paid_at > '2017-09-06' AND paid_at < '2017-09-07'")
Transaction.paid_not_within("2017-09-06", "2017-09-07") # => where("paid_at <= '2017-09-06' OR paid_at >= '2017-09-07'")

# Numeric scopes
Transaction.amount_to(100) # => where("amount <= 100")
Transaction.amount_from(100) # => where("amount >= 100")
Transaction.amount_above(100) # => where("amount > 100")
Transaction.amount_below(100) # => where("amount < 100")
Transaction.amount_between(100, 200) # => where("amount BETWEEN 100 AND 200")
Transaction.amount_not_between(100, 200) # => where("amount NOT BETWEEN 100 AND 200")
Transaction.amount_within(100, 200) # => where("amount > 100 AND amount < 200")
Transaction.amount_not_within(100, 200) # => where("amount <= 100 OR amount >= 200")

# String scopes
Transaction.description_contains("foo") # => where("description LIKE '%foo%'")
Transaction.description_contains("foo", sensitive: false) # => where("description ILIKE '%foo%'")
Transaction.description_starts_with("foo") # => where("description LIKE 'foo%'")
Transaction.description_starts_with("foo", sensitive: false) # => where("description ILIKE 'foo%'")
Transaction.description_does_not_start_with("foo") # => where("description NOT LIKE 'foo%'")
Transaction.description_does_not_start_with("foo", sensitive: false) # => where("description NOT ILIKE 'foo%'")
Transaction.description_ends_with("foo") # => where("description LIKE '%foo'")
Transaction.description_ends_with("foo", sensitive: false) # => where("description ILIKE '%foo'")
Transaction.description_does_not_end_with("foo") # => where("description NOT LIKE '%foo'")
Transaction.description_does_not_end_with("foo", sensitive: false) # => where("description NOT ILIKE '%foo'")
Transaction.description_like("%foo%") # => where("description LIKE '%foo%'")
Transaction.description_not_like("%foo%") # => where("description NOT LIKE '%foo%'")
Transaction.description_ilike("%foo%") # => where("description ILIKE '%foo%'")
Transaction.description_not_ilike("%foo%") # => where("description NOT ILIKE '%foo%'")
Transaction.description_matches("^Regex$") # => where("description ~ '^Regex$'")
Transaction.description_does_not_match("^Regex$") # => where("description !~ '^Regex$'")

# Boolean scopes
Transaction.non_profit # => where("non_profit = true")
Transaction.not_non_profit # => where("non_profit = false")
Transaction.is_valid # => where("is_valid = true")
Transaction.is_not_valid # => where("is_valid = false")
Transaction.has_payment # => where("has_payment = true")
Transaction.has_not_payment # => where("has_payment = false")
Transaction.was_processed # => where("was_processed = true")
Transaction.was_not_processed # => where("was_processed = false")

For the string colums, the pattern matching is escaped. So it’s safe to provide directly a user input. There is an exception for the column_like, column_ilike, column_matches and column_does_not_match where the pattern is not escaped and you shouldn’t provide untrusted strings.

1
Transaction.description_contains("%foo_") # => where("description LIKE '%[%]foo[_]%'")

References

[1] BaseSecrete/type_scopes: Automatic scopes for ActiveRecord models. - https://github.com/BaseSecrete/type_scopes

[2] type_scopes | RubyGems.org | your community gem host - https://gems.ruby-china.com/gems/type_scopes

[3] Guide to name scopes based on column types - https://bhserna.com/guide-to-name-scopes-based-on-column-types.html