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 save_with_bullet(*args, &proc)
      was_new_record = new_record?
      save_without_bullet(*args, &proc).tap do |result|
        Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
      end
    end
    alias_method_chain :save, :bullet

    def save_with_bullet!(*args, &proc)
      was_new_record = new_record?
      save_without_bullet!(*args, &proc).tap do |result|
        Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
      end
    end
    alias_method_chain :save!, :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 do |record|
          Bullet::Detector::Association.add_object_associations(record, associations)
        end
        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 do |record|
          Bullet::Detector::Association.add_object_associations(record, associations)
        end
        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
        Bullet::Detector::Association.add_object_associations(record, associations)
        Bullet::Detector::NPlusOneQuery.call_association(record, associations)
        @bullet_eager_loadings[record.class] ||= {}
        @bullet_eager_loadings[record.class][record] ||= Set.new
        @bullet_eager_loadings[record.class][record] << associations
      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
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_load_target
    end

    alias_method :origin_include?, :include?
    def include?(object)
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      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?
      if Bullet.start? && !loaded?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      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?

    # rubocop:disable Style/MethodCallWithoutArgsParentheses
    def has_cached_counter?(reflection = reflection())
      result = origin_has_cached_counter?(reflection)
      if Bullet.start? && !result
        Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
      end
      result
    end
    # rubocop:enable Style/MethodCallWithoutArgsParentheses
  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 72
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 do |record|
      Bullet::Detector::Association.add_object_associations(record, associations)
    end
    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

construct(ar_parent, parent, row, rs, seen, model_cache, aliases) click to toggle source
# File lib/bullet/active_record42.rb, line 137
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
        Bullet::Detector::Association.add_object_associations(ar_parent, associations)
        Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
        @bullet_eager_loadings[ar_parent.class] ||= {}
        @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
        @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
      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 119
def construct_association(record, join, row)
  result = origin_construct_association(record, join, row)

  if Bullet.start?
    associations = join.reflection.name
    Bullet::Detector::Association.add_object_associations(record, associations)
    Bullet::Detector::NPlusOneQuery.call_association(record, associations)
    @bullet_eager_loadings[record.class] ||= {}
    @bullet_eager_loadings[record.class][record] ||= Set.new
    @bullet_eager_loadings[record.class][record] << associations
  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 122
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
    Bullet::Detector::Association.add_object_associations(record, associations)
    Bullet::Detector::NPlusOneQuery.call_association(record, associations)
    @bullet_eager_loadings[record.class] ||= {}
    @bullet_eager_loadings[record.class][record] ||= Set.new
    @bullet_eager_loadings[record.class][record] << associations
  end

  result
end
count_records() click to toggle source
# File lib/bullet/active_record41.rb, line 182
def count_records
  result = has_cached_counter?
  if Bullet.start? && !result
    Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name)
  end
  origin_count_records
end
empty?() click to toggle source
# File lib/bullet/active_record4.rb, line 156
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 88
def find_with_associations
  records = origin_find_with_associations
  if Bullet.start?
    associations = (eager_load_values + includes_values).uniq
    records.each do |record|
      Bullet::Detector::Association.add_object_associations(record, associations)
    end
    Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
  end
  records
end
has_cached_counter?(reflection = reflection()) click to toggle source

rubocop:disable Style/MethodCallWithoutArgsParentheses

# File lib/bullet/active_record4.rb, line 193
def has_cached_counter?(reflection = reflection())
  result = origin_has_cached_counter?(reflection)
  if Bullet.start? && !result
    Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
  end
  result
end
include?(object) click to toggle source
# File lib/bullet/active_record4.rb, line 146
def include?(object)
  if Bullet.start?
    Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
  end
  origin_include?(object)
end
instantiate(rows) click to toggle source
# File lib/bullet/active_record4.rb, line 105
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
load_target() click to toggle source
# File lib/bullet/active_record4.rb, line 138
def load_target
  if Bullet.start?
    Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
  end
  origin_load_target
end
preloaders_for_one(association, records, scope) click to toggle source
Calls superclass method
# File lib/bullet/active_record5.rb, line 67
def preloaders_for_one(association, records, scope)
  if Bullet.start?
    records.compact!
    if records.first.class.name !~ /^HABTM_/
      records.each do |record|
        Bullet::Detector::Association.add_object_associations(record, association)
      end
      Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
    end
  end
  super
end
preloaders_on(association, records, scope) click to toggle source
# File lib/bullet/active_record41.rb, line 73
def preloaders_on(association, records, scope)
  if Bullet.start?
    records.compact!
    if records.first.class.name !~ /^HABTM_/
      records.each do |record|
        Bullet::Detector::Association.add_object_associations(record, association)
      end
      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 177
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 49
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
save_with_bullet(*args, &proc) click to toggle source
# File lib/bullet/active_record4.rb, line 51
def save_with_bullet(*args, &proc)
  was_new_record = new_record?
  save_without_bullet(*args, &proc).tap do |result|
    Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
  end
end
save_with_bullet!(*args, &proc) click to toggle source
# File lib/bullet/active_record4.rb, line 59
def save_with_bullet!(*args, &proc)
  was_new_record = new_record?
  save_without_bullet!(*args, &proc).tap do |result|
    Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
  end
end
target() click to toggle source

call has_one and belongs_to associations

Calls superclass method
# File lib/bullet/active_record5.rb, line 208
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