module ActiveRecord::VirtualAttributes::VirtualDelegates

VirtualDelegate is the same as delegate, but adds sql support, and a default when a value is not found

Model.belongs_to :association Model.virtual_delegate :field1, :field2, to: :association

Model.select(:field1) # now works

Public Class Methods

select_from_alias(to_ref, col, to_model_col_name, src_model_id) { |arel| ... } click to toggle source

Based upon ActiveRecord AssociationScope.scope

# File lib/active_record/virtual_attributes/virtual_delegates.rb, line 244
def self.select_from_alias(to_ref, col, to_model_col_name, src_model_id)
  query = if to_ref.scope
            to_ref.klass.instance_exec(nil, &to_ref.scope)
          else
            to_ref.klass.all
          end

  src_model   = to_ref.active_record
  to_table    = select_from_alias_table(to_ref.klass, src_model_id.relation)
  to_model_id = to_ref.klass.arel_attribute(to_model_col_name, to_table)
  to_column   = to_ref.klass.arel_attribute(col, to_table)
  arel        = query.except(:select).select(to_column).arel
                     .from(to_table)
                     .where(to_model_id.eq(src_model_id))

  # :type is in the reflection definition (meaning it is polymorphic)
  if to_ref.type
    # get the class name (e.g. "Host")
    polymorphic_type = src_model.base_class.name
    arel = arel.where(to_ref.klass.arel_attribute(to_ref.type).eq(polymorphic_type))
  end

  yield arel if block_given?

  Arel.sql("(#{arel.to_sql})")
end
select_from_alias_table(to_klass, src_relation) click to toggle source

determine table reference to use for a sub query

typically to_table is just the table used for the to_ref but if it is a self join, then it will also have an alias

# File lib/active_record/virtual_attributes/virtual_delegates.rb, line 275
def self.select_from_alias_table(to_klass, src_relation)
  to_table = to_klass.arel_table
  # if a self join, alias the second table to a different name
  if to_table.table_name == src_relation.table_name
    # use a dup to not modify the primary table in the model
    to_table = to_table.dup
    # use a table alias to not conflict with table name in the primary query
    to_table.table_alias = "#{to_table.table_name}_sub"
  end
  to_table
end