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.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -1
  4. data/.travis.yml +17 -6
  5. data/CHANGELOG.md +86 -0
  6. data/Gemfile +13 -12
  7. data/Gemfile.mongoid +14 -0
  8. data/Gemfile.mongoid-2.4 +19 -0
  9. data/Gemfile.mongoid-2.5 +19 -0
  10. data/Gemfile.mongoid-2.6 +19 -0
  11. data/Gemfile.mongoid-2.7 +19 -0
  12. data/Gemfile.mongoid-2.8 +19 -0
  13. data/Gemfile.mongoid-3.0 +19 -0
  14. data/Gemfile.mongoid-3.1 +19 -0
  15. data/Gemfile.mongoid-4.0 +19 -0
  16. data/Gemfile.rails-3.0 +19 -0
  17. data/Gemfile.rails-3.1 +19 -0
  18. data/Gemfile.rails-3.2 +19 -0
  19. data/Gemfile.rails-4.0 +19 -0
  20. data/Gemfile.rails-4.1 +19 -0
  21. data/{Hacking.textile → Hacking.md} +10 -5
  22. data/README.md +432 -0
  23. data/Rakefile +13 -6
  24. data/bullet.gemspec +6 -3
  25. data/lib/bullet/active_record3.rb +49 -11
  26. data/lib/bullet/active_record3x.rb +134 -0
  27. data/lib/bullet/{active_record31.rb → active_record4.rb} +45 -15
  28. data/lib/bullet/active_record41.rb +128 -0
  29. data/lib/bullet/dependency.rb +81 -0
  30. data/lib/bullet/detector/association.rb +24 -68
  31. data/lib/bullet/detector/base.rb +0 -6
  32. data/lib/bullet/detector/counter_cache.rb +59 -0
  33. data/lib/bullet/detector/n_plus_one_query.rb +56 -15
  34. data/lib/bullet/detector/unused_eager_loading.rb +84 -0
  35. data/lib/bullet/detector.rb +2 -2
  36. data/lib/bullet/ext/object.rb +10 -2
  37. data/lib/bullet/{mongoid.rb → mongoid2x.rb} +18 -18
  38. data/lib/bullet/mongoid3x.rb +56 -0
  39. data/lib/bullet/mongoid4x.rb +56 -0
  40. data/lib/bullet/notification/base.rb +22 -4
  41. data/lib/bullet/notification/n_plus_one_query.rb +1 -1
  42. data/lib/bullet/notification.rb +2 -0
  43. data/lib/bullet/rack.rb +51 -17
  44. data/lib/bullet/registry/base.rb +1 -1
  45. data/lib/bullet/registry/object.rb +4 -4
  46. data/lib/bullet/version.rb +1 -2
  47. data/lib/bullet.rb +119 -39
  48. data/perf/benchmark.rb +1 -4
  49. data/spec/bullet/detector/association_spec.rb +2 -70
  50. data/spec/bullet/detector/base_spec.rb +0 -6
  51. data/spec/bullet/detector/counter_cache_spec.rb +56 -0
  52. data/spec/bullet/detector/n_plus_one_query_spec.rb +78 -34
  53. data/spec/bullet/detector/unused_eager_loading_spec.rb +88 -0
  54. data/spec/bullet/ext/object_spec.rb +18 -7
  55. data/spec/bullet/ext/string_spec.rb +2 -2
  56. data/spec/bullet/notification/base_spec.rb +68 -15
  57. data/spec/bullet/notification/counter_cache_spec.rb +2 -2
  58. data/spec/bullet/notification/n_plus_one_query_spec.rb +4 -3
  59. data/spec/bullet/notification/unused_eager_loading_spec.rb +2 -2
  60. data/spec/bullet/notification_collector_spec.rb +4 -4
  61. data/spec/bullet/rack_spec.rb +40 -39
  62. data/spec/bullet/registry/association_spec.rb +3 -3
  63. data/spec/bullet/registry/base_spec.rb +6 -6
  64. data/spec/bullet/registry/object_spec.rb +4 -5
  65. data/spec/bullet_spec.rb +41 -0
  66. data/spec/integration/active_record3/association_spec.rb +662 -0
  67. data/spec/integration/active_record4/association_spec.rb +660 -0
  68. data/spec/integration/counter_cache_spec.rb +63 -0
  69. data/spec/integration/mongoid/association_spec.rb +190 -208
  70. data/spec/models/category.rb +1 -1
  71. data/spec/models/client.rb +1 -1
  72. data/spec/models/comment.rb +2 -2
  73. data/spec/models/document.rb +2 -2
  74. data/spec/models/firm.rb +1 -1
  75. data/spec/models/mongoid/address.rb +2 -0
  76. data/spec/models/mongoid/category.rb +2 -0
  77. data/spec/models/mongoid/comment.rb +2 -0
  78. data/spec/models/mongoid/company.rb +2 -0
  79. data/spec/models/mongoid/entry.rb +2 -0
  80. data/spec/models/mongoid/post.rb +4 -0
  81. data/spec/models/mongoid/user.rb +5 -0
  82. data/spec/models/newspaper.rb +1 -1
  83. data/spec/models/pet.rb +1 -1
  84. data/spec/models/post.rb +6 -13
  85. data/spec/spec_helper.rb +60 -25
  86. data/spec/support/bullet_ext.rb +1 -1
  87. data/spec/support/mongo_seed.rb +26 -2
  88. data/spec/support/sqlite_seed.rb +0 -6
  89. data/test.sh +15 -0
  90. metadata +77 -54
  91. data/.rvmrc +0 -2
  92. data/.rvmrc.example +0 -2
  93. data/Gemfile.lock +0 -132
  94. data/Gemfile.rails-2.3.14 +0 -16
  95. data/Gemfile.rails-2.3.14.lock +0 -65
  96. data/Gemfile.rails-3.0.12 +0 -17
  97. data/Gemfile.rails-3.0.12.lock +0 -116
  98. data/Gemfile.rails-3.1.4 +0 -18
  99. data/Gemfile.rails-3.1.4.lock +0 -134
  100. data/README.textile +0 -395
  101. data/README_for_rails2.textile +0 -400
  102. data/lib/bullet/action_controller2.rb +0 -49
  103. data/lib/bullet/active_record2.rb +0 -121
  104. data/lib/bullet/detector/counter.rb +0 -48
  105. data/lib/bullet/detector/unused_eager_association.rb +0 -42
  106. data/spec/bullet/detector/counter_spec.rb +0 -64
  107. data/spec/bullet/detector/unused_eager_association_spec.rb +0 -62
  108. data/spec/integration/association_spec.rb +0 -593
  109. data/spec/integration/counter_spec.rb +0 -39
  110. data/spec/integration/rails2/association_spec.rb +0 -593
  111. data/spec/integration/rails2/counter_spec.rb +0 -39
  112. 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::Association.add_possible_objects(records)
