bullet 4.9.0 → 4.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|