jit_preloader 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8dd36dd4639cc012aff19a8afdfb6bee699cdd1c
4
- data.tar.gz: 41d7e22054a231edba2dc80ce3d19ac16e57cef9
3
+ metadata.gz: d177cc9a1bc25389c34ee7088fa9d0a6182968d2
4
+ data.tar.gz: 7c265ce1139ab5bd96e978bc3e3213476b335775
5
5
  SHA512:
6
- metadata.gz: 9cb4b5385ab0e3e7a321ac759372dbd6bd715141b89b92c82773c40f547bef92311bdfa365ad43a7b5d544c8983962f769d39a02949476fa660da305b59792d8
7
- data.tar.gz: 5f24a4834a8bff484cdb849702ad71b62ddc10289134dbc1f6ad9431644015244a4ab252a6ddb74ac9d91ed47b9e1963af3cf2041fcd305e3eff0493cf1477b5
6
+ metadata.gz: cb77c853c929c4bc7c8379de9ebf00240d4f4016dc28814c7745db21365376d838b93a46afc8e89be269603e680c762919dd8ee3f9a8f5ac752e72064803b1e8
7
+ data.tar.gz: e493b69a1feea9c2ecc5502f66959ae18a02a8414e1b926137123016f01a022d6e12bdadafc8664b29b106a85d7acbc49b42521bc52c062f88d2c8220cceed3b
data/README.md CHANGED
@@ -137,6 +137,42 @@ Contact.jit_preload.each do |contact|
137
137
  end
138
138
  ```
139
139
 
140
+ ### Loading aggregate methods on associations
141
+
142
+ There is now a `has_many_aggregate` method available for ActiveRecord::Base. This will dynamically create a method available on objects that will allow making aggregate queries for a collection.
143
+
144
+ ```ruby
145
+ # old
146
+ Contact.all.each do |contact|
147
+ contact.addresses.maximum("LENGTH(street)")
148
+ contact.addresses.count
149
+ end
150
+ # SELECT * FROM contacts
151
+ # SELECT MAX(LENGTH(street)) FROM addresses WHERE contact_id = 1
152
+ # SELECT COUNT(*) FROM addresses WHERE contact_id = 1
153
+ # SELECT MAX(LENGTH(street)) FROM addresses WHERE contact_id = 2
154
+ # SELECT COUNT(*) FROM addresses WHERE contact_id = 2
155
+ # SELECT MAX(LENGTH(street)) FROM addresses WHERE contact_id = 3
156
+ # SELECT COUNT(*) FROM addresses WHERE contact_id = 3
157
+ # ...
158
+
159
+ #new
160
+ class Contact < ActiveRecord::Bas
161
+ has_many :addresses
162
+ has_many_aggregate :addresses, :max_street_length, :maximum, "LENGTH(street)"
163
+ has_many_aggregate :addresses, :count_all, :count, "*"
164
+ end
165
+
166
+ Contact.jit_preload.each do |contact|
167
+ contact.addresses_max_street_length
168
+ contact.adddresses_count_all
169
+ end
170
+ # SELECT * FROM contacts
171
+ # SELECT contact_id, MAX(LENGTH(street)) FROM addresses WHERE contact_id IN (1, 2, 3, ...) GROUP BY contact_id
172
+ # SELECT contact_id, COUNT(*) FROM addresses WHERE contact_id IN (1, 2, 3, ...) GROUP BY contact_id
173
+
174
+ ```
175
+
140
176
  ### Jit preloading globally across your application
141
177
 
142
178
  The JitPreloader can be globally enabled, in which case most N+1 queries in your app should just disappear. It is off by default.
@@ -165,7 +201,6 @@ This is mostly a magic bullet, but it doesn't solve all database-related problem
165
201
  ```ruby
166
202
  Contact.all.each do |contact|
167
203
  contact.emails.reload # Reloading the association
168
- contact.phone_numbers.max("LENGTH(number)") # Aggregate functions on the association
169
204
  contact.addresses.where(billing: true).to_a # Querying the association
170
205
  end
