predictive_load 0.4.2 → 0.5.0

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
  SHA256:
3
- metadata.gz: 580ba2c90f13384b9fba7ea3c386fbc8c25f79a35dbed9fa1ea3a59aadc2e854
4
- data.tar.gz: 79a274f1f22adb3bec3a795c55dde8a01ed29056433e5316ad8d72dd605232f9
3
+ metadata.gz: 54bffe32fe50393ce8bb75bd01ae397084d3c2924ea4dafa9bccddb860baedf3
4
+ data.tar.gz: 1b37f4afd9874034fe0b7adc81317687d734cc51539665a5df005deefc0553c0
5
5
  SHA512:
6
- metadata.gz: ec156ee9fea24e8b223f63d31b9f2d000ebb65fcf63005998682097e4ebba3c1c8c19e2df9aadc19819c77c9a90d5a8d01993353215ed61f14aeddcc54a2a20e
7
- data.tar.gz: 4019f082b0a466304725cb37ad3e0753a19f2fd665bfe699a7221674c2f59be09cdb43db1b84331fe5386388081a529daba414d5d0e8d9b5918c702cba097d2b
6
+ metadata.gz: 8af3089f4ab81c3470d2811a335d40e36bede5ca38c59cf739135ebf4fdb18d7181338d9065a7fff06ff436a54c73e4ad618c61a890d8ee610c935855542e1da
7
+ data.tar.gz: de072a8eeaf0080dcf1bd2c1183b9b04f305c1206ed170737a6cbf97b62dee6945e876fb739b2495d715f9e1e373605383db35b13ed8ccb6b5a0f1bc71528e4c
data/README.md CHANGED
@@ -40,40 +40,6 @@ Some things cannot be preloaded, use `predictive_load: false`
40
40
  has_many :foos, predictive_load: false
41
41
  ```
42
42
 
43
- ### N+1 detection logging
44
-
45
- There is also a log-only version:
46
- ```ruby
47
- require 'predictive_load'
48
- require 'predictive_load/active_record_collection_observation'
49
- ActiveRecord::Base.send(:include, PredictiveLoad::ActiveRecordCollectionObservation)
50
-
51
- require 'predictive_load/watcher'
52
-
53
- ActiveRecord::Relation.collection_observer = PredictiveLoad::Watcher
54
-
55
- Comment.all.each do |comment|
56
- comment.account
57
- end
58
-
59
- ```
60
-
61
- Produces:
62
-
63
- ```
64
- detected n1 call on Comment#account
65
- expect to prevent 10 queries
66
- would preload with: SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` IN (...)
67
- +----+-------------+----------+-------+---------------+---------+---------+-------+------+-------+
68
- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
69
- +----+-------------+----------+-------+---------------+---------+---------+-------+------+-------+
70
- | 1 | SIMPLE | accounts | const | PRIMARY | PRIMARY | 4 | const | 10 | |
71
- +----+-------------+----------+-------+---------------+---------+---------+-------+------+-------+
72
- 1 row in set (0.00 sec)
73
- would have prevented all 10 queries
74
-
75
- ```
76
-
77
43
  #### Known limitations:
78
44
 
79
45
  * Calling association#size will trigger an N+1 on SELECT COUNT(*). Work around by calling #length, loading all records.
@@ -1,31 +1,40 @@
1
1
  module PredictiveLoad::ActiveRecordCollectionObservation
2
2
 
3
3
  def self.included(base)
4
- ActiveRecord::Relation.send(:include, RelationObservation)
5
- ActiveRecord::Base.send(:include, CollectionMember)
6
- ActiveRecord::Base.send(:extend, UnscopedTracker)
7
- ActiveRecord::Associations::Association.send(:include, AssociationNotification)
8
- ActiveRecord::Associations::CollectionAssociation.send(:include, CollectionAssociationNotification)
4
+ ActiveRecord::Relation.class_attribute :collection_observer
5
+ if ActiveRecord::VERSION::MAJOR >= 5
6
+ ActiveRecord::Relation.prepend Rails5RelationObservation
7
+ else
8
+ ActiveRecord::Relation.prepend Rails4RelationObservation
9
+ end
10
+ ActiveRecord::Base.include CollectionMember
11
+ ActiveRecord::Base.extend UnscopedTracker
12
+ ActiveRecord::Associations::Association.include AssociationNotification
13
+ ActiveRecord::Associations::CollectionAssociation.include CollectionAssociationNotification
9
14
  end
10
15
 
11
- module RelationObservation
12
-
13
- def self.included(base)
14
- base.class_attribute :collection_observer
15
- base.send(:alias_method, :to_a_without_collection_observer, :to_a)
16
- base.send(:alias_method, :to_a, :to_a_with_collection_observer)
16
+ module Rails5RelationObservation
17
+ # this essentially intercepts the enumerable methods that would result in n+1s since most of
18
+ # those are delegated to :records in Rails 5+ in the ActiveRecord::Relation::Delegation module
19
+ def records
20
+ record_array = super
21
+ if record_array.size > 1 && collection_observer
22
+ collection_observer.observe(record_array.dup)
23
+ end
24
+ record_array
17
25
  end
26
+ end
18
27
 
19
- def to_a_with_collection_observer
20
- records = to_a_without_collection_observer
21
-
22
- if records.size > 1 && collection_observer
23
- collection_observer.observe(records.dup)
28
+ module Rails4RelationObservation
29
+ # this essentially intercepts the enumerable methods that would result in n+1s since most of
30
+ # those are delegated to :to_a in Rails 5+ in the ActiveRecord::Relation::Delegation module
31
+ def to_a
32
+ record_array = super
33
+ if record_array.size > 1 && collection_observer
34
+ collection_observer.observe(record_array.dup)
24
35
  end
