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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +20 -0
- data/CHANGELOG.md +75 -0
- data/Gemfile +19 -0
- data/Gemfile.mongoid +14 -0
- data/Gemfile.mongoid-2.4 +19 -0
- data/Gemfile.mongoid-2.5 +19 -0
- data/Gemfile.mongoid-2.6 +19 -0
- data/Gemfile.mongoid-2.7 +19 -0
- data/Gemfile.mongoid-2.8 +19 -0
- data/Gemfile.mongoid-3.0 +19 -0
- data/Gemfile.mongoid-3.1 +19 -0
- data/Gemfile.mongoid-4.0 +19 -0
- data/Gemfile.rails-3.0 +19 -0
- data/Gemfile.rails-3.1 +19 -0
- data/Gemfile.rails-3.2 +19 -0
- data/Gemfile.rails-4.0 +19 -0
- data/Gemfile.rails-4.1 +19 -0
- data/Guardfile +8 -0
- data/Hacking.md +74 -0
- data/MIT-LICENSE +20 -0
- data/README.md +428 -0
- data/Rakefile +52 -0
- data/bullet_instructure.gemspec +27 -0
- data/lib/bullet.rb +196 -0
- data/lib/bullet/active_record3.rb +148 -0
- data/lib/bullet/active_record3x.rb +128 -0
- data/lib/bullet/active_record4.rb +128 -0
- data/lib/bullet/active_record41.rb +121 -0
- data/lib/bullet/dependency.rb +81 -0
- data/lib/bullet/detector.rb +9 -0
- data/lib/bullet/detector/association.rb +67 -0
- data/lib/bullet/detector/base.rb +6 -0
- data/lib/bullet/detector/counter_cache.rb +59 -0
- data/lib/bullet/detector/n_plus_one_query.rb +89 -0
- data/lib/bullet/detector/unused_eager_loading.rb +84 -0
- data/lib/bullet/ext/object.rb +9 -0
- data/lib/bullet/ext/string.rb +5 -0
- data/lib/bullet/mongoid2x.rb +56 -0
- data/lib/bullet/mongoid3x.rb +56 -0
- data/lib/bullet/mongoid4x.rb +56 -0
- data/lib/bullet/notification.rb +10 -0
- data/lib/bullet/notification/base.rb +97 -0
- data/lib/bullet/notification/counter_cache.rb +13 -0
- data/lib/bullet/notification/n_plus_one_query.rb +28 -0
- data/lib/bullet/notification/unused_eager_loading.rb +13 -0
- data/lib/bullet/notification_collector.rb +24 -0
- data/lib/bullet/rack.rb +81 -0
- data/lib/bullet/registry.rb +7 -0
- data/lib/bullet/registry/association.rb +13 -0
- data/lib/bullet/registry/base.rb +40 -0
- data/lib/bullet/registry/object.rb +13 -0
- data/lib/bullet/version.rb +4 -0
- data/perf/benchmark.rb +121 -0
- data/rails/init.rb +1 -0
- data/spec/bullet/detector/association_spec.rb +26 -0
- data/spec/bullet/detector/base_spec.rb +8 -0
- data/spec/bullet/detector/counter_cache_spec.rb +56 -0
- data/spec/bullet/detector/n_plus_one_query_spec.rb +138 -0
- data/spec/bullet/detector/unused_eager_loading_spec.rb +88 -0
- data/spec/bullet/ext/object_spec.rb +17 -0
- data/spec/bullet/ext/string_spec.rb +13 -0
- data/spec/bullet/notification/base_spec.rb +83 -0
- data/spec/bullet/notification/counter_cache_spec.rb +12 -0
- data/spec/bullet/notification/n_plus_one_query_spec.rb +14 -0
- data/spec/bullet/notification/unused_eager_loading_spec.rb +12 -0
- data/spec/bullet/notification_collector_spec.rb +32 -0
- data/spec/bullet/rack_spec.rb +97 -0
- data/spec/bullet/registry/association_spec.rb +26 -0
- data/spec/bullet/registry/base_spec.rb +44 -0
- data/spec/bullet/registry/object_spec.rb +24 -0
- data/spec/bullet_spec.rb +41 -0
- data/spec/integration/active_record3/association_spec.rb +651 -0
- data/spec/integration/active_record4/association_spec.rb +649 -0
- data/spec/integration/counter_cache_spec.rb +63 -0
- data/spec/integration/mongoid/association_spec.rb +258 -0
- data/spec/models/address.rb +3 -0
- data/spec/models/author.rb +3 -0
- data/spec/models/base_user.rb +5 -0
- data/spec/models/category.rb +7 -0
- data/spec/models/city.rb +3 -0
- data/spec/models/client.rb +4 -0
- data/spec/models/comment.rb +4 -0
- data/spec/models/company.rb +3 -0
- data/spec/models/country.rb +3 -0
- data/spec/models/document.rb +5 -0
- data/spec/models/entry.rb +3 -0
- data/spec/models/firm.rb +4 -0
- data/spec/models/folder.rb +2 -0
- data/spec/models/mongoid/address.rb +7 -0
- data/spec/models/mongoid/category.rb +8 -0
- data/spec/models/mongoid/comment.rb +7 -0
- data/spec/models/mongoid/company.rb +7 -0
- data/spec/models/mongoid/entry.rb +7 -0
- data/spec/models/mongoid/post.rb +12 -0
- data/spec/models/mongoid/user.rb +5 -0
- data/spec/models/newspaper.rb +3 -0
- data/spec/models/page.rb +2 -0
- data/spec/models/person.rb +3 -0
- data/spec/models/pet.rb +3 -0
- data/spec/models/post.rb +10 -0
- data/spec/models/relationship.rb +4 -0
- data/spec/models/student.rb +3 -0
- data/spec/models/submission.rb +4 -0
- data/spec/models/teacher.rb +3 -0
- data/spec/models/user.rb +4 -0
- data/spec/models/writer.rb +2 -0
- data/spec/spec_helper.rb +103 -0
- data/spec/support/bullet_ext.rb +55 -0
- data/spec/support/mongo_seed.rb +65 -0
- data/spec/support/rack_double.rb +55 -0
- data/spec/support/sqlite_seed.rb +229 -0
- data/tasks/bullet_tasks.rake +9 -0
- data/test.sh +15 -0
- 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
|
+
|
data/lib/bullet.rb
ADDED
@@ -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
|