activerecord-refined 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +17 -0
  3. data/README.md +61 -1064
  4. data/activerecord-refined.gemspec +8 -7
  5. data/docs/conditions.md +206 -0
  6. data/docs/ctes.md +65 -0
  7. data/docs/expressions.md +125 -0
  8. data/docs/functions.md +219 -0
  9. data/docs/grouping.md +55 -0
  10. data/docs/index.md +70 -0
  11. data/docs/joins.md +73 -0
  12. data/docs/json.md +230 -0
  13. data/docs/ordering.md +55 -0
  14. data/docs/time_zones.md +30 -0
  15. data/docs/windows.md +41 -0
  16. data/docs/writing.md +33 -0
  17. data/examples/aggregations.rb +18 -0
  18. data/examples/expressions.rb +35 -5
  19. data/lib/active_record/refined/ast.rb +461 -246
  20. data/lib/active_record/refined/dialect/mariadb.rb +25 -0
  21. data/lib/active_record/refined/dialect/mysql.rb +18 -0
  22. data/lib/active_record/refined/dialect/mysql_compat.rb +67 -0
  23. data/lib/active_record/refined/dialect/oracle.rb +110 -0
  24. data/lib/active_record/refined/dialect/postgresql.rb +120 -0
  25. data/lib/active_record/refined/dialect/sql_server.rb +115 -0
  26. data/lib/active_record/refined/dialect/sqlite.rb +57 -0
  27. data/lib/active_record/refined/dialect.rb +340 -0
  28. data/lib/active_record/refined.rb +682 -192
  29. data/lib/activerecord-refined/version.rb +1 -1
  30. data/lib/activerecord-refined.rb +1 -0
  31. metadata +43 -16
  32. data/.github/workflows/push_gem.yml +0 -45
  33. data/.github/workflows/sandbox.yml +0 -295
  34. data/.github/workflows/test.yml +0 -104
  35. data/.gitignore +0 -19
  36. data/.rubocop.yml +0 -393
  37. data/Gemfile +0 -14
  38. data/Rakefile +0 -53
  39. data/benchmark/query_building.rb +0 -129
  40. data/test/test_block_syntax.rb +0 -2999
  41. data/test/test_helper.rb +0 -238
@@ -16,6 +16,7 @@ class Setup < ActiveRecord::Migration[8.1]
16
16
  t.integer :price
17
17
  t.integer :quantity
18
18
  t.integer :flags
19
+ t.date :ordered_on
19
20
  end
20
21
  end
21
22
  end
@@ -24,10 +25,16 @@ Setup.new.up
24
25
  class LineItem < ActiveRecord::Base
25
26
  end
26
27
 
27
- LineItem.create!(sku: "A-1", category: "tools", price: 1200, quantity: 2, flags: 12)
28
- LineItem.create!(sku: "A-2", category: "tools", price: 300, quantity: 5, flags: 10)
29
- LineItem.create!(sku: "B-1", category: "paper", price: 80, quantity: 10, flags: 3)
30
- LineItem.create!(sku: "C-1", category: nil, price: 50, quantity: 1, flags: 4)
28
+ LineItem.create!(sku: "A-1", category: "tools", price: 1200, quantity: 2, flags: 12,
29
+ ordered_on: Date.new(2026, 1, 5))
30
+ LineItem.create!(sku: "A-2", category: "tools", price: 300, quantity: 5, flags: 10,
31
+ ordered_on: Date.new(2026, 1, 20))
32
+ LineItem.create!(sku: "B-1", category: "paper", price: 80, quantity: 10, flags: 3,
33
+ ordered_on: Date.new(2026, 2, 3))
34
+ LineItem.create!(sku: "B-2", category: "paper", price: 80, quantity: 10, flags: 3,
35
+ ordered_on: Date.new(2026, 2, 3))
36
+ LineItem.create!(sku: "C-1", category: nil, price: 50, quantity: 1, flags: 4,
37
+ ordered_on: Date.new(2026, 2, 14))
31
38
 
32
39
  def show(title, relation, rows = nil)
33
40
  puts "--- #{title} ---"
@@ -64,6 +71,17 @@ show("a tax through BigDecimal, exact on the wire",
64
71
  LineItem.select { [:sku, (BigDecimal("1.1") * :price).as(:taxed)] }.
65
72
  map { |i| [i.sku, i.taxed] })
66
73
 
74
+ # A duration moves a date. Each adapter spells the move its own way; SQLite
75
+ # has date() and datetime(), and a date column keeps being a date.
76
+ show("a date moved by a duration",
77
+ LineItem.where { :ordered_on + 30.days < Date.new(2026, 2, 10) },
78
+ LineItem.where { :ordered_on + 30.days < Date.new(2026, 2, 10) }.pluck(:sku))
79
+
80
+ show("a due date a month on",
81
+ LineItem.select { [:sku, (:ordered_on + 1.month).as(:due_on)] },
82
+ LineItem.select { [:sku, (:ordered_on + 1.month).as(:due_on)] }.
83
+ map { |i| [i.sku, i.due_on] })
84
+
67
85
  # The bitwise operators. & and | are AND and OR between conditions, which is
68
86
  # what leaves them free here. Each expression parenthesises itself, so the
69
87
  # grouping is Ruby's rather than the adapter's.
@@ -89,12 +107,17 @@ rescue ArgumentError => e
89
107
  end
90
108
 
91
109
  # 2. Aggregates. count takes :* for COUNT(*) and distinct: true for
92
- # COUNT(DISTINCT ...); the rest are sum, avg, min and max.
110
+ # COUNT(DISTINCT ...), which sum and avg take too; the rest are min and max.
93
111
  show("COUNT(*) and COUNT(DISTINCT ...)",
94
112
  LineItem.select { [count(:*).as(:rows), count(:category, distinct: true).as(:categories)] },
95
113
  LineItem.select { [count(:*).as(:rows), count(:category, distinct: true).as(:categories)] }.
96
114
  map { |i| [i.rows, i.categories] })
97
115
 
116
+ show("SUM(DISTINCT ...), each quantity counted once",
117
+ LineItem.select { [sum(:quantity).as(:all), sum(:quantity, distinct: true).as(:once)] },
118
+ LineItem.select { [sum(:quantity).as(:all), sum(:quantity, distinct: true).as(:once)] }.
119
+ map { |i| [i.all, i.once] })
120
+
98
121
  # filter takes the aggregate over the rows a condition holds for. SQLite and
99
122
  # PostgreSQL have the FILTER clause; MySQL gets the CASE that means the same,
100
123
  # since an aggregate passes over the NULL a missed row leaves.
@@ -173,6 +196,13 @@ show("NULLS LAST",
173
196
  LineItem.order { [:category.asc.nulls_last, :sku.asc] },
174
197
  LineItem.order { [:category.asc.nulls_last, :sku.asc] }.pluck(:category, :sku))
175
198
 
199
+ # collate names a collation -- how the database compares and orders strings --
200
+ # and gives back an expression, so it carries into a comparison or an order.
201
+ # The names are the database's own; nocase is SQLite's case-insensitive one.
202
+ show("a case-insensitive comparison under a collation",
203
+ LineItem.where { :category.collate(:nocase) == "TOOLS" },
204
+ LineItem.where { :category.collate(:nocase) == "TOOLS" }.pluck(:sku))
205
+
176
206
  # Aggregates and expressions can be ordered by, too.
177
207
  show("grouped, aggregated and ordered by the aggregate",
178
208
  LineItem.