activerecord-refined 0.8.0 → 0.9.0

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.
data/examples/windows.rb CHANGED
@@ -1,14 +1,16 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
1
+ # frozen_string_literal: true
2
2
 
3
- require 'active_record'
4
- require 'activerecord-refined'
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
5
4
 
6
- ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
5
+ require "active_record"
6
+ require "activerecord-refined"
7
+
8
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
7
9
  ActiveRecord::Migration.verbose = false
8
10
 
9
11
  class Setup < ActiveRecord::Migration[8.1]
10
12
  def up
11
- create_table(:sales) {|t| t.string :region; t.string :day; t.integer :amount }
13
+ create_table(:sales) { |t| t.string :region; t.string :day; t.integer :amount }
12
14
  end
13
15
  end
14
16
  Setup.new.up
@@ -16,11 +18,11 @@ Setup.new.up
16
18
  class Sale < ActiveRecord::Base
17
19
  end
18
20
 
19
- Sale.create!(region: 'east', day: '2026-01-01', amount: 100)
20
- Sale.create!(region: 'east', day: '2026-01-02', amount: 40)
21
- Sale.create!(region: 'east', day: '2026-01-03', amount: 60)
22
- Sale.create!(region: 'west', day: '2026-01-01', amount: 20)
23
- Sale.create!(region: 'west', day: '2026-01-02', amount: 90)
21
+ Sale.create!(region: "east", day: "2026-01-01", amount: 100)
22
+ Sale.create!(region: "east", day: "2026-01-02", amount: 40)
23
+ Sale.create!(region: "east", day: "2026-01-03", amount: 60)
24
+ Sale.create!(region: "west", day: "2026-01-01", amount: 20)
25
+ Sale.create!(region: "west", day: "2026-01-02", amount: 90)
24
26
 
25
27
  def show(title, relation, rows = nil)
26
28
  puts "--- #{title} ---"
@@ -32,18 +34,18 @@ end
32
34
  # 1. A window on an aggregate. over turns an aggregate into a window
33
35
  # function: the rows stay, and the aggregate is worked out over the window
34
36
  # beside each one. Without partition or order the window is every row.
35
- show('the total beside every row',
37
+ show("the total beside every row",
36
38
  Sale.select { [:region, :amount, sum(:amount).over.as(:total)] },
37
39
  Sale.select { [:region, :amount, sum(:amount).over.as(:total)] }.
38
- map {|s| [s.region, s.amount, s.total] })
40
+ map { |s| [s.region, s.amount, s.total] })
39
41
 
40
42
  # partition divides the rows into groups the window is worked out within.
41
43
  totals = -> {
42
44
  Sale.select { [:region, :amount, sum(:amount).over.partition(:region).as(:region_total)] }
43
45
  }
44
- show('a total per region, still row by row',
46
+ show("a total per region, still row by row",
45
47
  totals.call,
46
- totals.call.map {|s| [s.region, s.amount, s.region_total] })
48
+ totals.call.map { |s| [s.region, s.amount, s.region_total] })
47
49
 
48
50
  # 2. Frames. With an order, the window can be cut down to a range of rows
49
51
  # around the current one. A Range of integers is what says which: 0 is the
@@ -55,9 +57,9 @@ running = -> {
55
57
  sum(:amount).over.partition(:region).order(:day).rows(..0).as(:running)]
56
58
  }
57
59
  }
58
- show('a running total within each region',
60
+ show("a running total within each region",
59
61
  running.call,
60
- running.call.map {|s| [s.region, s.day, s.amount, s.running] })
62
+ running.call.map { |s| [s.region, s.day, s.amount, s.running] })
61
63
 
62
64
  # 3. The functions that say nothing without a window. row_number, rank and
63
65
  # dense_rank number the rows of each partition; lag and lead reach the row
@@ -70,14 +72,14 @@ ranked = -> {
70
72
  lag(:amount).over.partition(:region).order(:day).as(:previous)]
71
73
  }
72
74
  }
73
- show('the place within the region, and the day before',
75
+ show("the place within the region, and the day before",
74
76
  ranked.call,
75
- ranked.call.map {|s| [s.region, s.amount, s.place, s.previous] })
77
+ ranked.call.map { |s| [s.region, s.amount, s.place, s.previous] })
76
78
 
77
79
  begin
78
80
  Sale.select { row_number }
79
81
  rescue ArgumentError => e
80
- puts '--- a window function needs a window ---'
82
+ puts "--- a window function needs a window ---"
81
83
  puts " #{e.message}"
82
84
  puts
83
85
  end
@@ -90,7 +92,7 @@ numbered = Sale.select {
90
92
  }
91
93
  # The subquery is named after the model's own table because Active Record goes
92
94
  # on qualifying columns with that name, so where has to find it there.
93
- show('the biggest sale of each region',
95
+ show("the biggest sale of each region",
94
96
  Sale.from(numbered, :sales).where { :place == 1 },
95
97
  Sale.from(numbered, :sales).where { :place == 1 }.
96
- map {|s| [s.region, s.day, s.amount] })
98
+ map { |s| [s.region, s.day, s.amount] })
data/examples/writes.rb CHANGED
@@ -1,9 +1,11 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
1
+ # frozen_string_literal: true
2
2
 
