module Ar::Preloader

Constants

VERSION

Public Class Methods

check_association_exists(klass, association) click to toggle source
# File lib/ar/preloader.rb, line 8
def self.check_association_exists(klass, association)
  case association
  when String, Symbol
    return klass.reflections.keys.include?(association.to_s)
  when Array
    association.each do |v|
      return false unless Ar::Preloader.check_association_exists(klass, v)
    end
  when Hash
    association.each_pair do |k,v|
      return false unless klass.reflections.keys.include?(k.to_s)

      associated_klass = klass.reflections[k.to_s].class_name.constantize

      case v
      when String, Symbol, Hash
        return false unless Ar::Preloader.check_association_exists(associated_klass, v)
      when Array
        v.each do |each_v|
          return false unless Ar::Preloader.check_association_exists(associated_klass, each_v)
        end
      else
      end
    end

    return true
  else
    return false
  end
end
preload_association(list, association, inner_associations = []) click to toggle source
# File lib/ar/preloader.rb, line 39
    def self.preload_association(list, association, inner_associations = [])
      return if list.length == 0

      case association
      when String, Symbol
        klass = list.first.class
        klass.send :attr_reader, "_#{association}".to_sym

        association_details = klass.reflections[association.to_s]
        association_klass = association_details.class_name.constantize

        case association_details
        when ActiveRecord::Reflection::HasManyReflection
          foreign_key = association_details.foreign_key
          association_pk = association_klass.primary_key

          list.each do |obj|
            instance_variable_name = "@_#{association}".to_sym
            obj.instance_variable_set(instance_variable_name, [])
          end

          query = <<-EOS
SELECT
  distinct #{association_klass.table_name}.*
FROM #{association_klass.table_name}
WHERE #{association_klass.table_name}.#{foreign_key} in (#{ list.map{ |e| e[klass.primary_key.to_sym].try(:to_s) }.compact.join(",") })

EOS

          association_list = association_klass.find_by_sql(query).to_a

          if inner_associations.is_a?(Array)
            association_list.preload(*inner_associations)
          else
            association_list.preload(inner_associations)
          end

          list.each do |obj|
            instance_variable_name = "@_#{association}".to_sym

            association_list.select{ |e| e[foreign_key.to_sym] == obj[klass.primary_key.to_sym] }.each do |association_obj|
              each_list = obj.instance_variable_get(instance_variable_name)
              each_list << association_obj
              obj.instance_variable_set(instance_variable_name, each_list)
            end
          end
        when ActiveRecord::Reflection::BelongsToReflection
          foreign_key = association_details.foreign_key
          association_pk = association_klass.primary_key

          if list.map{ |e| e[foreign_key.to_sym] }.compact.length == 0
            return true
          end

          query = <<-EOS
SELECT
  distinct #{association_klass.table_name}.*
FROM #{association_klass.table_name}
WHERE #{association_klass.table_name}.#{association_klass.primary_key} in (#{ list.map{ |e| e[foreign_key.to_sym].try(:to_s) }.compact.join(",") })

EOS

          association_list = association_klass.find_by_sql(query).to_a

          if inner_associations.is_a?(Array)
            association_list.preload(*inner_associations)
          else
            association_list.preload(inner_associations)
          end

          association_map = association_list.map{ |e| [e[e.class.primary_key.to_sym], e] }.to_h

          list.each do |obj|
            if obj[foreign_key.to_sym].present? && association_map[obj[foreign_key.to_sym]].present?
              obj.instance_variable_set( "@_#{association}".to_sym, association_map[obj[foreign_key.to_sym]] )
            end
          end
        when ActiveRecord::Reflection::HasAndBelongsToManyReflection
          foreign_key = association_details.foreign_key
          association_foreign_key = association_details.association_foreign_key
          join_table = association_details.join_table

          list.each do |obj|
            instance_variable_name = "@_#{association}".to_sym
            obj.instance_variable_set(instance_variable_name, [])
          end

          join_query = <<-EOS
SELECT
  #{join_table}.#{foreign_key},
  #{join_table}.#{association_foreign_key}
FROM #{join_table}
WHERE #{join_table}.#{foreign_key} IN (#{ list.map{ |e| e[klass.primary_key.to_sym].try(:to_s) }.compact.join(",") })

EOS
          join_entries = ActiveRecord::Base.connection.execute(join_query).to_a

          query = <<-EOS
SELECT
  distinct #{association_klass.table_name}.*
FROM #{association_klass.table_name}
INNER JOIN #{join_table} ON #{join_table}.#{association_foreign_key} = #{association_klass.table_name}.#{association_klass.primary_key}
WHERE #{join_table}.#{foreign_key} IN (#{ list.map{ |e| e[klass.primary_key.to_sym].try(:to_s) }.compact.join(",") })

EOS

          association_list = association_klass.find_by_sql(query).to_a

          if inner_associations.is_a?(Array)
            association_list.preload(*inner_associations)
          else
            association_list.preload(inner_associations)
          end

          list.each do |obj|
            instance_variable_name = "@_#{association}".to_sym

            association_key_list = join_entries.select{ |e| e[0] == obj[klass.primary_key.to_s] }.map(&:last)

            association_list.select{ |e| association_key_list.include?( e[association_klass.primary_key.to_s] ) }.each do |association_obj|
              each_list = obj.instance_variable_get(instance_variable_name)
              each_list << association_obj
              obj.instance_variable_set(instance_variable_name, each_list)
            end
          end
        else
          raise Ar::Preloader::Error.new("Unsupported association type: '#{association}'")
        end
      when Hash
        association.each_pair do |k,v|
          Ar::Preloader.preload_association(list, k, v)
        end
      end
    end