-
Notifications
You must be signed in to change notification settings - Fork 5
/
arguments_support.rb
67 lines (55 loc) · 1.7 KB
/
arguments_support.rb
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
55
56
57
58
59
60
61
62
63
64
65
66
67
# frozen_string_literal: true
require "n1_loader/active_record"
require_relative 'context/setup_database'
class User < ActiveRecord::Base
has_many :payments
n1_optimized :payments_total do
# Arguments can be:
# argument :something, optional: true
# argument :something, default: -> { 100 }
#
# Note: do not use mutable (mostly timing related) defaults like:
# argument :from, default -> { 2.minutes.from_now }
# because such values will be unique for every loader call which will make N+1 issue stay
argument :from
argument :to
# This is used to define logic how loaders are compared to each other
# default is:
# cache_key { *arguments.map(&:object_id) }
cache_key { [from, to] }
def perform(users)
total_per_user =
Payment
.group(:user_id)
.where(created_at: from..to)
.where(user: users)
.sum(:amount)
.tap { |h| h.default = 0 }
users.each do |user|
total = total_per_user[user.id]
fulfill(user, total)
end
end
end
end
class Payment < ActiveRecord::Base
belongs_to :user
validates :amount, presence: true
end
fill_database
from = 2.days.ago
to = 1.day.ago
# Has N+1
p User.all.map { |user|
user.payments.select do |payment|
payment.created_at >= from && payment.created_at <= to
end.sum(&:amount)
}
# Has no N+1 but we load too many data that we don't need
p User.all.includes(:payments).map { |user|
user.payments.select do |payment|
payment.created_at >= from && payment.created_at <= to
end.sum(&:amount)
}
# Has no N+1 and calculation is the most efficient
p User.all.includes(:payments_total).map { |user| user.payments_total(from: from, to: to) }