class RelationToJSON::Base

Attributes

relation[R]
schema[R]

Public Class Methods

new(relation, schema) click to toggle source
# File lib/relation_to_json/base.rb, line 10
def initialize(relation, schema)
  @relation = relation
  @schema = schema
end

Public Instance Methods

as_json() click to toggle source
# File lib/relation_to_json/base.rb, line 15
def as_json
  # put everything here, anything else is private
  attributes, schema_associations = schema
    .partition { |e| e.is_a?(Symbol) }
  schema_associations = schema_associations.first.dup || []

  attributes = Set[:id] + attributes

  reflections = RelationToJSON::ReflectionBuilder.build(schema_associations, relation)
  schema_associations.each do |schema_association, association_attributes|
    reflection = reflections[schema_association]

    case reflection
    when RelationToJSON::BelongsToReflection
      attributes << reflection.foreign_key.to_sym
    when RelationToJSON::HasOneReflection
      association_attributes << reflection.foreign_key.to_sym
    end
  end

  raise_unless_all_attributes_present_on_model!(relation, attributes)

  result = relation
    .reload # if when the relation isn't loaded, it may have strange ordering
    .pluck(*attributes)
    .map { |plucked| attributes.zip(Array.wrap(plucked)).to_h }

  transposed = transpose(result)

  reflections.each do |reflection_name, reflection|
    foreign_key = reflection.foreign_key
    primary_key = reflection.primary_key

    # if the current schema still has associations
    # then we need to recursively find the JSON
    # representation of that association
    # Otherwise, we can perform a shallow .pluck
    # of the association attributes
    # and map them back onto the transposed hash
    # this returns an array of hashes that map association attributes to plucked values
    plucked_values = reflection.pluck_association_columns(transposed)

    case reflection
    when RelationToJSON::BelongsToReflection
    # build a temporary mapping of id => assigned_attributes
      associated_model_primary_key_indexed_plucked_values = plucked_values
          .to_h { |attrs| [attrs&.fetch(primary_key, nil), attrs] }
          .compact

      result.each do |record|
        foreign_key_value = record[foreign_key]
        plucked_values = associated_model_primary_key_indexed_plucked_values[foreign_key_value]
        record[reflection_name] = plucked_values
        record.except!(foreign_key)
      end
    when RelationToJSON::HasOneReflection
    # build a temporary mapping of id => assigned_attributes
      associated_model_foreign_key_indexed_plucked_values = plucked_values
        .to_h { |attrs| [attrs&.fetch(foreign_key, nil), attrs] }
        .compact

      result.each do |record|
        primary_key_value = record[primary_key]
        plucked_values = associated_model_foreign_key_indexed_plucked_values[primary_key_value]
        plucked_values.except!(foreign_key) if plucked_values
        record[reflection_name] = plucked_values
      end
    end
  end

  result&.map { |partial| partial.with_indifferent_access }
end

Private Instance Methods

raise_unless_all_attributes_present_on_model!(relation, requested) click to toggle source
# File lib/relation_to_json/base.rb, line 114
def raise_unless_all_attributes_present_on_model!(relation, requested)
  available = Set[*relation.klass.column_names]
  requested = Set[*requested.map(&:to_s)]
  return if requested <= available

  missing = requested - available

  raise InvalidSchemaError.new(missing.to_a, relation.klass)
end
transpose(values) click to toggle source
# File lib/relation_to_json/base.rb, line 90
def transpose(values)
  # values is a list of hashes
  # each hash should be identical
  # i.e. [{id: 1, clinical_sender_id: 2}, {id: 2, clinical_sender_id: 3}]
  # and tranposes into a hash
  # with identical keys
  # but values are arrays
  # i.e. { id: [1, 2], clinical_sender_id: [2, 3] }

  result = {}

  values.each do |value|
    value.each do |k, v|
      if result.include?(k)
        result[k] << v
      else
        result[k] = [v]
      end
    end
  end

  result
end