171
206
  ```
@@ -5,10 +5,43 @@ module JitPreloadExtension
5
5
  included do
6
6
  attr_accessor :jit_preloader
7
7
  attr_accessor :jit_n_plus_one_tracking
8
+ attr_accessor :jit_preload_aggregates
8
9
  end
9
10
 
10
11
  class_methods do
11
12
  delegate :jit_preload, to: :all
13
+
14
+ def has_many_aggregate(assoc, name, aggregate, field, default: 0)
15
+ define_method("#{assoc}_#{name}") do
16
+ self.jit_preload_aggregates ||= {}
17
+ return jit_preload_aggregates[aggregate] if jit_preload_aggregates[aggregate]
18
+ if jit_preloader
19
+ reflection = association(assoc).reflection
20
+ primary_ids = jit_preloader.records.collect{|r| r[reflection.active_record_primary_key] }
21
+ klass = reflection.klass
22
+
23
+ preloaded_data = Hash[klass
24
+ .where(reflection.foreign_key => primary_ids)
25
+ .group(reflection.foreign_key)
26
+ .send(aggregate, field)
27
+ ]
28
+
29
+ jit_preloader.records.each do |record|
30
+ record.jit_preload_aggregates ||= {}
31
+ record.jit_preload_aggregates[aggregate] = preloaded_data[record.id] || default
32
+ end
33
+ else
34
+ self.jit_preload_aggregates[aggregate] = send(assoc).send(aggregate, field) || default
35
+ end
36
+ jit_preload_aggregates[aggregate]
37
+ end
38
+
39
+ def reload(*args)
40
+ self.jit_preload_aggregates = {}
41
+ super
42
+ end
43
+
44
+ end
12
45
  end
13
46
  end
14
47
 
@@ -1,3 +1,3 @@
1
1
  module JitPreloader
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -4,9 +4,9 @@ RSpec.describe JitPreloader::Preloader do
4
4
 
5
5
  let!(:contact1) do
6
6
  Contact.create(
7
- name: "Only Addresses",
7
+ name: "Only Addresses",
8
8
  addresses: [
9
- Address.new(street: "123 Fake st", country: canada),
9
+ Address.new(street: "123 Fake st", country: canada),
10
10
  Address.new(street: "21 Jump st", country: usa)
11
11
  ]
12
12
  )
@@ -21,9 +21,9 @@ RSpec.describe JitPreloader::Preloader do
21
21
 
22
22
  let!(:contact3) do
23
23
  Contact.create(
24
- name: "Both!",
24
+ name: "Both!",
25
25
  addresses: [
26
- Address.new(street: "1 First st", country: canada),
26
+ Address.new(street: "1 First st", country: canada),
27
27
  Address.new(street: "10 Tenth Ave", country: usa)
28
28
  ],
29
29
  email_address: EmailAddress.new(address: "woot@woot.com"),
@@ -38,6 +38,36 @@ RSpec.describe JitPreloader::Preloader do
38
38
  ->(event, data){ source_map[data[:source]] << data[:association] }
39
39
  end
40
40
 
41
+ context "when preloading an aggregate" do
42
+ context "without jit_preload" do
43
+ it "generates N+1 query notifications for each one" do
44
+ ActiveSupport::Notifications.subscribed(callback, "n_plus_one_query") do
45
+ counts = [2,0,2]
46
+ maxes = [11,0,12]
47
+ Contact.all.each_with_index do |c, i|
48
+ expect(c.addresses_count).to eql counts[i]
49
+ expect(c.addresses_max_street_length).to eql maxes[i]
50
+ end
51
+ end
52
+ contact_queries = [contact1,contact2, contact3].product([["addresses.count", "addresses.maximum"]])
53
+ expect(source_map).to eql(Hash[contact_queries])
54
+ end
55
+ end
56
+ context "without jit_preload" do
57
+ it "generates N+1 query notifications for each one" do
58
+ ActiveSupport::Notifications.subscribed(callback, "n_plus_one_query") do
59
+ counts = [2,0,2]
60
+ maxes = [11,0,12]
61
+ Contact.jit_preload.each_with_index do |c, i|
62
+ expect(c.addresses_count).to eql counts[i]
63
+ expect(c.addresses_max_street_length).to eql maxes[i]
64
+ end
65
+ end
66
+ expect(source_map).to eql({})
67
+ end
68
+ end
69
+ end
70
+
41
71
  context "when we marshal dump the active record object" do
42
72
  it "nullifes the jit_preloader reference" do
43
73
  contacts = Contact.jit_preload.to_a
@@ -73,7 +103,7 @@ RSpec.describe JitPreloader::Preloader do
73
103
  end
74
104
  contact_queries = [contact1,contact2, contact3].product([["addresses.count", "addresses.sum"]])
75
105
  expect(source_map).to eql(Hash[contact_queries])
76
- end
106
+ end
77
107
  end
78
108
  end
79
109
 
@@ -85,7 +115,7 @@ RSpec.describe JitPreloader::Preloader do
85
115
  end
86
116
  contact_queries = [contact1,contact2, contact3].product([["addresses.count", "addresses.sum"]])
87
117
  expect(source_map).to eql(Hash[contact_queries])
88
- end
118
+ end
89
119
  end
90
120
 
91
121
  context "when explicitly finding a contact" do
@@ -140,7 +170,7 @@ RSpec.describe JitPreloader::Preloader do
140
170
  contact_queries = [contact1,contact2,contact3].product([[:email_address]])
141
171
  address_queries = Address.all.product([[:country]])
142
172
  expect(source_map).to eql(Hash[address_queries.concat(contact_queries)])
143
- end
173
+ end
144
174
  end
145
175
 
146
176
  context "and we use jit preload" do
@@ -1,6 +1,9 @@
1
1
  class Contact < ActiveRecord::Base
2
2
  has_many :addresses
3
3
  has_one :email_address
4
+
5
+ has_many_aggregate :addresses, :max_street_length, :maximum, "LENGTH(street)"
6
+ has_many_aggregate :addresses, :count, :count, "*"
4
7
  end
5
8
 
6
9
  class Address < ActiveRecord::Base
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jit_preloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle d'Oliveira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-08 00:00:00.000000000 Z
11
+ date: 2017-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
173
  version: '0'
174
174
  requirements: []
175
175
  rubyforge_project:
176
- rubygems_version: 2.4.3
176
+ rubygems_version: 2.4.8
177
177
  signing_key:
178
178
  specification_version: 4
179
179
  summary: Tool to understand N+1 queries and to remove them