module Bullet::ActiveRecord

Public Class Methods

enable() click to toggle source
# File lib/bullet/active_record4.rb, line 5
def self.enable
  require 'active_record'
  ::ActiveRecord::Base.class_eval do
    class << self
      alias_method :origin_find_by_sql, :find_by_sql
      def find_by_sql(sql, binds = [])
        result = origin_find_by_sql(sql, binds)
        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
  end

  ::ActiveRecord::Relation.class_eval do
    alias_method :origin_to_a, :to_a
    # 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 to_a
      records = origin_to_a
      if Bullet.start?
        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
      records
    end
  end

  ::ActiveRecord::Persistence.class_eval do
    def _create_record_with_bullet(*args)
      _create_record_without_bullet(*args).tap { Bullet::Detector::NPlusOneQuery.add_impossible_object(self) }
    end
    alias_method_chain :_create_record, :bullet
  end

  ::ActiveRecord::Associations::Preloader.class_eval do
    # include query for one to many associations.
    # keep this eager loadings.
    alias_method :origin_initialize, :initialize
    def initialize(records, associations, preload_scope = nil)
      origin_initialize(records, associations, preload_scope)

      if Bullet.start?
        records = [records].flatten.compact.uniq
        return if records.empty?

        records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
        Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
      end
    end
  end

  ::ActiveRecord::FinderMethods.class_eval do
    # add includes in scope
    alias_method :origin_find_with_associations, :find_with_associations
    def find_with_associations
      records = origin_find_with_associations
      if Bullet.start?
        associations = (eager_load_values + includes_values).uniq
        records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
        Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
      end
      records
    end
  end

  ::ActiveRecord::Associations::JoinDependency.class_eval do
    alias_method :origin_instantiate, :instantiate
    alias_method :origin_construct_association, :construct_association

    def instantiate(rows)
      @bullet_eager_loadings = {}
      records = origin_instantiate(rows)

      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

    # call join associations
    def construct_association(record, join, row)
      result = origin_construct_association(record, join, row)

      if Bullet.start?
        associations = [join.reflection.name]
        if join.reflection.nested?
          associations << join.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::CollectionAssociation.class_eval do
    # call one to many associations
    alias_method :origin_load_target, :load_target
    def load_target
      Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
      origin_load_target
    end

    alias_method :origin_include?, :include?
    def include?(object)
      Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
      origin_include?(object)
    end
  end

  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
    alias_method :origin_empty?, :empty?
    def empty?
      if Bullet.start? && !loaded? && !has_cached_counter?(@reflection)
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_empty?
    end
  end

  ::ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
    alias_method :origin_empty?, :empty?
    def empty?
      Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start? && !loaded?
      origin_empty?
    end
  end

  ::ActiveRecord::Associations::SingularAssociation.class_eval do
    # call has_one and belongs_to associations
    alias_method :origin_reader, :reader
    def reader(force_reload = false)
      result = origin_reader(force_reload)
      if Bullet.start?
        unless @inversed
          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
          Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
        end
      end
      result
    end
  end

  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
    alias_method :origin_has_cached_counter?, :has_cached_counter?

    def has_cached_counter?(reflection = reflection())
      result = origin_has_cached_counter?(reflection)
      Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) if Bullet.start? && !result
      result
    end
  end

  ::ActiveRecord::Associations::CollectionProxy.class_eval do
    def count(column_name = nil, options = {})
      if Bullet.start?
        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)
      end
      super(column_name, options)
    end
  end
end
find(*args) click to toggle source
# File lib/bullet/active_record42.rb, line 10
def find(*args)
  result = origin_find(*args)
  if Bullet.start?
    if result.is_a? Array
      Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
      Bullet::Detector::CounterCache.add_possible_objects(result)
    elsif result.is_a? ::ActiveRecord::Base
      Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
      Bullet::Detector::CounterCache.add_impossible_object(result)
    end
  end
  result
end
Also aliased as: origin_find
find_by_sql(sql, binds = []) click to toggle source
# File lib/bullet/active_record4.rb, line 10
def find_by_sql(sql, binds = [])
  result = origin_find_by_sql(sql, binds)
  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
Also aliased as: origin_find_by_sql, origin_find_by_sql, origin_find_by_sql
new(records, associations, preload_scope = nil) click to toggle source
# File lib/bullet/active_record4.rb, line 61
def initialize(records, associations, preload_scope = nil)
  origin_initialize(records, associations, preload_scope)

  if Bullet.start?
    records = [records].flatten.compact.uniq
    return if records.empty?

    records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
    Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
  end
end
origin_find(*args)
Alias for: find
origin_find_by_sql(sql, binds = [])
Alias for: find_by_sql

Public Instance Methods

_create_record_with_bullet(*args) click to toggle source
# File lib/bullet/active_record4.rb, line 51
def _create_record_with_bullet(*args)
  _create_record_without_bullet(*args).tap { Bullet::Detector::NPlusOneQuery.add_impossible_object(self) }
end
call() click to toggle source
Calls superclass method
# File lib/bullet/active_record70.rb, line 65
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
construct(ar_parent, parent, row, rs, seen, model_cache, aliases) click to toggle source
# File lib/bullet/active_record42.rb, line 124
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
  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.nested?
          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

  origin_construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
