bullet_instructure 4.0.2

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 (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +20 -0
  6. data/CHANGELOG.md +75 -0
  7. data/Gemfile +19 -0
  8. data/Gemfile.mongoid +14 -0
  9. data/Gemfile.mongoid-2.4 +19 -0
  10. data/Gemfile.mongoid-2.5 +19 -0
  11. data/Gemfile.mongoid-2.6 +19 -0
  12. data/Gemfile.mongoid-2.7 +19 -0
  13. data/Gemfile.mongoid-2.8 +19 -0
  14. data/Gemfile.mongoid-3.0 +19 -0
  15. data/Gemfile.mongoid-3.1 +19 -0
  16. data/Gemfile.mongoid-4.0 +19 -0
  17. data/Gemfile.rails-3.0 +19 -0
  18. data/Gemfile.rails-3.1 +19 -0
  19. data/Gemfile.rails-3.2 +19 -0
  20. data/Gemfile.rails-4.0 +19 -0
  21. data/Gemfile.rails-4.1 +19 -0
  22. data/Guardfile +8 -0
  23. data/Hacking.md +74 -0
  24. data/MIT-LICENSE +20 -0
  25. data/README.md +428 -0
  26. data/Rakefile +52 -0
  27. data/bullet_instructure.gemspec +27 -0
  28. data/lib/bullet.rb +196 -0
  29. data/lib/bullet/active_record3.rb +148 -0
  30. data/lib/bullet/active_record3x.rb +128 -0
  31. data/lib/bullet/active_record4.rb +128 -0
  32. data/lib/bullet/active_record41.rb +121 -0
  33. data/lib/bullet/dependency.rb +81 -0
  34. data/lib/bullet/detector.rb +9 -0
  35. data/lib/bullet/detector/association.rb +67 -0
  36. data/lib/bullet/detector/base.rb +6 -0
  37. data/lib/bullet/detector/counter_cache.rb +59 -0
  38. data/lib/bullet/detector/n_plus_one_query.rb +89 -0
  39. data/lib/bullet/detector/unused_eager_loading.rb +84 -0
  40. data/lib/bullet/ext/object.rb +9 -0
  41. data/lib/bullet/ext/string.rb +5 -0
  42. data/lib/bullet/mongoid2x.rb +56 -0
  43. data/lib/bullet/mongoid3x.rb +56 -0
  44. data/lib/bullet/mongoid4x.rb +56 -0
  45. data/lib/bullet/notification.rb +10 -0
  46. data/lib/bullet/notification/base.rb +97 -0
  47. data/lib/bullet/notification/counter_cache.rb +13 -0
  48. data/lib/bullet/notification/n_plus_one_query.rb +28 -0
  49. data/lib/bullet/notification/unused_eager_loading.rb +13 -0
  50. data/lib/bullet/notification_collector.rb +24 -0
  51. data/lib/bullet/rack.rb +81 -0
  52. data/lib/bullet/registry.rb +7 -0
  53. data/lib/bullet/registry/association.rb +13 -0
  54. data/lib/bullet/registry/base.rb +40 -0
  55. data/lib/bullet/registry/object.rb +13 -0
  56. data/lib/bullet/version.rb +4 -0
  57. data/perf/benchmark.rb +121 -0
  58. data/rails/init.rb +1 -0
  59. data/spec/bullet/detector/association_spec.rb +26 -0
  60. data/spec/bullet/detector/base_spec.rb +8 -0
  61. data/spec/bullet/detector/counter_cache_spec.rb +56 -0
  62. data/spec/bullet/detector/n_plus_one_query_spec.rb +138 -0
  63. data/spec/bullet/detector/unused_eager_loading_spec.rb +88 -0
  64. data/spec/bullet/ext/object_spec.rb +17 -0
  65. data/spec/bullet/ext/string_spec.rb +13 -0
  66. data/spec/bullet/notification/base_spec.rb +83 -0
  67. data/spec/bullet/notification/counter_cache_spec.rb +12 -0
  68. data/spec/bullet/notification/n_plus_one_query_spec.rb +14 -0
  69. data/spec/bullet/notification/unused_eager_loading_spec.rb +12 -0
  70. data/spec/bullet/notification_collector_spec.rb +32 -0
  71. data/spec/bullet/rack_spec.rb +97 -0
  72. data/spec/bullet/registry/association_spec.rb +26 -0
  73. data/spec/bullet/registry/base_spec.rb +44 -0
  74. data/spec/bullet/registry/object_spec.rb +24 -0
  75. data/spec/bullet_spec.rb +41 -0
  76. data/spec/integration/active_record3/association_spec.rb +651 -0
  77. data/spec/integration/active_record4/association_spec.rb +649 -0
  78. data/spec/integration/counter_cache_spec.rb +63 -0
  79. data/spec/integration/mongoid/association_spec.rb +258 -0
  80. data/spec/models/address.rb +3 -0
  81. data/spec/models/author.rb +3 -0
  82. data/spec/models/base_user.rb +5 -0
  83. data/spec/models/category.rb +7 -0
  84. data/spec/models/city.rb +3 -0
  85. data/spec/models/client.rb +4 -0
  86. data/spec/models/comment.rb +4 -0
  87. data/spec/models/company.rb +3 -0
  88. data/spec/models/country.rb +3 -0
  89. data/spec/models/document.rb +5 -0
  90. data/spec/models/entry.rb +3 -0
  91. data/spec/models/firm.rb +4 -0
  92. data/spec/models/folder.rb +2 -0
  93. data/spec/models/mongoid/address.rb +7 -0
  94. data/spec/models/mongoid/category.rb +8 -0
  95. data/spec/models/mongoid/comment.rb +7 -0
  96. data/spec/models/mongoid/company.rb +7 -0
  97. data/spec/models/mongoid/entry.rb +7 -0
  98. data/spec/models/mongoid/post.rb +12 -0
  99. data/spec/models/mongoid/user.rb +5 -0
  100. data/spec/models/newspaper.rb +3 -0
  101. data/spec/models/page.rb +2 -0
  102. data/spec/models/person.rb +3 -0
  103. data/spec/models/pet.rb +3 -0
  104. data/spec/models/post.rb +10 -0
  105. data/spec/models/relationship.rb +4 -0
  106. data/spec/models/student.rb +3 -0
  107. data/spec/models/submission.rb +4 -0
  108. data/spec/models/teacher.rb +3 -0
  109. data/spec/models/user.rb +4 -0
  110. data/spec/models/writer.rb +2 -0
  111. data/spec/spec_helper.rb +103 -0
  112. data/spec/support/bullet_ext.rb +55 -0
  113. data/spec/support/mongo_seed.rb +65 -0
  114. data/spec/support/rack_double.rb +55 -0
  115. data/spec/support/sqlite_seed.rb +229 -0
  116. data/tasks/bullet_tasks.rake +9 -0
  117. data/test.sh +15 -0
  118. metadata +246 -0
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require "bullet/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "bullet_instructure"
8
+ s.version = Bullet::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Richard Huang"]
11
+ s.email = ["flyerhzm@gmail.com"]
12
+ s.homepage = "http://github.com/flyerhzm/bullet"
13
+ s.summary = "help to kill N+1 queries and unused eager loading, pretty formatter for Instructure."
14
+ s.description = "help to kill N+1 queries and unused eager loading, pretty formatter for Instructure."
15
+
16
+ s.license = 'MIT'
17
+
18
+ s.required_rubygems_version = ">= 1.3.6"
19
+
20
+ s.add_runtime_dependency "activesupport", ">= 3.0.0"
21
+ s.add_runtime_dependency "uniform_notifier", ">= 1.6.0"
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.require_paths = ["lib"]
26
+ end
27
+
@@ -0,0 +1,196 @@
1
+ require "active_support/core_ext/module/delegation"
2
+ require 'set'
3
+ require 'uniform_notifier'
4
+ require 'bullet/ext/object'
5
+ require 'bullet/ext/string'
6
+ require 'bullet/dependency'
7
+
8
+ module Bullet
9
+ extend Dependency
10
+
11
+ autoload :ActiveRecord, "bullet/#{active_record_version}" if active_record?
12
+ autoload :Mongoid, "bullet/#{mongoid_version}" if mongoid?
13
+ autoload :Rack, 'bullet/rack'
14
+ autoload :Notification, 'bullet/notification'
15
+ autoload :Detector, 'bullet/detector'
16
+ autoload :Registry, 'bullet/registry'
17
+ autoload :NotificationCollector, 'bullet/notification_collector'
18
+
19
+ if defined? Rails::Railtie
20
+ class BulletRailtie < Rails::Railtie
21
+ initializer "bullet.configure_rails_initialization" do |app|
22
+ app.middleware.use Bullet::Rack
23
+ end
24
+ end
25
+ end
26
+
27
+ class << self
28
+ attr_writer :enable, :n_plus_one_query_enable, :unused_eager_loading_enable, :counter_cache_enable, :stacktrace_includes
29
+ attr_reader :notification_collector, :whitelist
30
+ attr_accessor :add_footer, :orm_pathches_applied
31
+
32
+ delegate :alert=, :console=, :growl=, :rails_logger=, :xmpp=, :airbrake=, :bugsnag=, :to => UniformNotifier
33
+
34
+ def raise=(should_raise)
35
+ UniformNotifier.raise=(should_raise ? Notification::UnoptimizedQueryError : false)
36
+ end
37
+
38
+ DETECTORS = [ Bullet::Detector::NPlusOneQuery,
39
+ Bullet::Detector::UnusedEagerLoading,
40
+ Bullet::Detector::CounterCache ]
41
+
42
+ def enable=(enable)
43
+ @enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
44
+ if enable?
45
+ reset_whitelist
46
+ unless orm_pathches_applied
47
+ self.orm_pathches_applied = true
48
+ Bullet::Mongoid.enable if mongoid?
49
+ Bullet::ActiveRecord.enable if active_record?
50
+ end
51
+ end
52
+ end
53
+
54
+ def enable?
55
+ !!@enable
56
+ end
57
+
58
+ def n_plus_one_query_enable?
59
+ self.enable? && !!@n_plus_one_query_enable
60
+ end
61
+
62
+ def unused_eager_loading_enable?
63
+ self.enable? && !!@unused_eager_loading_enable
64
+ end
65
+
66
+ def counter_cache_enable?
67
+ self.enable? && !!@counter_cache_enable
68
+ end
69
+
70
+ def stacktrace_includes
71
+ @stacktrace_includes || []
72
+ end
73
+
74
+ def add_whitelist(options)
75
+ @whitelist[options[:type]][options[:class_name].classify] ||= []
76
+ @whitelist[options[:type]][options[:class_name].classify] << options[:association].to_sym
77
+ end
78
+
79
+ def get_whitelist_associations(type, class_name)
80
+ Array(@whitelist[type][class_name])
81
+ end
82
+
83
+ def reset_whitelist
84
+ @whitelist = {:n_plus_one_query => {}, :unused_eager_loading => {}, :counter_cache => {}}
85
+ end
86
+
87
+ def bullet_logger=(active)
88
+ if active
89
+ require 'fileutils'
90
+ root_path = "#{rails? ? Rails.root.to_s : Dir.pwd}"
91
+ FileUtils.mkdir_p(root_path + '/log')
92
+ bullet_log_file = File.open("#{root_path}/log/bullet.html", 'a+')
93
+ bullet_log_file.sync = true
94
+ UniformNotifier.customized_logger = bullet_log_file
95
+ end
96
+ end
97
+
98
+ def debug(title, message)
99
+ puts "[Bullet][#{title}] #{message}" if ENV['DEBUG'] == 'true'
100
+ end
101
+
102
+ def start_request
103
+ Thread.current[:bullet_start] = true
104
+ Thread.current[:bullet_notification_collector] = Bullet::NotificationCollector.new
105
+
106
+ Thread.current[:bullet_object_associations] = Bullet::Registry::Base.new
107
+ Thread.current[:bullet_call_object_associations] = Bullet::Registry::Base.new
108
+ Thread.current[:bullet_possible_objects] = Bullet::Registry::Object.new
109
+ Thread.current[:bullet_impossible_objects] = Bullet::Registry::Object.new
110
+ Thread.current[:bullet_eager_loadings] = Bullet::Registry::Association.new
111
+
112
+ Thread.current[:bullet_counter_possible_objects] ||= Bullet::Registry::Object.new
113
+ Thread.current[:bullet_counter_impossible_objects] ||= Bullet::Registry::Object.new
114
+ end
115
+
116
+ def end_request
117
+ Thread.current[:bullet_start] = nil
118
+ Thread.current[:bullet_notification_collector] = nil
119
+
120
+ Thread.current[:bullet_object_associations] = nil
121
+ Thread.current[:bullet_possible_objects] = nil
122
+ Thread.current[:bullet_impossible_objects] = nil
123
+ Thread.current[:bullet_call_object_associations] = nil
124
+ Thread.current[:bullet_eager_loadings] = nil
125
+
126
+ Thread.current[:bullet_counter_possible_objects] = nil
127
+ Thread.current[:bullet_counter_impossible_objects] = nil
128
+ end
129
+
130
+ def start?
131
+ Thread.current[:bullet_start]
132
+ end
133
+
134
+ def notification_collector
135
+ Thread.current[:bullet_notification_collector]
136
+ end
137
+
138
+ def notification?
139
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
140
+ notification_collector.notifications_present?
141
+ end
142
+
143
+ def gather_inline_notifications
144
+ responses = []
145
+ for_each_active_notifier_with_notification do |notification|
146
+ responses << notification.notify_inline
147
+ end
148
+ responses.join( "\n" )
149
+ end
150
+
151
+ def perform_out_of_channel_notifications(env = {})
152
+ for_each_active_notifier_with_notification do |notification|
153
+ notification.url = [env['HTTP_HOST'], env['REQUEST_URI']].compact.join
154
+ notification.notify_out_of_channel
155
+ end
156
+ end
157
+
158
+ def footer_info
159
+ info = []
160
+ for_each_active_notifier_with_notification do |notification|
161
+ info << notification.short_notice
162
+ end
163
+ info
164
+ end
165
+
166
+ def warnings
167
+ notification_collector.collection.inject({}) do |warnings, notification|
168
+ warning_type = notification.class.to_s.split(':').last.tableize
169
+ warnings[warning_type] ||= []
170
+ warnings[warning_type] << notification
171
+ warnings
172
+ end
173
+ end
174
+
175
+ def profile
176
+ Bullet.start_request if Bullet.enable?
177
+
178
+ yield
179
+
180
+ if Bullet.enable? && Bullet.notification?
181
+ Bullet.perform_out_of_channel_notifications
182
+ end
183
+ Bullet.end_request if Bullet.enable?
184
+ end
185
+
186
+ private
187
+ def for_each_active_notifier_with_notification
188
+ UniformNotifier.active_notifiers.each do |notifier|
189
+ notification_collector.collection.each do |notification|
190
+ notification.notifier = notifier
191
+ yield notification
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,148 @@
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::AssociationPreload::ClassMethods.class_eval do
23
+ alias_method :origin_preload_associations, :preload_associations
24
+ # include query for one to many associations.
25
+ # keep this eager loadings.
26
+ def preload_associations(records, associations, preload_options={})
27
+ records = [records].flatten.compact.uniq
28
+ return if records.empty?
29
+ records.each do |record|
30
+ Bullet::Detector::Association.add_object_associations(record, associations)
31
+ end
32
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
33
+ origin_preload_associations(records, associations, preload_options={})
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::ClassMethods::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::AssociationCollection.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_first, :first
90
+ def first(*args)
91
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
92
+ origin_first(*args)
93
+ end
94
+
95
+ alias_method :origin_last, :last
96
+ def last(*args)
97
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
98
+ origin_last(*args)
99
+ end
100
+
101
+ alias_method :origin_empty?, :empty?
102
+ def empty?
103
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
104
+ origin_empty?
105
+ end
106
+ end
107
+
108
+ ::ActiveRecord::Associations::AssociationProxy.class_eval do
109
+ # call has_one and belong_to association
110
+ alias_method :origin_load_target, :load_target
111
+ def load_target
112
+ # avoid stack level too deep
113
+ result = origin_load_target
114
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.any? { |c| c.include?("load_target") }
115
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
116
+ result
117
+ end
118
+
119
+ alias_method :origin_set_inverse_instance, :set_inverse_instance
120
+ def set_inverse_instance(record, instance)
121
+ if record && we_can_set_the_inverse_on_this?(record)
122
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(record)
123
+ end
124
+ origin_set_inverse_instance(record, instance)
125
+ end
126
+ end
127
+
128
+ ::ActiveRecord::Associations::HasManyAssociation.class_eval do
129
+ alias_method :origin_has_cached_counter?, :has_cached_counter?
130
+
131
+ def has_cached_counter?
132
+ result = origin_has_cached_counter?
133
+ Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
134
+ result
135
+ end
136
+ end
137
+
138
+ ::ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
139
+ alias_method :origin_has_cached_counter?, :has_cached_counter?
140
+ def has_cached_counter?
141
+ result = origin_has_cached_counter?
142
+ Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
143
+ result
144
+ end
145
+ end
146
+ end
147
+ end
148
+ 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.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
+ end
95
+
96
+ ::ActiveRecord::Associations::SingularAssociation.class_eval do
97
+ # call has_one and belongs_to associations
98
+ alias_method :origin_reader, :reader
99
+ def reader(force_reload = false)
100
+ result = origin_reader(force_reload)
101
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
102
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
103
+ result
104
+ end
105
+ end
106
+
107
+ ::ActiveRecord::Associations::Association.class_eval do
108
+ alias_method :origin_set_inverse_instance, :set_inverse_instance
109
+ def set_inverse_instance(record)
110
+ if record && invertible_for?(record)
111
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(record)
112
+ end
113
+ origin_set_inverse_instance(record)
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