bullet 4.0.0 → 4.13.1
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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -1
- data/.travis.yml +17 -6
- data/CHANGELOG.md +86 -0
- data/Gemfile +13 -12
- data/Gemfile.mongoid +14 -0
- data/Gemfile.mongoid-2.4 +19 -0
- data/Gemfile.mongoid-2.5 +19 -0
- data/Gemfile.mongoid-2.6 +19 -0
- data/Gemfile.mongoid-2.7 +19 -0
- data/Gemfile.mongoid-2.8 +19 -0
- data/Gemfile.mongoid-3.0 +19 -0
- data/Gemfile.mongoid-3.1 +19 -0
- data/Gemfile.mongoid-4.0 +19 -0
- data/Gemfile.rails-3.0 +19 -0
- data/Gemfile.rails-3.1 +19 -0
- data/Gemfile.rails-3.2 +19 -0
- data/Gemfile.rails-4.0 +19 -0
- data/Gemfile.rails-4.1 +19 -0
- data/{Hacking.textile → Hacking.md} +10 -5
- data/README.md +432 -0
- data/Rakefile +13 -6
- data/bullet.gemspec +6 -3
- data/lib/bullet/active_record3.rb +49 -11
- data/lib/bullet/active_record3x.rb +134 -0
- data/lib/bullet/{active_record31.rb → active_record4.rb} +45 -15
- data/lib/bullet/active_record41.rb +128 -0
- data/lib/bullet/dependency.rb +81 -0
- data/lib/bullet/detector/association.rb +24 -68
- data/lib/bullet/detector/base.rb +0 -6
- data/lib/bullet/detector/counter_cache.rb +59 -0
- data/lib/bullet/detector/n_plus_one_query.rb +56 -15
- data/lib/bullet/detector/unused_eager_loading.rb +84 -0
- data/lib/bullet/detector.rb +2 -2
- data/lib/bullet/ext/object.rb +10 -2
- data/lib/bullet/{mongoid.rb → mongoid2x.rb} +18 -18
- data/lib/bullet/mongoid3x.rb +56 -0
- data/lib/bullet/mongoid4x.rb +56 -0
- data/lib/bullet/notification/base.rb +22 -4
- data/lib/bullet/notification/n_plus_one_query.rb +1 -1
- data/lib/bullet/notification.rb +2 -0
- data/lib/bullet/rack.rb +51 -17
- data/lib/bullet/registry/base.rb +1 -1
- data/lib/bullet/registry/object.rb +4 -4
- data/lib/bullet/version.rb +1 -2
- data/lib/bullet.rb +119 -39
- data/perf/benchmark.rb +1 -4
- data/spec/bullet/detector/association_spec.rb +2 -70
- data/spec/bullet/detector/base_spec.rb +0 -6
- data/spec/bullet/detector/counter_cache_spec.rb +56 -0
- data/spec/bullet/detector/n_plus_one_query_spec.rb +78 -34
- data/spec/bullet/detector/unused_eager_loading_spec.rb +88 -0
- data/spec/bullet/ext/object_spec.rb +18 -7
- data/spec/bullet/ext/string_spec.rb +2 -2
- data/spec/bullet/notification/base_spec.rb +68 -15
- data/spec/bullet/notification/counter_cache_spec.rb +2 -2
- data/spec/bullet/notification/n_plus_one_query_spec.rb +4 -3
- data/spec/bullet/notification/unused_eager_loading_spec.rb +2 -2
- data/spec/bullet/notification_collector_spec.rb +4 -4
- data/spec/bullet/rack_spec.rb +40 -39
- data/spec/bullet/registry/association_spec.rb +3 -3
- data/spec/bullet/registry/base_spec.rb +6 -6
- data/spec/bullet/registry/object_spec.rb +4 -5
- data/spec/bullet_spec.rb +41 -0
- data/spec/integration/active_record3/association_spec.rb +662 -0
- data/spec/integration/active_record4/association_spec.rb +660 -0
- data/spec/integration/counter_cache_spec.rb +63 -0
- data/spec/integration/mongoid/association_spec.rb +190 -208
- data/spec/models/category.rb +1 -1
- data/spec/models/client.rb +1 -1
- data/spec/models/comment.rb +2 -2
- data/spec/models/document.rb +2 -2
- data/spec/models/firm.rb +1 -1
- data/spec/models/mongoid/address.rb +2 -0
- data/spec/models/mongoid/category.rb +2 -0
- data/spec/models/mongoid/comment.rb +2 -0
- data/spec/models/mongoid/company.rb +2 -0
- data/spec/models/mongoid/entry.rb +2 -0
- data/spec/models/mongoid/post.rb +4 -0
- data/spec/models/mongoid/user.rb +5 -0
- data/spec/models/newspaper.rb +1 -1
- data/spec/models/pet.rb +1 -1
- data/spec/models/post.rb +6 -13
- data/spec/spec_helper.rb +60 -25
- data/spec/support/bullet_ext.rb +1 -1
- data/spec/support/mongo_seed.rb +26 -2
- data/spec/support/sqlite_seed.rb +0 -6
- data/test.sh +15 -0
- metadata +77 -54
- data/.rvmrc +0 -2
- data/.rvmrc.example +0 -2
- data/Gemfile.lock +0 -132
- data/Gemfile.rails-2.3.14 +0 -16
- data/Gemfile.rails-2.3.14.lock +0 -65
- data/Gemfile.rails-3.0.12 +0 -17
- data/Gemfile.rails-3.0.12.lock +0 -116
- data/Gemfile.rails-3.1.4 +0 -18
- data/Gemfile.rails-3.1.4.lock +0 -134
- data/README.textile +0 -395
- data/README_for_rails2.textile +0 -400
- data/lib/bullet/action_controller2.rb +0 -49
- data/lib/bullet/active_record2.rb +0 -121
- data/lib/bullet/detector/counter.rb +0 -48
- data/lib/bullet/detector/unused_eager_association.rb +0 -42
- data/spec/bullet/detector/counter_spec.rb +0 -64
- data/spec/bullet/detector/unused_eager_association_spec.rb +0 -62
- data/spec/integration/association_spec.rb +0 -593
- data/spec/integration/counter_spec.rb +0 -39
- data/spec/integration/rails2/association_spec.rb +0 -593
- data/spec/integration/rails2/counter_spec.rb +0 -39
- data/test.result +0 -2293
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
def self.enable
|
|
4
|
+
require 'active_record'
|
|
5
|
+
::ActiveRecord::Relation.class_eval do
|
|
6
|
+
alias_method :origin_to_a, :to_a
|
|
7
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
8
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
9
|
+
def to_a
|
|
10
|
+
records = origin_to_a
|
|
11
|
+
if records.size > 1
|
|
12
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
13
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
14
|
+
elsif records.size == 1
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
16
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
17
|
+
end
|
|
18
|
+
records
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
::ActiveRecord::Associations::Preloader.class_eval do
|
|
23
|
+
# include query for one to many associations.
|
|
24
|
+
# keep this eager loadings.
|
|
25
|
+
alias_method :origin_initialize, :initialize
|
|
26
|
+
def initialize(records, associations, preload_scope = nil)
|
|
27
|
+
origin_initialize(records, associations, preload_scope)
|
|
28
|
+
records = [records].flatten.compact.uniq
|
|
29
|
+
return if records.empty?
|
|
30
|
+
records.each do |record|
|
|
31
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
32
|
+
end
|
|
33
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
::ActiveRecord::FinderMethods.class_eval do
|
|
38
|
+
# add includes in scope
|
|
39
|
+
alias_method :origin_find_with_associations, :find_with_associations
|
|
40
|
+
def find_with_associations
|
|
41
|
+
records = origin_find_with_associations
|
|
42
|
+
associations = (eager_load_values + includes_values).uniq
|
|
43
|
+
records.each do |record|
|
|
44
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
45
|
+
end
|
|
46
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
47
|
+
records
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
::ActiveRecord::Associations::JoinDependency.class_eval do
|
|
52
|
+
alias_method :origin_instantiate, :instantiate
|
|
53
|
+
alias_method :origin_construct_association, :construct_association
|
|
54
|
+
|
|
55
|
+
def instantiate(rows)
|
|
56
|
+
@bullet_eager_loadings = {}
|
|
57
|
+
records = origin_instantiate(rows)
|
|
58
|
+
|
|
59
|
+
@bullet_eager_loadings.each do |klazz, eager_loadings_hash|
|
|
60
|
+
objects = eager_loadings_hash.keys
|
|
61
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
|
62
|
+
end
|
|
63
|
+
records
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# call join associations
|
|
67
|
+
def construct_association(record, join, row)
|
|
68
|
+
result = origin_construct_association(record, join, row)
|
|
69
|
+
|
|
70
|
+
associations = join.reflection.name
|
|
71
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
72
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
73
|
+
@bullet_eager_loadings[record.class] ||= {}
|
|
74
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
75
|
+
@bullet_eager_loadings[record.class][record] << associations
|
|
76
|
+
|
|
77
|
+
result
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
::ActiveRecord::Associations::CollectionAssociation.class_eval do
|
|
82
|
+
# call one to many associations
|
|
83
|
+
alias_method :origin_load_target, :load_target
|
|
84
|
+
def load_target
|
|
85
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
86
|
+
origin_load_target
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
alias_method :origin_empty?, :empty?
|
|
90
|
+
def empty?
|
|
91
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
92
|
+
origin_empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
alias_method :origin_include?, :include?
|
|
96
|
+
def include?(object)
|
|
97
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
98
|
+
origin_include?(object)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
::ActiveRecord::Associations::SingularAssociation.class_eval do
|
|
103
|
+
# call has_one and belongs_to associations
|
|
104
|
+
alias_method :origin_reader, :reader
|
|
105
|
+
def reader(force_reload = false)
|
|
106
|
+
result = origin_reader(force_reload)
|
|
107
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
108
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
109
|
+
result
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
::ActiveRecord::Associations::Association.class_eval do
|
|
114
|
+
alias_method :origin_set_inverse_instance, :set_inverse_instance
|
|
115
|
+
def set_inverse_instance(record)
|
|
116
|
+
if record && invertible_for?(record)
|
|
117
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(record, inverse_reflection_for(record).name)
|
|
118
|
+
end
|
|
119
|
+
origin_set_inverse_instance(record)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
|
124
|
+
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
125
|
+
|
|
126
|
+
def has_cached_counter?(reflection = reflection())
|
|
127
|
+
result = origin_has_cached_counter?(reflection)
|
|
128
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
|
|
129
|
+
result
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -9,11 +9,11 @@ module Bullet
|
|
|
9
9
|
def to_a
|
|
10
10
|
records = origin_to_a
|
|
11
11
|
if records.size > 1
|
|
12
|
-
Bullet::Detector::
|
|
13
|
-
Bullet::Detector::
|
|
12
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
13
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
14
14
|
elsif records.size == 1
|
|
15
|
-
Bullet::Detector::
|
|
16
|
-
Bullet::Detector::
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
16
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
17
17
|
end
|
|
18
18
|
records
|
|
19
19
|
end
|
|
@@ -23,14 +23,14 @@ module Bullet
|
|
|
23
23
|
# include query for one to many associations.
|
|
24
24
|
# keep this eager loadings.
|
|
25
25
|
alias_method :origin_initialize, :initialize
|
|
26
|
-
def initialize(records, associations,
|
|
27
|
-
origin_initialize(records, associations,
|
|
26
|
+
def initialize(records, associations, preload_scope = nil)
|
|
27
|
+
origin_initialize(records, associations, preload_scope)
|
|
28
28
|
records = [records].flatten.compact.uniq
|
|
29
29
|
return if records.empty?
|
|
30
30
|
records.each do |record|
|
|
31
31
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
32
32
|
end
|
|
33
|
-
Bullet::Detector::
|
|
33
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -39,24 +39,42 @@ module Bullet
|
|
|
39
39
|
alias_method :origin_find_with_associations, :find_with_associations
|
|
40
40
|
def find_with_associations
|
|
41
41
|
records = origin_find_with_associations
|
|
42
|
-
associations = (
|
|
42
|
+
associations = (eager_load_values + includes_values).uniq
|
|
43
43
|
records.each do |record|
|
|
44
44
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
45
|
-
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
46
45
|
end
|
|
47
|
-
Bullet::Detector::
|
|
46
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
48
47
|
records
|
|
49
48
|
end
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
::ActiveRecord::Associations::JoinDependency.class_eval do
|
|
52
|
+
alias_method :origin_instantiate, :instantiate
|
|
53
53
|
alias_method :origin_construct_association, :construct_association
|
|
54
|
+
|
|
55
|
+
def instantiate(rows)
|
|
56
|
+
@bullet_eager_loadings = {}
|
|
57
|
+
records = origin_instantiate(rows)
|
|
58
|
+
|
|
59
|
+
@bullet_eager_loadings.each do |klazz, eager_loadings_hash|
|
|
60
|
+
objects = eager_loadings_hash.keys
|
|
61
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
|
62
|
+
end
|
|
63
|
+
records
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
# call join associations
|
|
55
67
|
def construct_association(record, join, row)
|
|
68
|
+
result = origin_construct_association(record, join, row)
|
|
69
|
+
|
|
56
70
|
associations = join.reflection.name
|
|
57
71
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
58
72
|
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
59
|
-
|
|
73
|
+
@bullet_eager_loadings[record.class] ||= {}
|
|
74
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
75
|
+
@bullet_eager_loadings[record.class][record] << associations
|
|
76
|
+
|
|
77
|
+
result
|
|
60
78
|
end
|
|
61
79
|
end
|
|
62
80
|
|
|
@@ -67,6 +85,18 @@ module Bullet
|
|
|
67
85
|
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
68
86
|
origin_load_target
|
|
69
87
|
end
|
|
88
|
+
|
|
89
|
+
alias_method :origin_empty?, :empty?
|
|
90
|
+
def empty?
|
|
91
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
92
|
+
origin_empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
alias_method :origin_include?, :include?
|
|
96
|
+
def include?(object)
|
|
97
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
98
|
+
origin_include?(object)
|
|
99
|
+
end
|
|
70
100
|
end
|
|
71
101
|
|
|
72
102
|
::ActiveRecord::Associations::SingularAssociation.class_eval do
|
|
@@ -74,8 +104,8 @@ module Bullet
|
|
|
74
104
|
alias_method :origin_reader, :reader
|
|
75
105
|
def reader(force_reload = false)
|
|
76
106
|
result = origin_reader(force_reload)
|
|
77
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
78
|
-
Bullet::Detector::
|
|
107
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
|
|
108
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
79
109
|
result
|
|
80
110
|
end
|
|
81
111
|
end
|
|
@@ -83,9 +113,9 @@ module Bullet
|
|
|
83
113
|
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
|
84
114
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
85
115
|
|
|
86
|
-
def has_cached_counter?(reflection = reflection)
|
|
116
|
+
def has_cached_counter?(reflection = reflection())
|
|
87
117
|
result = origin_has_cached_counter?(reflection)
|
|
88
|
-
Bullet::Detector::
|
|
118
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
|
|
89
119
|
result
|
|
90
120
|
end
|
|
91
121
|
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
def self.enable
|
|
4
|
+
require 'active_record'
|
|
5
|
+
::ActiveRecord::Relation.class_eval do
|
|
6
|
+
alias_method :origin_to_a, :to_a
|
|
7
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
8
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
9
|
+
def to_a
|
|
10
|
+
records = origin_to_a
|
|
11
|
+
if records.first.class.name !~ /^HABTM_/
|
|
12
|
+
if records.size > 1
|
|
13
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
14
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
15
|
+
elsif records.size == 1
|
|
16
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
17
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
records
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
::ActiveRecord::Associations::Preloader.class_eval do
|
|
25
|
+
alias_method :origin_preloaders_on, :preloaders_on
|
|
26
|
+
|
|
27
|
+
def preloaders_on(association, records, scope)
|
|
28
|
+
records.compact!
|
|
29
|
+
if records.first.class.name !~ /^HABTM_/
|
|
30
|
+
records.each do |record|
|
|
31
|
+
Bullet::Detector::Association.add_object_associations(record, association)
|
|
32
|
+
end
|
|
33
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
|
34
|
+
end
|
|
35
|
+
origin_preloaders_on(association, records, scope)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
::ActiveRecord::FinderMethods.class_eval do
|
|
40
|
+
# add includes in scope
|
|
41
|
+
alias_method :origin_find_with_associations, :find_with_associations
|
|
42
|
+
def find_with_associations
|
|
43
|
+
records = origin_find_with_associations
|
|
44
|
+
associations = (eager_load_values + includes_values).uniq
|
|
45
|
+
records.each do |record|
|
|
46
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
47
|
+
end
|
|
48
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
49
|
+
records
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
::ActiveRecord::Associations::JoinDependency.class_eval do
|
|
54
|
+
alias_method :origin_instantiate, :instantiate
|
|
55
|
+
alias_method :origin_construct_model, :construct_model
|
|
56
|
+
|
|
57
|
+
def instantiate(result_set, aliases)
|
|
58
|
+
@bullet_eager_loadings = {}
|
|
59
|
+
records = origin_instantiate(result_set, aliases)
|
|
60
|
+
|
|
61
|
+
@bullet_eager_loadings.each do |klazz, eager_loadings_hash|
|
|
62
|
+
objects = eager_loadings_hash.keys
|
|
63
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
|
64
|
+
end
|
|
65
|
+
records
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# call join associations
|
|
69
|
+
def construct_model(record, node, row, model_cache, id, aliases)
|
|
70
|
+
result = origin_construct_model(record, node, row, model_cache, id, aliases)
|
|
71
|
+
|
|
72
|
+
associations = node.reflection.name
|
|
73
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
74
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
75
|
+
@bullet_eager_loadings[record.class] ||= {}
|
|
76
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
77
|
+
@bullet_eager_loadings[record.class][record] << associations
|
|
78
|
+
|
|
79
|
+
result
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
::ActiveRecord::Associations::CollectionAssociation.class_eval do
|
|
84
|
+
# call one to many associations
|
|
85
|
+
alias_method :origin_load_target, :load_target
|
|
86
|
+
def load_target
|
|
87
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
|
|
88
|
+
origin_load_target
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
alias_method :origin_empty?, :empty?
|
|
92
|
+
def empty?
|
|
93
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
94
|
+
origin_empty?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
alias_method :origin_include?, :include?
|
|
98
|
+
def include?(object)
|
|
99
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
100
|
+
origin_include?(object)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
::ActiveRecord::Associations::SingularAssociation.class_eval do
|
|
105
|
+
# call has_one and belongs_to associations
|
|
106
|
+
alias_method :origin_reader, :reader
|
|
107
|
+
def reader(force_reload = false)
|
|
108
|
+
result = origin_reader(force_reload)
|
|
109
|
+
if @owner.class.name !~ /^HABTM_/
|
|
110
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
|
|
111
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
112
|
+
end
|
|
113
|
+
result
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
|
118
|
+
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
119
|
+
|
|
120
|
+
def has_cached_counter?(reflection = reflection())
|
|
121
|
+
result = origin_has_cached_counter?(reflection)
|
|
122
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
|
|
123
|
+
result
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Dependency
|
|
3
|
+
def mongoid?
|
|
4
|
+
@mongoid ||= defined? ::Mongoid
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def active_record?
|
|
8
|
+
@active_record ||= defined? ::ActiveRecord
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def rails?
|
|
12
|
+
@rails ||= defined? ::Rails
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def active_record_version
|
|
16
|
+
@active_record_version ||= begin
|
|
17
|
+
if active_record30?
|
|
18
|
+
'active_record3'
|
|
19
|
+
elsif active_record31? || active_record32?
|
|
20
|
+
'active_record3x'
|
|
21
|
+
elsif active_record40?
|
|
22
|
+
'active_record4'
|
|
23
|
+
elsif active_record41?
|
|
24
|
+
'active_record41'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def mongoid_version
|
|
30
|
+
@mongoid_version ||= begin
|
|
31
|
+
if mongoid2x?
|
|
32
|
+
'mongoid2x'
|
|
33
|
+
elsif mongoid3x?
|
|
34
|
+
'mongoid3x'
|
|
35
|
+
elsif mongoid4x?
|
|
36
|
+
'mongoid4x'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def active_record3?
|
|
42
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR == 3
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def active_record4?
|
|
46
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR == 4
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def active_record30?
|
|
50
|
+
active_record3? && ::ActiveRecord::VERSION::MINOR == 0
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def active_record31?
|
|
54
|
+
active_record3? && ::ActiveRecord::VERSION::MINOR == 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def active_record32?
|
|
58
|
+
active_record3? && ::ActiveRecord::VERSION::MINOR == 2
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def active_record40?
|
|
62
|
+
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def active_record41?
|
|
66
|
+
active_record4? && ::ActiveRecord::VERSION::MINOR == 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def mongoid2x?
|
|
70
|
+
mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-8]/
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def mongoid3x?
|
|
74
|
+
mongoid? && ::Mongoid::VERSION =~ /\A3/
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def mongoid4x?
|
|
78
|
+
mongoid? && ::Mongoid::VERSION =~ /\A4/
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -2,72 +2,22 @@ module Bullet
|
|
|
2
2
|
module Detector
|
|
3
3
|
class Association < Base
|
|
4
4
|
class <<self
|
|
5
|
-
def start_request
|
|
6
|
-
@@checked = false
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def clear
|
|
10
|
-
# Note that under ruby class variables are shared among the class
|
|
11
|
-
# that declares them and all classes derived from that class.
|
|
12
|
-
# The following variables are accessible by all classes that
|
|
13
|
-
# derive from Bullet::Detector::Association - changing the variable
|
|
14
|
-
# in one subclass will make the change visible to all subclasses!
|
|
15
|
-
@@object_associations = nil
|
|
16
|
-
@@possible_objects = nil
|
|
17
|
-
@@impossible_objects = nil
|
|
18
|
-
@@call_object_associations = nil
|
|
19
|
-
@@eager_loadings = nil
|
|
20
|
-
end
|
|
21
|
-
|
|
22
5
|
def add_object_associations(object, associations)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def add_call_object_associations(object, associations)
|
|
27
|
-
call_object_associations.add(object.bullet_ar_key, associations) if object.id
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def add_possible_objects(object_or_objects)
|
|
31
|
-
return unless object_or_objects
|
|
32
|
-
if object_or_objects.is_a? Array
|
|
33
|
-
object_or_objects.each { |object| possible_objects.add object.bullet_ar_key }
|
|
34
|
-
else
|
|
35
|
-
possible_objects.add object_or_objects.bullet_ar_key if object_or_objects.id
|
|
36
|
-
end
|
|
37
|
-
end
|
|
6
|
+
return unless Bullet.start?
|
|
7
|
+
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
8
|
+
return unless object.primary_key_value
|
|
38
9
|
|
|
39
|
-
|
|
40
|
-
|
|
10
|
+
Bullet.debug("Detector::Association#add_object_associations", "object: #{object.bullet_key}, associations: #{associations}")
|
|
11
|
+
object_associations.add(object.bullet_key, associations)
|
|
41
12
|
end
|
|
42
13
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
to_merge, to_delete = [], []
|
|
48
|
-
eager_loadings.each do |k, v|
|
|
49
|
-
key_objects_overlap = k & bullet_ar_keys
|
|
50
|
-
|
|
51
|
-
next if key_objects_overlap.empty?
|
|
52
|
-
|
|
53
|
-
if key_objects_overlap == k
|
|
54
|
-
to_add = [k, associations]
|
|
55
|
-
break
|
|
56
|
-
else
|
|
57
|
-
to_merge << [key_objects_overlap, ( eager_loadings[k].dup << associations )]
|
|
58
|
-
|
|
59
|
-
keys_without_objects = k - bullet_ar_keys
|
|
60
|
-
to_merge << [keys_without_objects, eager_loadings[k]]
|
|
61
|
-
to_delete << k
|
|
62
|
-
bullet_ar_keys = bullet_ar_keys - k
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
eager_loadings.add *to_add if to_add
|
|
67
|
-
to_merge.each { |k,val| eager_loadings.merge k, val }
|
|
68
|
-
to_delete.each { |k| eager_loadings.delete k }
|
|
14
|
+
def add_call_object_associations(object, associations)
|
|
15
|
+
return unless Bullet.start?
|
|
16
|
+
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
17
|
+
return unless object.primary_key_value
|
|
69
18
|
|
|
70
|
-
|
|
19
|
+
Bullet.debug("Detector::Association#add_call_object_associations", "object: #{object.bullet_key}, associations: #{associations}")
|
|
20
|
+
call_object_associations.add(object.bullet_key, associations)
|
|
71
21
|
end
|
|
72
22
|
|
|
73
23
|
private
|
|
@@ -77,39 +27,45 @@ module Bullet
|
|
|
77
27
|
# the object_associations keep all associations that may be or may no be
|
|
78
28
|
# unpreload associations or unused preload associations.
|
|
79
29
|
def object_associations
|
|
80
|
-
|
|
30
|
+
Thread.current[:bullet_object_associations]
|
|
81
31
|
end
|
|
82
32
|
|
|
83
|
-
#
|
|
33
|
+
# call_object_associations keep the object relationships
|
|
84
34
|
# that object.associations is called.
|
|
85
35
|
# e.g. { "Post:1" => [:comments] }
|
|
86
36
|
# they are used to detect unused preload associations.
|
|
87
37
|
def call_object_associations
|
|
88
|
-
|
|
38
|
+
Thread.current[:bullet_call_object_associations]
|
|
89
39
|
end
|
|
90
40
|
|
|
91
41
|
# possible_objects keep the class to object relationships
|
|
92
42
|
# that the objects may cause N+1 query.
|
|
93
43
|
# e.g. { Post => ["Post:1", "Post:2"] }
|
|
94
44
|
def possible_objects
|
|
95
|
-
|
|
45
|
+
Thread.current[:bullet_possible_objects]
|
|
96
46
|
end
|
|
97
47
|
|
|
98
48
|
# impossible_objects keep the class to objects relationships
|
|
99
49
|
# that the objects may not cause N+1 query.
|
|
100
50
|
# e.g. { Post => ["Post:1", "Post:2"] }
|
|
101
|
-
# Notice: impossible_objects are not accurate,
|
|
102
51
|
# if find collection returns only one object, then the object is impossible object,
|
|
103
52
|
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
|
|
104
53
|
def impossible_objects
|
|
105
|
-
|
|
54
|
+
Thread.current[:bullet_impossible_objects]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# inversed_objects keeps object relationships
|
|
58
|
+
# that association is inversed.
|
|
59
|
+
# e.g. { "Comment:1" => ["post"] }
|
|
60
|
+
def inversed_objects
|
|
61
|
+
Thread.current[:bullet_inversed_objects]
|
|
106
62
|
end
|
|
107
63
|
|
|
108
64
|
# eager_loadings keep the object relationships
|
|
109
65
|
# that the associations are preloaded by find :include.
|
|
110
66
|
# e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
|
|
111
67
|
def eager_loadings
|
|
112
|
-
|
|
68
|
+
Thread.current[:bullet_eager_loadings]
|
|
113
69
|
end
|
|
114
70
|
end
|
|
115
71
|
end
|
data/lib/bullet/detector/base.rb
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Detector
|
|
3
|
+
class CounterCache < Base
|
|
4
|
+
class <<self
|
|
5
|
+
def add_counter_cache(object, associations)
|
|
6
|
+
return unless Bullet.start?
|
|
7
|
+
return unless Bullet.counter_cache_enable?
|
|
8
|
+
return unless object.primary_key_value
|
|
9
|
+
|
|
10
|
+
Bullet.debug("Detector::CounterCache#add_counter_cache", "object: #{object.bullet_key}, associations: #{associations}")
|
|
11
|
+
if conditions_met?(object.bullet_key, associations)
|
|
12
|
+
create_notification object.class.to_s, associations
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_possible_objects(object_or_objects)
|
|
17
|
+
return unless Bullet.start?
|
|
18
|
+
return unless Bullet.counter_cache_enable?
|
|
19
|
+
objects = Array(object_or_objects)
|
|
20
|
+
return if objects.map(&:primary_key_value).compact.empty?
|
|
21
|
+
|
|
22
|
+
Bullet.debug("Detector::CounterCache#add_possible_objects", "objects: #{objects.map(&:bullet_key).join(', ')}")
|
|
23
|
+
objects.each { |object| possible_objects.add object.bullet_key }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_impossible_object(object)
|
|
27
|
+
return unless Bullet.start?
|
|
28
|
+
return unless Bullet.counter_cache_enable?
|
|
29
|
+
return unless object.primary_key_value
|
|
30
|
+
|
|
31
|
+
Bullet.debug("Detector::CounterCache#add_impossible_object", "object: #{object.bullet_key}")
|
|
32
|
+
impossible_objects.add object.bullet_key
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
def create_notification(klazz, associations)
|
|
37
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
|
|
38
|
+
|
|
39
|
+
if notify_associations.present?
|
|
40
|
+
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
|
|
41
|
+
Bullet.notification_collector.add notice
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def possible_objects
|
|
46
|
+
Thread.current[:bullet_counter_possible_objects]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def impossible_objects
|
|
50
|
+
Thread.current[:bullet_counter_impossible_objects]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def conditions_met?(bullet_key, associations)
|
|
54
|
+
possible_objects.include?(bullet_key) && !impossible_objects.include?(bullet_key)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|