13
- Bullet::Detector::Counter.add_possible_objects(records)
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::Association.add_impossible_object(records.first)
16
- Bullet::Detector::Counter.add_impossible_object(records.first)
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, preload_options = {})
27
- origin_initialize(records, associations, preload_options)
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::Association.add_eager_loadings(records, associations)
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 = (@eager_load_values + @includes_values).uniq
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::Association.add_eager_loadings(records, associations)
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
- 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
 
@@ -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::Association.add_possible_objects(result)
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::Counter.add_counter_cache(owner, reflection.name) unless result
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
- object_associations.add(object.bullet_ar_key, associations) if object.id
24
- end
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
- def add_impossible_object(object)
40
- impossible_objects.add object.bullet_ar_key if object.id
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 add_eager_loadings(objects, associations)
44
- bullet_ar_keys = objects.map(&:bullet_ar_key)
45
-
46
- to_add = nil
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
- eager_loadings.add bullet_ar_keys, associations unless bullet_ar_keys.empty?
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
- @@object_associations ||= Bullet::Registry::Base.new
30
+ Thread.current[:bullet_object_associations]
81
31
  end
82
32
 
83
- # call_object_assciations keep the object relationships
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
- @@call_object_associations ||= Bullet::Registry::Base.new
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
- @@possible_objects ||= Bullet::Registry::Object.new
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
- @@impossible_objects ||= Bullet::Registry::Object.new
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
- @@eager_loadings ||= Bullet::Registry::Association.new
68
+ Thread.current[:bullet_eager_loadings]
113
69
  end
114
70
  end
115
71
  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
@@ -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