# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(*args, **kwargs, &block)
result = super(*args, **kwargs, &block)
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader::Batch.prepend(
Module.new do
def call
if Bullet.start?
@preloaders.each do |preloader|
preloader.records.each { |record|
Bullet::Detector::Association.add_object_associations(record, preloader.associations)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::Branch.prepend(
Module.new do
def preloaders_for_reflection(reflection, reflection_records)
if Bullet.start?
reflection_records.compact!
if reflection_records.first.class.name !~ /^HABTM_/
reflection_records.each { |record|
Bullet::Detector::Association.add_object_associations(record, reflection.name)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def source_preloaders
if Bullet.start? && !defined?(@source_preloaders)
preloaders = super
preloaders.each do |preloader|
reflection_name = preloader.send(:reflection).name
preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
else
super
end
end
def through_preloaders
if Bullet.start? && !defined?(@through_preloaders)
preloaders = super
preloaders.each do |preloader|
reflection_name = preloader.send(:reflection).name
preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
else
super
end
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(*args, **kwargs, &block)
@bullet_eager_loadings = {}
records = super(*args, **kwargs, &block)
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(*args, **kwargs, &block)
ar_parent, parent, row = args
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super(*args, **kwargs, &block)
end
# call join associations
def construct_model(*args, **kwargs, &block)
record, node = args
result = super(*args, **kwargs, &block)
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
def inversed_from_queries(record)
if Bullet.start? && inversable?(record)
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def reader
result = super
if Bullet.start?
if owner.class.name !~ /^HABTM_/
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end