jit_preloader 0.0.3 → 0.0.4

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.
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