bullet 4.9.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -1
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +7 -0
  5. data/Gemfile +1 -3
  6. data/Gemfile.mongoid +2 -3
  7. data/Gemfile.mongoid-2.4 +0 -3
  8. data/Gemfile.mongoid-2.5 +0 -3
  9. data/Gemfile.mongoid-2.6 +0 -3
  10. data/Gemfile.mongoid-2.7 +0 -3
  11. data/Gemfile.mongoid-2.8 +0 -3
  12. data/Gemfile.mongoid-3.0 +0 -3
  13. data/Gemfile.mongoid-3.1 +0 -3
  14. data/Gemfile.mongoid-4.0 +19 -0
  15. data/Gemfile.rails-3.0 +0 -2
  16. data/Gemfile.rails-3.1 +1 -3
  17. data/Gemfile.rails-3.2 +1 -3
  18. data/Gemfile.rails-4.0 +1 -3
  19. data/Gemfile.rails-4.1 +1 -3
  20. data/README.md +19 -2
  21. data/bullet.gemspec +4 -2
  22. data/lib/bullet.rb +39 -10
  23. data/lib/bullet/active_record3.rb +20 -2
  24. data/lib/bullet/active_record3x.rb +20 -2
  25. data/lib/bullet/active_record4.rb +20 -2
  26. data/lib/bullet/active_record41.rb +20 -2
  27. data/lib/bullet/detector/association.rb +14 -25
  28. data/lib/bullet/detector/base.rb +0 -6
  29. data/lib/bullet/detector/counter_cache.rb +10 -12
  30. data/lib/bullet/detector/n_plus_one_query.rb +12 -8
  31. data/lib/bullet/detector/unused_eager_loading.rb +5 -1
  32. data/lib/bullet/mongoid3x.rb +5 -4
  33. data/lib/bullet/mongoid4x.rb +5 -4
  34. data/lib/bullet/version.rb +1 -1
  35. data/spec/bullet/detector/association_spec.rb +0 -19
  36. data/spec/bullet/detector/base_spec.rb +0 -6
  37. data/spec/bullet/detector/counter_cache_spec.rb +0 -8
  38. data/spec/bullet/detector/n_plus_one_query_spec.rb +0 -6
  39. data/spec/bullet/detector/unused_eager_loading_spec.rb +0 -7
  40. data/spec/bullet/rack_spec.rb +1 -7
  41. data/spec/bullet/registry/object_spec.rb +0 -1
  42. data/spec/bullet_spec.rb +41 -0
  43. data/spec/integration/active_record3/association_spec.rb +18 -75
  44. data/spec/integration/active_record4/association_spec.rb +16 -75
  45. data/spec/integration/counter_cache_spec.rb +1 -1
  46. data/spec/integration/mongoid/association_spec.rb +0 -10
  47. data/spec/spec_helper.rb +17 -0
  48. data/spec/support/mongo_seed.rb +1 -1
  49. data/test.sh +1 -0
  50. 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
- origin_construct_association(record, join, row)
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
- origin_construct_model(record, node, row, model_cache, id, aliases)
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
- object_associations.add(object.bullet_ar_key, associations) if object.id
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
- call_object_associations.add(object.bullet_ar_key, associations) if object.id
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
- @@object_associations ||= Bullet::Registry::Base.new
28
+ Thread.current[:bullet_object_associations]
40
29
  end
41
30
 
42
- # call_object_assciations keep the object relationships
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
- @@call_object_associations ||= Bullet::Registry::Base.new
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
- @@possible_objects ||= Bullet::Registry::Object.new
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
- @@impossible_objects ||= Bullet::Registry::Object.new
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
- @@eager_loadings ||= Bullet::Registry::Association.new
60
+ Thread.current[:bullet_eager_loadings]
72
61
  end
73
62
  end
74
63
  end
@@ -1,12 +1,6 @@
1
1
  module Bullet
2
2
  module Detector
3
3
  class Base
4
- def self.start_request
5
- end
6
-
7
- def self.end_request
8
- clear
9
- end
10
4
  end
11
5
  end
12
6
  end
@@ -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
- if object_or_objects.is_a? Array
22
- object_or_objects.each { |object| possible_objects.add object.bullet_ar_key }
23
- else
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
- @@possible_objects ||= Bullet::Registry::Object.new
43
+ Thread.current[:bullet_counter_possible_objects]
46
44
  end
47
45
 
48
46
  def impossible_objects
49
- @@impossible_objects ||= Bullet::Registry::Object.new
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
- @@checked = true
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
- return if object_or_objects.blank?
23
- if object_or_objects.is_a? Array
24
- object_or_objects.each { |object| possible_objects.add object.bullet_ar_key }
25
- else
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
- impossible_objects.add object.bullet_ar_key if object.id
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
@@ -41,13 +41,14 @@ module Bullet
41
41
  end
42
42
 
43
43
  ::Mongoid::Relations::Accessors.class_eval do
44
- alias_method :origin_set_relation, :set_relation
44
+ alias_method :origin_get_relation, :get_relation
45
45
 
46
- def set_relation(name, relation)
47
- if relation && relation.metadata.macro !~ /embed/
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
- origin_set_relation(name, relation)
51
+ result
51
52
  end
52
53
  end
53
54
  end
@@ -41,13 +41,14 @@ module Bullet
41
41
  end
42
42
 
43
43
  ::Mongoid::Relations::Accessors.class_eval do
44
- alias_method :origin_set_relation, :set_relation
44
+ alias_method :origin_get_relation, :get_relation
45
45
 
46
- def set_relation(name, relation)
47
- if relation && relation.relation_metadata.macro !~ /embed/
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
- origin_set_relation(name, relation)
51
+ result
51
52
  end
52
53
  end
53
54
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Bullet
3
- VERSION = "4.9.0"
3
+ VERSION = "4.10.0"
4
4
  end
@@ -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
@@ -3,12 +3,6 @@ require 'spec_helper'
3
3
  module Bullet
4
4
  module Detector
5
5
  describe Base do
6
- context ".end_request" do
7
- it "should call clear" do
8
- expect(Base).to receive(:clear)
9
- Base.end_request
10
- end
11
- end
12
6
  end
13
7
  end
14
8
  end
@@ -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)