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.
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)