25
-
26
- records
36
+ record_array
27
37
  end
28
-
29
38
  end
30
39
 
31
40
  module CollectionMember
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: predictive_load
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Chapweske
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-16 00:00:00.000000000 Z
11
+ date: 2020-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,132 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.2.0
19
+ version: 4.2.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '5.1'
22
+ version: '5.3'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 3.2.0
29
+ version: 4.2.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '5.1'
33
- - !ruby/object:Gem::Dependency
34
- name: minitest
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
- type: :development
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
- - !ruby/object:Gem::Dependency
48
- name: minitest-rg
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
- - !ruby/object:Gem::Dependency
62
- name: sqlite3
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '0'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
- - !ruby/object:Gem::Dependency
76
- name: rake
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: '0'
89
- - !ruby/object:Gem::Dependency
90
- name: bump
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: '0'
96
- type: :development
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - ">="
101
- - !ruby/object:Gem::Version
102
- version: '0'
103
- - !ruby/object:Gem::Dependency
104
- name: wwtd
105
- requirement: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
- type: :development
111
- prerelease: false
112
- version_requirements: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- version: '0'
117
- - !ruby/object:Gem::Dependency
118
- name: query_diet
119
- requirement: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - ">="
122
- - !ruby/object:Gem::Version
123
- version: '0'
124
- type: :development
125
- prerelease: false
126
- version_requirements: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - ">="
129
- - !ruby/object:Gem::Version
130
- version: '0'
131
- - !ruby/object:Gem::Dependency
132
- name: byebug
133
- requirement: !ruby/object:Gem::Requirement
134
- requirements:
135
- - - ">="
136
- - !ruby/object:Gem::Version
137
- version: '0'
138
- type: :development
139
- prerelease: false
140
- version_requirements: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - ">="
143
- - !ruby/object:Gem::Version
144
- version: '0'
32
+ version: '5.3'
145
33
  description: Predictive loader
146
34
  email:
147
35
  - eac@zendesk.com
@@ -155,7 +43,6 @@ files:
155
43
  - lib/predictive_load/active_record_collection_observation.rb
156
44
  - lib/predictive_load/loader.rb
157
45
  - lib/predictive_load/preload_log.rb
158
- - lib/predictive_load/watcher.rb
159
46
  homepage: https://github.com/zendesk/predictive_load
160
47
  licenses:
161
48
  - Apache License Version 2.0
@@ -168,7 +55,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
55
  requirements:
169
56
  - - ">="
170
57
  - !ruby/object:Gem::Version
171
- version: '0'
58
+ version: '2.4'
172
59
  required_rubygems_version: !ruby/object:Gem::Requirement
173
60
  requirements:
174
61
  - - ">="
@@ -1,84 +0,0 @@
1
- raise "Not supported on rails 4.1+" if ActiveRecord::VERSION::STRING >= "4.1.0"
2
-
3
- require 'predictive_load/loader'
4
- require 'predictive_load/preload_log'
5
-
6
- module PredictiveLoad
7
- # Provides N+1 detection / log mode.
8
- #
9
- # Usage:
10
- # ActiveRecord::Relation.collection_observer = PredictiveLoad::Watcher
11
- #
12
- # Example output:
13
- # predictive_load: detected n1 call on Comment#account
14
- # predictive_load: expect to prevent 1 queries
15
- # predictive_load: would preload with: SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` IN (...)
16
- # predictive_load: +----+-------------+----------+-------+---------------+---------+---------+-------+------+-------+
17
- # predictive_load: | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
18
- # predictive_load: +----+-------------+----------+-------+---------------+---------+---------+-------+------+-------+
19
- # predictive_load: | 1 | SIMPLE | accounts | const | PRIMARY | PRIMARY | 4 | const | 1 | |
20
- # predictive_load: +----+-------------+----------+-------+---------------+---------+---------+-------+------+-------+
21
- # predictive_load: 1 row in set (0.00 sec)
22
- # predictive_load: would have prevented all 1 queries
23
- class Watcher < Loader
24
-
25
- attr_reader :loaded_associations
26
-
27
- def initialize(records)
28
- super
29
- @loaded_associations = {}
30
- end
31
-
32
- def loading_association(record, association)
33
- association_name = association.reflection.name
34
- return if !all_records_will_likely_load_association?(association_name)
35
- return if !supports_preload?(association)
36
-
37
- if loaded_associations.key?(association_name)
38
- log_query_plan(association_name)
39
- end
40
-
41
- increment_query_count(association_name)
42
- end
43
-
44
- protected
45
-
46
- def log_query_plan(association_name)
47
- log("detected n+1 call on #{records.first.class.name}##{association_name}")
48
-
49
- # Detailed logging for first query
50
- if query_count(association_name) == 1
51
- log("expect to prevent #{expected_query_count} queries")
52
- log_preload(association_name)
53
- end
54
-
55
- # All records loaded association
56
- if query_count(association_name) == expected_query_count
57
- log("would have prevented all #{expected_query_count} queries")
58
- end
59
-
60
- end
61
-
62
- def query_count(association_name)
63
- loaded_associations[association_name] || 0
64
- end
65
-
66
- def increment_query_count(association_name)
67
- loaded_associations[association_name] ||= 0
68
- loaded_associations[association_name] += 1
69
- end
70
-
71
- def expected_query_count
72
- records.size - 1
73
- end
74
-
75
- def log_preload(association_name)
76
- PreloadLog.new(records_with_association(association_name), [ association_name ]).run
77
- end
78
-
79
- def log(message)
80
- ActiveRecord::Base.logger.info("predictive_load: #{message}")
81
- end
82
-
83
- end
84
- end