rcarver/orel

View on GitHub
lib/orel/query/reader.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module Orel
  class Query
    # Orel::Query::Reader converts rows of data into objects and their
    # associations.
    class Reader

      def initialize(klass, heading, connection, query, select_manager, description)
        @klass = klass
        @heading = heading
        @connection = connection
        @query = query
        @select_manager = select_manager
        @description = description
      end

      # Implements Orel::QueryReader::Reader.
      def read(description)
        # Execute the query.
        rows = @connection.execute(@select_manager.to_sql, description)

        # Extract objects from rows.
        if @query.projected_joins.empty?
          objects = extract_objects_without_joins(rows)
        else
          objects = extract_objects_with_joins(@query.projected_joins, rows)
        end

        # Finalize and return the objects.
        objects.each { |object|
          # The object is persisited because it came from the databse.
          object.persisted!

          # The object is readonly because it's a complex relation
          object.readonly!

          # The object is locked for query because you should get all
          # of the data you're interested in one shot.
          object.locked_for_query! if @query.locked_for_query
        }
      end

    protected

      def extract_objects_without_joins(rows)
        rows.each(:as => :hash).map { |row|
          @klass.new(row)
        }
      end

      def extract_objects_with_joins(projected_joins, rows)
        objects = []
        objects_hash = {}
        rows.each(:as => :hash) { |row|

          # Extract association projections from the row.
          association_projections = {}
          projected_joins.each { |join|
            join_id = join.join_id
            association_projections[join.join_class] = {}
            row.each { |key, value|
              if key[0, join_id.size] == join_id
                name = key[(join_id.size)..-1]
                row.delete(key)
                association_projections[join.join_class][name] = value
              end
            }
          }

          # Only instantiate the object once.
          if objects_hash[row]
            object = objects_hash[row]
          else
            object = objects_hash[row] = @klass.new(row)
            objects << object
          end

          projected_joins.each { |join|
            object._store_association(join.join_class, association_projections[join.join_class])
          }
        }
        objects
      end
    end
  end
end