class Pragma::Decorator::Association::Adapter::ActiveRecord

The ActiveRecord association adapter is used in AR environments and tries to minimize the number of SQL queries that are made to retrieve the associated object's data.

@api private

Public Class Methods

new(bond) click to toggle source

Initializes the adapter.

@param bond [Bond] the bond to use in the adapter

@raise [InconsistentTypeError] when the association's real type is different from the

one defined on the decorator ()e.g. decorator defines the association as +belongs_to+,
but ActiveRecord reports its type is +has_one+)
Calls superclass method
# File lib/pragma/decorator/association/adapter/active_record.rb, line 33
def initialize(bond)
  super
  check_type_consistency
end
supports?(bond) click to toggle source

Returns whether the adapter supports the given bond.

@param bond [Bond] the bond to check

@return [Boolean] whether the object is an instance of ActiveRecord::Base

# File lib/pragma/decorator/association/adapter/active_record.rb, line 20
def supports?(bond)
  Object.const_defined?('::ActiveRecord::Base') &&
    bond.model.is_a?(::ActiveRecord::Base)
end

Public Instance Methods

full_object() click to toggle source

Returns the expanded associated object.

This will simply return the associated object itself, delegating caching to AR.

@return [Object] the associated object

@todo Ensure the required attributes are present on the associated object

# File lib/pragma/decorator/association/adapter/active_record.rb, line 87
def full_object
  associated_object
end
primary_key() click to toggle source

Returns the primary key of the associated object.

If the exec_context of the association is decorator, this will simply return early with the value returned by #id on the associated object.

If the association is a belongs_to, there are three possible scenarios:

* the association does not have a custom scope: this will compute the PK by calling
  the foreign key on the parent model;
* the association has a custom scope and it has not been loaded: this will compute
  the PK by +pluck+ing the PK column of the associated object;
* the association has a custom scope and it has been loaded: this will compute
  the PK by retrieving the PK attribute from the loaded object.

If the association is a has_one, there are two possible scenarios:

* the association has already been loaded: this will compute the PK by retrieving the
  PK attribute from the loaded object;
* the association has not been loaded: this will compute the PK by +pluck+ing the PK
  column of the associated object;

Custom scopes are always respected in both belongs_to and has_one.

nil values are handled gracefully in all cases.

@return [String|Integer|NilClass] the PK of the associated object

@todo Allow to specify a different PK attribute when exec_context is decorator

# File lib/pragma/decorator/association/adapter/active_record.rb, line 66
def primary_key
  return associated_object&.id if association_reflection.nil? ||
                                  reflection.options[:exec_context] == :decorator

  case reflection.type
  when :belongs_to
    compute_belongs_to_fk
  when :has_one
    compute_has_one_fk
  else
    fail "Cannot compute primary key for #{reflection.type} association"
  end
end

Private Instance Methods

association_reflection() click to toggle source
# File lib/pragma/decorator/association/adapter/active_record.rb, line 135
def association_reflection
  @association_reflection ||= model.class.reflect_on_association(reflection.attribute)
end
check_type_consistency() click to toggle source
# File lib/pragma/decorator/association/adapter/active_record.rb, line 139
def check_type_consistency
  return unless association_reflection
  return if association_reflection.macro.to_sym == reflection.type.to_sym

  fail InconsistentTypeError.new(
    decorator: decorator,
    reflection: reflection,
    model_type: association_reflection.macro
  )
end
compute_belongs_to_fk() click to toggle source
# File lib/pragma/decorator/association/adapter/active_record.rb, line 93
def compute_belongs_to_fk
  primary_key = if association_reflection.polymorphic?
    association_reflection.options[:primary_key] || associated_object.class.primary_key
  else
    association_reflection.association_primary_key
  end

  if model.association(reflection.attribute).loaded?
    return associated_object&.public_send(primary_key)
  end

  if association_reflection.scope.nil?
    return model.public_send(association_reflection.foreign_key)
  end

  pluck_association_fk do |scope|
    fk = model.public_send(association_reflection.foreign_key)
    scope.where(primary_key => fk)
  end
end
compute_has_one_fk() click to toggle source
# File lib/pragma/decorator/association/adapter/active_record.rb, line 114
def compute_has_one_fk
  if model.association(reflection.attribute).loaded?
    return associated_object&.public_send(association_reflection.association_primary_key)
  end

  pluck_association_fk do |scope|
    pk = model.public_send(association_reflection.active_record_primary_key)
    scope.where(association_reflection.foreign_key => pk)
  end
end
pluck_association_fk() { |scope| ... } click to toggle source
# File lib/pragma/decorator/association/adapter/active_record.rb, line 125
def pluck_association_fk
  scope = association_reflection.klass.all

  if association_reflection.scope
    scope = scope.instance_eval(&association_reflection.scope)
  end

  yield(scope).pluck(association_reflection.association_primary_key).first
end