end
construct_association(record, join, row) click to toggle source

call join associations

# File lib/bullet/active_record4.rb, line 106
def construct_association(record, join, row)
  result = origin_construct_association(record, join, row)

  if Bullet.start?
    associations = [join.reflection.name]
    if join.reflection.nested?
      associations << join.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
construct_model(record, node, row, model_cache, id, aliases) click to toggle source

call join associations

# File lib/bullet/active_record41.rb, line 109
def construct_model(record, node, row, model_cache, id, aliases)
  result = origin_construct_model(record, node, row, model_cache, id, aliases)

  if Bullet.start?
    associations = [node.reflection.name]
    if node.reflection.nested?
      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
count(column_name = nil, options = {}) click to toggle source
Calls superclass method
# File lib/bullet/active_record4.rb, line 186
def count(column_name = nil, options = {})
  if Bullet.start?
    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)
  end
  super(column_name, options)
end
count_records() click to toggle source
# File lib/bullet/active_record41.rb, line 170
def count_records
  result = has_cached_counter?
  Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) if Bullet.start? && !result
  origin_count_records
end
empty?() click to toggle source
# File lib/bullet/active_record4.rb, line 144
def empty?
  if Bullet.start? && !loaded? && !has_cached_counter?(@reflection)
    Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
  end
  origin_empty?
end
find_with_associations() click to toggle source
# File lib/bullet/active_record4.rb, line 77
def find_with_associations
  records = origin_find_with_associations
  if Bullet.start?
    associations = (eager_load_values + includes_values).uniq
    records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
    Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
  end
  records
end
has_cached_counter?(reflection = reflection()) click to toggle source
# File lib/bullet/active_record4.rb, line 178
def has_cached_counter?(reflection = reflection())
  result = origin_has_cached_counter?(reflection)
  Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) if Bullet.start? && !result
  result
end
include?(object) click to toggle source
# File lib/bullet/active_record4.rb, line 136
def include?(object)
  Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
  origin_include?(object)
end
instantiate(rows) click to toggle source
# File lib/bullet/active_record4.rb, line 92
def instantiate(rows)
  @bullet_eager_loadings = {}
  records = origin_instantiate(rows)

  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
inversed_from(record) click to toggle source
Calls superclass method
# File lib/bullet/active_record52.rb, line 147
def inversed_from(record)
  if Bullet.start?
    Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
  end
  super
end
inversed_from_queries(record) click to toggle source
Calls superclass method
# File lib/bullet/active_record70.rb, line 190
def inversed_from_queries(record)
  if Bullet.start? && inversable?(record)
    Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
  end
  super
end
load_target() click to toggle source
# File lib/bullet/active_record4.rb, line 130
def load_target
  Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
  origin_load_target
end
preloaded_records() click to toggle source
Calls superclass method
# File lib/bullet/active_record60.rb, line 91
def preloaded_records
  if Bullet.start? && !defined?(@preloaded_records)
    source_preloaders.each do |source_preloader|
      reflection_name = source_preloader.send(:reflection).name
      source_preloader.send(:owners).each do |owner|
        Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
      end
    end
  end
  super
end
preloaders_for_one(association, records, scope) click to toggle source
Calls superclass method
# File lib/bullet/active_record5.rb, line 65
def preloaders_for_one(association, records, scope)
  if Bullet.start?
    records.compact!
    if records.first.class.name !~ /^HABTM_/
      records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
      Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
    end
  end
  super
end
preloaders_for_reflection(reflection, records, scope) click to toggle source
Calls superclass method
# File lib/bullet/active_record60.rb, line 76
def preloaders_for_reflection(reflection, records, scope)
  if Bullet.start?
    records.compact!
    if records.first.class.name !~ /^HABTM_/
      records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
      Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
    end
  end
  super
end
preloaders_on(association, records, scope) click to toggle source
# File lib/bullet/active_record41.rb, line 63
def preloaders_on(association, records, scope)
  if Bullet.start?
    records.compact!
    if records.first.class.name !~ /^HABTM_/
      records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
      Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
    end
  end
  origin_preloaders_on(association, records, scope)
end
reader(force_reload = false) click to toggle source
# File lib/bullet/active_record4.rb, line 163
def reader(force_reload = false)
  result = origin_reader(force_reload)
  if Bullet.start?
    unless @inversed
      Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
    end
  end
  result
end
records() click to toggle source

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.

Calls superclass method
# File lib/bullet/active_record5.rb, line 45
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
source_preloaders() click to toggle source
Calls superclass method
# File lib/bullet/active_record70.rb, line 98
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
target() click to toggle source

call has_one and belongs_to associations

Calls superclass method
# File lib/bullet/active_record5.rb, line 235
def target
  result = super()

  if Bullet.start?
    if owner.class.name !~ /^HABTM_/ && !@inversed
      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
to_a() click to toggle source

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.

# File lib/bullet/active_record4.rb, line 35
def to_a
  records = origin_to_a
  if Bullet.start?
    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
  records
end