bullet 4.9.0 → 4.10.0
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 +4 -4
- data/.rspec +1 -1
- data/.travis.yml +1 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile +1 -3
- data/Gemfile.mongoid +2 -3
- data/Gemfile.mongoid-2.4 +0 -3
- data/Gemfile.mongoid-2.5 +0 -3
- data/Gemfile.mongoid-2.6 +0 -3
- data/Gemfile.mongoid-2.7 +0 -3
- data/Gemfile.mongoid-2.8 +0 -3
- data/Gemfile.mongoid-3.0 +0 -3
- data/Gemfile.mongoid-3.1 +0 -3
- data/Gemfile.mongoid-4.0 +19 -0
- data/Gemfile.rails-3.0 +0 -2
- data/Gemfile.rails-3.1 +1 -3
- data/Gemfile.rails-3.2 +1 -3
- data/Gemfile.rails-4.0 +1 -3
- data/Gemfile.rails-4.1 +1 -3
- data/README.md +19 -2
- data/bullet.gemspec +4 -2
- data/lib/bullet.rb +39 -10
- data/lib/bullet/active_record3.rb +20 -2
- data/lib/bullet/active_record3x.rb +20 -2
- data/lib/bullet/active_record4.rb +20 -2
- data/lib/bullet/active_record41.rb +20 -2
- data/lib/bullet/detector/association.rb +14 -25
- data/lib/bullet/detector/base.rb +0 -6
- data/lib/bullet/detector/counter_cache.rb +10 -12
- data/lib/bullet/detector/n_plus_one_query.rb +12 -8
- data/lib/bullet/detector/unused_eager_loading.rb +5 -1
- data/lib/bullet/mongoid3x.rb +5 -4
- data/lib/bullet/mongoid4x.rb +5 -4
- data/lib/bullet/version.rb +1 -1
- data/spec/bullet/detector/association_spec.rb +0 -19
- data/spec/bullet/detector/base_spec.rb +0 -6
- data/spec/bullet/detector/counter_cache_spec.rb +0 -8
- data/spec/bullet/detector/n_plus_one_query_spec.rb +0 -6
- data/spec/bullet/detector/unused_eager_loading_spec.rb +0 -7
- data/spec/bullet/rack_spec.rb +1 -7
- data/spec/bullet/registry/object_spec.rb +0 -1
- data/spec/bullet_spec.rb +41 -0
- data/spec/integration/active_record3/association_spec.rb +18 -75
- data/spec/integration/active_record4/association_spec.rb +16 -75
- data/spec/integration/counter_cache_spec.rb +1 -1
- data/spec/integration/mongoid/association_spec.rb +0 -10
- data/spec/spec_helper.rb +17 -0
- data/spec/support/mongo_seed.rb +1 -1
- data/test.sh +1 -0
- metadata +7 -3
@@ -42,7 +42,6 @@ module Bullet
|
|
42
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
46
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
48
47
|
records
|
@@ -50,13 +49,32 @@ module Bullet
|
|
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
|
|
@@ -43,7 +43,6 @@ module Bullet
|
|
43
43
|
associations = (eager_load_values + includes_values).uniq
|
44
44
|
records.each do |record|
|
45
45
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
46
|
-
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
47
46
|
end
|
48
47
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
49
48
|
records
|
@@ -51,13 +50,32 @@ module Bullet
|
|
51
50
|
end
|
52
51
|
|
53
52
|
::ActiveRecord::Associations::JoinDependency.class_eval do
|
53
|
+
alias_method :origin_instantiate, :instantiate
|
54
54
|
alias_method :origin_construct_model, :construct_model
|
55
|
+
|
56
|
+
def instantiate(result_set, aliases)
|
57
|
+
@bullet_eager_loadings = {}
|
58
|
+
records = origin_instantiate(result_set, aliases)
|
59
|
+
|
60
|
+
@bullet_eager_loadings.each do |klazz, eager_loadings_hash|
|
61
|
+
objects = eager_loadings_hash.keys
|
62
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
63
|
+
end
|
64
|
+
records
|
65
|
+
end
|
66
|
+
|
55
67
|
# call join associations
|
56
68
|
def construct_model(record, node, row, model_cache, id, aliases)
|
69
|
+
result = origin_construct_model(record, node, row, model_cache, id, aliases)
|
70
|
+
|
57
71
|
associations = node.reflection.name
|
58
72
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
59
73
|
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
60
|
-
|
74
|
+
@bullet_eager_loadings[record.class] ||= {}
|
75
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
76
|
+
@bullet_eager_loadings[record.class][record] << associations
|
77
|
+
|
78
|
+
result
|
61
79
|
end
|
62
80
|
end
|
63
81
|
|
@@ -2,31 +2,20 @@ 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)
|
6
|
+
return unless Bullet.start?
|
23
7
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
24
|
-
|
8
|
+
|
9
|
+
Bullet.debug("Detector::Association#add_object_associations", "object: #{object.bullet_ar_key}, associations: #{associations}")
|
10
|
+
object_associations.add(object.bullet_ar_key, associations)
|
25
11
|
end
|
26
12
|
|
27
13
|
def add_call_object_associations(object, associations)
|
14
|
+
return unless Bullet.start?
|
28
15
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
29
|
-
|
16
|
+
|
17
|
+
Bullet.debug("Detector::Association#add_call_object_associations", "object: #{object.bullet_ar_key}, associations: #{associations}")
|
18
|
+
call_object_associations.add(object.bullet_ar_key, associations)
|
30
19
|
end
|
31
20
|
|
32
21
|
private
|
@@ -36,22 +25,22 @@ module Bullet
|
|
36
25
|
# the object_associations keep all associations that may be or may no be
|
37
26
|
# unpreload associations or unused preload associations.
|
38
27
|
def object_associations
|
39
|
-
|
28
|
+
Thread.current[:bullet_object_associations]
|
40
29
|
end
|
41
30
|
|
42
|
-
#
|
31
|
+
# call_object_associations keep the object relationships
|
43
32
|
# that object.associations is called.
|
44
33
|
# e.g. { "Post:1" => [:comments] }
|
45
34
|
# they are used to detect unused preload associations.
|
46
35
|
def call_object_associations
|
47
|
-
|
36
|
+
Thread.current[:bullet_call_object_associations]
|
48
37
|
end
|
49
38
|
|
50
39
|
# possible_objects keep the class to object relationships
|
51
40
|
# that the objects may cause N+1 query.
|
52
41
|
# e.g. { Post => ["Post:1", "Post:2"] }
|
53
42
|
def possible_objects
|
54
|
-
|
43
|
+
Thread.current[:bullet_possible_objects]
|
55
44
|
end
|
56
45
|
|
57
46
|
# impossible_objects keep the class to objects relationships
|
@@ -61,14 +50,14 @@ module Bullet
|
|
61
50
|
# if find collection returns only one object, then the object is impossible object,
|
62
51
|
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
|
63
52
|
def impossible_objects
|
64
|
-
|
53
|
+
Thread.current[:bullet_impossible_objects]
|
65
54
|
end
|
66
55
|
|
67
56
|
# eager_loadings keep the object relationships
|
68
57
|
# that the associations are preloaded by find :include.
|
69
58
|
# e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
|
70
59
|
def eager_loadings
|
71
|
-
|
60
|
+
Thread.current[:bullet_eager_loadings]
|
72
61
|
end
|
73
62
|
end
|
74
63
|
end
|
data/lib/bullet/detector/base.rb
CHANGED
@@ -2,32 +2,30 @@ module Bullet
|
|
2
2
|
module Detector
|
3
3
|
class CounterCache < Base
|
4
4
|
class <<self
|
5
|
-
def clear
|
6
|
-
@@possible_objects = nil
|
7
|
-
@@impossible_objects = nil
|
8
|
-
end
|
9
|
-
|
10
5
|
def add_counter_cache(object, associations)
|
6
|
+
return unless Bullet.start?
|
11
7
|
return unless Bullet.counter_cache_enable?
|
12
8
|
|
9
|
+
Bullet.debug("Detector::CounterCache#add_counter_cache", "object: #{object.bullet_ar_key}, associations: #{associations}")
|
13
10
|
if conditions_met?(object.bullet_ar_key, associations)
|
14
11
|
create_notification object.class.to_s, associations
|
15
12
|
end
|
16
13
|
end
|
17
14
|
|
18
15
|
def add_possible_objects(object_or_objects)
|
16
|
+
return unless Bullet.start?
|
19
17
|
return unless Bullet.counter_cache_enable?
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
possible_objects.add object_or_objects.bullet_ar_key
|
25
|
-
end
|
19
|
+
objects = Array(object_or_objects)
|
20
|
+
Bullet.debug("Detector::CounterCache#add_possible_objects", "objects: #{objects.map(&:bullet_ar_key).join(', ')}")
|
21
|
+
objects.each { |object| possible_objects.add object.bullet_ar_key }
|
26
22
|
end
|
27
23
|
|
28
24
|
def add_impossible_object(object)
|
25
|
+
return unless Bullet.start?
|
29
26
|
return unless Bullet.counter_cache_enable?
|
30
27
|
|
28
|
+
Bullet.debug("Detector::CounterCache#add_impossible_object", "object: #{object.bullet_ar_key}")
|
31
29
|
impossible_objects.add object.bullet_ar_key
|
32
30
|
end
|
33
31
|
|
@@ -42,11 +40,11 @@ module Bullet
|
|
42
40
|
end
|
43
41
|
|
44
42
|
def possible_objects
|
45
|
-
|
43
|
+
Thread.current[:bullet_counter_possible_objects]
|
46
44
|
end
|
47
45
|
|
48
46
|
def impossible_objects
|
49
|
-
|
47
|
+
Thread.current[:bullet_counter_impossible_objects]
|
50
48
|
end
|
51
49
|
|
52
50
|
def conditions_met?(bullet_ar_key, associations)
|
@@ -9,27 +9,31 @@ module Bullet
|
|
9
9
|
# then, it checks if this associations call is unpreload.
|
10
10
|
# if it is, keeps this unpreload associations and caller.
|
11
11
|
def call_association(object, associations)
|
12
|
-
|
12
|
+
return unless Bullet.start?
|
13
13
|
add_call_object_associations(object, associations)
|
14
14
|
|
15
|
+
Bullet.debug("Detector::NPlusOneQuery#call_association", "object: #{object.bullet_ar_key}, associations: #{associations}")
|
15
16
|
if conditions_met?(object.bullet_ar_key, associations)
|
17
|
+
Bullet.debug("detect n + 1 query", "object: #{object.bullet_ar_key}, associations: #{associations}")
|
16
18
|
create_notification caller_in_project, object.class.to_s, associations
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
22
|
def add_possible_objects(object_or_objects)
|
23
|
+
return unless Bullet.start?
|
21
24
|
return unless Bullet.n_plus_one_query_enable?
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
possible_objects.add object_or_objects.bullet_ar_key if object_or_objects.id
|
27
|
-
end
|
25
|
+
|
26
|
+
objects = Array(object_or_objects)
|
27
|
+
Bullet.debug("Detector::NPlusOneQuery#add_possible_objects", "objects: #{objects.map(&:bullet_ar_key).join(', ')}")
|
28
|
+
objects.each { |object| possible_objects.add object.bullet_ar_key }
|
28
29
|
end
|
29
30
|
|
30
31
|
def add_impossible_object(object)
|
32
|
+
return unless Bullet.start?
|
31
33
|
return unless Bullet.n_plus_one_query_enable?
|
32
|
-
|
34
|
+
|
35
|
+
Bullet.debug("Detector::NPlusOneQuery#add_impossible_object", "object: #{object.bullet_ar_key}")
|
36
|
+
impossible_objects.add object.bullet_ar_key
|
33
37
|
end
|
34
38
|
|
35
39
|
private
|
@@ -7,19 +7,23 @@ module Bullet
|
|
7
7
|
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
8
8
|
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
|
9
9
|
def check_unused_preload_associations
|
10
|
+
return unless Bullet.start?
|
10
11
|
return unless Bullet.unused_eager_loading_enable?
|
11
12
|
|
12
|
-
@@checked = true
|
13
13
|
object_associations.each do |bullet_ar_key, associations|
|
14
14
|
object_association_diff = diff_object_associations bullet_ar_key, associations
|
15
15
|
next if object_association_diff.empty?
|
16
16
|
|
17
|
+
Bullet.debug("detect unused preload", "object: #{bullet_ar_key}, associations: #{object_association_diff}")
|
17
18
|
create_notification bullet_ar_key.bullet_class_name, object_association_diff
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
22
|
def add_eager_loadings(objects, associations)
|
23
|
+
return unless Bullet.start?
|
22
24
|
return unless Bullet.unused_eager_loading_enable?
|
25
|
+
|
26
|
+
Bullet.debug("Detector::UnusedEagerLoading#add_eager_loadings", "objects: #{objects.map(&:bullet_ar_key).join(', ')}, associations: #{associations}")
|
23
27
|
bullet_ar_keys = objects.map(&:bullet_ar_key)
|
24
28
|
|
25
29
|
to_add = nil
|
data/lib/bullet/mongoid3x.rb
CHANGED
@@ -41,13 +41,14 @@ module Bullet
|
|
41
41
|
end
|
42
42
|
|
43
43
|
::Mongoid::Relations::Accessors.class_eval do
|
44
|
-
alias_method :
|
44
|
+
alias_method :origin_get_relation, :get_relation
|
45
45
|
|
46
|
-
def
|
47
|
-
|
46
|
+
def get_relation(name, metadata, reload = false)
|
47
|
+
result = origin_get_relation(name, metadata, reload)
|
48
|
+
if metadata.macro !~ /embed/
|
48
49
|
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
49
50
|
end
|
50
|
-
|
51
|
+
result
|
51
52
|
end
|
52
53
|
end
|
53
54
|
end
|
data/lib/bullet/mongoid4x.rb
CHANGED
@@ -41,13 +41,14 @@ module Bullet
|
|
41
41
|
end
|
42
42
|
|
43
43
|
::Mongoid::Relations::Accessors.class_eval do
|
44
|
-
alias_method :
|
44
|
+
alias_method :origin_get_relation, :get_relation
|
45
45
|
|
46
|
-
def
|
47
|
-
|
46
|
+
def get_relation(name, metadata, object, reload = false)
|
47
|
+
result = origin_get_relation(name, metadata, object, reload)
|
48
|
+
if metadata.macro !~ /embed/
|
48
49
|
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
49
50
|
end
|
50
|
-
|
51
|
+
result
|
51
52
|
end
|
52
53
|
end
|
53
54
|
end
|
data/lib/bullet/version.rb
CHANGED
@@ -7,25 +7,6 @@ module Bullet
|
|
7
7
|
@post1 = Post.first
|
8
8
|
@post2 = Post.last
|
9
9
|
end
|
10
|
-
before(:each) { Association.clear }
|
11
|
-
|
12
|
-
context ".start_request" do
|
13
|
-
it "should set @@checked to false" do
|
14
|
-
Association.start_request
|
15
|
-
expect(Association.class_variable_get(:@@checked)).to eq false
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
context ".clear" do
|
20
|
-
it "should clear all class variables" do
|
21
|
-
Association.clear
|
22
|
-
expect(Association.class_variable_get(:@@object_associations)).to be_nil
|
23
|
-
expect(Association.class_variable_get(:@@possible_objects)).to be_nil
|
24
|
-
expect(Association.class_variable_get(:@@impossible_objects)).to be_nil
|
25
|
-
expect(Association.class_variable_get(:@@call_object_associations)).to be_nil
|
26
|
-
expect(Association.class_variable_get(:@@eager_loadings)).to be_nil
|
27
|
-
end
|
28
|
-
end
|
29
10
|
|
30
11
|
context ".add_object_association" do
|
31
12
|
it "should add object, associations pair" do
|
@@ -7,14 +7,6 @@ module Bullet
|
|
7
7
|
@post1 = Post.first
|
8
8
|
@post2 = Post.last
|
9
9
|
end
|
10
|
-
before(:each) { CounterCache.clear }
|
11
|
-
|
12
|
-
context ".clear" do
|
13
|
-
it "should clear all class variables" do
|
14
|
-
expect(CounterCache.class_variable_get(:@@possible_objects)).to be_nil
|
15
|
-
expect(CounterCache.class_variable_get(:@@impossible_objects)).to be_nil
|
16
|
-
end
|
17
|
-
end
|
18
10
|
|
19
11
|
context ".add_counter_cache" do
|
20
12
|
it "should create notification if conditions met" do
|
@@ -7,14 +7,8 @@ module Bullet
|
|
7
7
|
@post = Post.first
|
8
8
|
@post2 = Post.last
|
9
9
|
end
|
10
|
-
before(:each) { NPlusOneQuery.clear }
|
11
10
|
|
12
11
|
context ".call_association" do
|
13
|
-
it "should set @@checked to true" do
|
14
|
-
NPlusOneQuery.call_association(@post, :associations)
|
15
|
-
expect(NPlusOneQuery.class_variable_get(:@@checked)).to eq true
|
16
|
-
end
|
17
|
-
|
18
12
|
it "should add call_object_associations" do
|
19
13
|
expect(NPlusOneQuery).to receive(:add_call_object_associations).with(@post, :associations)
|
20
14
|
NPlusOneQuery.call_association(@post, :associations)
|
@@ -7,7 +7,6 @@ module Bullet
|
|
7
7
|
@post = Post.first
|
8
8
|
@post2 = Post.last
|
9
9
|
end
|
10
|
-
before(:each) { UnusedEagerLoading.clear }
|
11
10
|
|
12
11
|
context ".call_associations" do
|
13
12
|
it "should get empty array if eager_loadings" do
|
@@ -39,11 +38,6 @@ module Bullet
|
|
39
38
|
end
|
40
39
|
|
41
40
|
context ".check_unused_preload_associations" do
|
42
|
-
it "should set @@checked to true" do
|
43
|
-
UnusedEagerLoading.check_unused_preload_associations
|
44
|
-
expect(UnusedEagerLoading.class_variable_get(:@@checked)).to eq true
|
45
|
-
end
|
46
|
-
|
47
41
|
it "should create notification if object_association_diff is not empty" do
|
48
42
|
UnusedEagerLoading.add_object_associations(@post, :association)
|
49
43
|
expect(UnusedEagerLoading).to receive(:create_notification).with("Post", [:association])
|
@@ -51,7 +45,6 @@ module Bullet
|
|
51
45
|
end
|
52
46
|
|
53
47
|
it "should not create notification if object_association_diff is empty" do
|
54
|
-
UnusedEagerLoading.clear
|
55
48
|
UnusedEagerLoading.add_object_associations(@post, :association)
|
56
49
|
UnusedEagerLoading.add_eager_loadings([@post], :association)
|
57
50
|
UnusedEagerLoading.add_call_object_associations(@post, :association)
|