3
- require 'active_record'
4
- require 'activerecord-refined'
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
5
4
 
6
- ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
5
+ require "active_record"
6
+ require "activerecord-refined"
7
+
8
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
7
9
  ActiveRecord::Migration.verbose = false
8
10
 
9
11
  class Setup < ActiveRecord::Migration[8.1]
@@ -22,22 +24,22 @@ Setup.new.up
22
24
  class Page < ActiveRecord::Base
23
25
  end
24
26
 
25
- Page.create!(path: '/index', hits: 10, bonus: 1, title: 'home')
26
- Page.create!(path: '/about', hits: 3, bonus: 0, title: 'about us')
27
- Page.create!(path: '/faq', hits: 0, bonus: 5, title: 'faq')
27
+ Page.create!(path: "/index", hits: 10, bonus: 1, title: "home")
28
+ Page.create!(path: "/about", hits: 3, bonus: 0, title: "about us")
29
+ Page.create!(path: "/faq", hits: 0, bonus: 5, title: "faq")
28
30
 
29
31
  # These statements run rather than being built, so unlike the other examples
30
32
  # the SQL is taken from the notification Active Record sends for each one.
31
33
  def write(title)
32
34
  statements = []
33
- subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*, payload|
35
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
34
36
  statements << payload[:sql] unless %w[SCHEMA TRANSACTION].include?(payload[:name])
35
37
  end
36
38
  yield
37
39
  ActiveSupport::Notifications.unsubscribe(subscriber)
38
40
  puts "--- #{title} ---"
39
- statements.each {|sql| puts sql }
40
- Page.order(:path).each {|p| puts " #{p.path} hits=#{p.hits} title=#{p.title}" }
41
+ statements.each { |sql| puts sql }
42
+ Page.order(:path).each { |p| puts " #{p.path} hits=#{p.hits} title=#{p.title}" }
41
43
  puts
42
44
  end
43
45
 
@@ -45,17 +47,17 @@ end
45
47
  # :hits) would set the column to the symbol itself -- and the block reads a
46
48
  # symbol as the column it names, which is what lets the new value be worked
47
49
  # out from the old.
48
- write('hits made from the old hits') do
49
- Page.where { :hits > 0 }.update_all { {hits: :hits + :bonus} }
50
+ write("hits made from the old hits") do
51
+ Page.where { :hits > 0 }.update_all { { hits: :hits + :bonus } }
50
52
  end
51
53
 
52
- write('a function over the column') do
53
- Page.update_all { {title: upper(:title)} }
54
+ write("a function over the column") do
55
+ Page.update_all { { title: upper(:title) } }
54
56
  end
55
57
 
56
58
  # An expression as involved as any other block builds.
57
- write('CASE in an update') do
58
- Page.update_all { {hits: case_when { :hits > 10 }.then(10).else(:hits)} }
59
+ write("CASE in an update") do
60
+ Page.update_all { { hits: case_when { :hits > 10 }.then(10).else(:hits) } }
59
61
  end
60
62
 
61
63
  # 2. upsert_all. The block is the part that decides what happens to a row
@@ -63,22 +65,22 @@ end
63
65
  # inserted. PostgreSQL and SQLite name it that; MySQL says VALUES(column),
64
66
  # and the block comes out as whichever the adapter reads.
65
67
  incoming = [
66
- {path: '/index', hits: 100, bonus: 0, title: 'HOME'},
67
- {path: '/new', hits: 7, bonus: 0, title: 'NEW'},
68
+ { path: "/index", hits: 100, bonus: 0, title: "HOME" },
69
+ { path: "/new", hits: 7, bonus: 0, title: "NEW" },
68
70
  ]
69
- write('the old value and the new one added together') do
70
- Page.upsert_all(incoming, unique_by: :path) { {hits: :hits + excluded(:hits)} }
71
+ write("the old value and the new one added together") do
72
+ Page.upsert_all(incoming, unique_by: :path) { { hits: :hits + excluded(:hits) } }
71
73
  end
72
74
 
73
75
  # Without a block, upsert_all overwrites -- that is Active Record's own
74
76
  # behaviour, and the block is what makes the old value reachable.
75
- write('and without a block it is a plain overwrite') do
76
- Page.upsert_all([{path: '/index', hits: 1, bonus: 0, title: 'HOME'}],
77
+ write("and without a block it is a plain overwrite") do
78
+ Page.upsert_all([{ path: "/index", hits: 1, bonus: 0, title: "HOME" }],
77
79
  unique_by: :path)
78
80
  end
79
81
 
80
82
  # insert_all has no block: Active Record type-casts each value into the VALUES
81
83
  # list, so an expression there would become nothing rather than SQL.
82
- write('insert_all takes literals') do
83
- Page.insert_all([{path: '/legal', hits: 0, bonus: 0, title: 'legal'}])
84
+ write("insert_all takes literals") do
85
+ Page.insert_all([{ path: "/legal", hits: 0, bonus: 0, title: "legal" }])
84
86
  end