locomotivecms/steam

View on GitHub
lib/locomotive/steam/models/mapper.rb

Summary

Maintainability
A
35 mins
Test Coverage
module Locomotive::Steam
  module Models

    class Mapper

      ASSOCIATION_CLASSES = {
        embedded:     EmbeddedAssociation,
        belongs_to:   BelongsToAssociation,
        has_many:     HasManyAssociation,
        many_to_many: ManyToManyAssociation
      }.freeze

      attr_reader :name, :options, :default_attributes, :localized_attributes, :associations

      def initialize(name, options, repository, &block)
        @name, @options, @repository = name, options, repository

        @localized_attributes = []
        @default_attributes   = []
        @associations         = []

        @entity_map           = {}

        instance_eval(&block) if block_given?
      end

      def localized_attributes(*args)
        @localized_attributes += [*args]

        @localized_attributes_hash = @localized_attributes.inject({}) do |hash, attribute|
          hash[attribute.to_sym] = true; hash
        end

        @localized_attributes
      end

      def default_attribute(name, value)
        @default_attributes += [[name.to_sym, value]]
      end

      ASSOCIATION_CLASSES.each do |type, _|
        define_method("#{type}_association") do |name, repository_klass, options = nil, &block|
          association(type, name, repository_klass, options, &block)
        end
      end

      def association(type, name, repository_klass, options = nil, &block)
        @associations << [type, name.to_sym, repository_klass, options || {}, block]
      end

      def to_entity(attributes)
        cache_entity(entity_klass, attributes) do
          entity_klass.new(deserialize(attributes)).tap do |entity|
            set_default_attributes(entity)

            entity.localized_attributes = @localized_attributes_hash || {}
            entity.associations = {}

            attach_entity_to_associations(entity)

            entity.base_url = @repository.base_url(entity)
          end
        end
      end

      def deserialize(attributes)
        enhanced_attributes = attributes.with_indifferent_access
        build_localized_attributes(enhanced_attributes)
        build_associations(enhanced_attributes)
        enhanced_attributes
      end

      def serialize(entity)
        entity.serialize.tap do |attributes|
          # scope
          @repository.scope.apply(attributes)

          # localized fields
          @localized_attributes.each do |name|
            # hack: force the name for select type fields (content entries only)
            entity.send(name).serialize(attributes, name) 
          end

          # association name -> id (belongs_to) or ids (many_to_many)
          (entity.associations || {}).each do |name, association|
            association.__serialize__(attributes)
          end
        end
      end

      def entity_klass
        options[:entity]
      end

      def i18n_value_of(entity, name, locale)
        value = entity.send(name.to_sym)
        (value.respond_to?(:translations) ? value[locale] : value)
      end

      def reset_entity_map
        @entity_map = {}
      end

      private

      # create a proxy class for each localized attribute
      def build_localized_attributes(attributes)
        @localized_attributes.each do |name|
          _name = name.to_sym
          attributes[_name] = I18nField.new(_name, attributes[name.to_s] || attributes[_name])
        end
      end

      # create a proxy class for each association
      def build_associations(attributes)
        @associations.each do |(type, name, repository_klass, options, block)|
          klass = ASSOCIATION_CLASSES[type]

          _options = options.merge(association_name: name, mapper_name: self.name)

          attributes[name] = (if type == :embedded
            klass.new(repository_klass, attributes[name], @repository.scope, _options)
          else
            klass.new(repository_klass, @repository.scope, @repository.adapter, _options, &block)
          end)
        end
      end

      def attach_entity_to_associations(entity)
        @associations.each do |(type, name, _)|
          association = entity[name]
          association.__attach__(entity)

          entity.associations[name] = association
        end
      end

      def set_default_attributes(entity)
        @default_attributes.each do |(name, value)|
          _value = value.respond_to?(:call) ? value.call(@repository) : value
          entity.send(:"#{name}=", _value)
        end
      end

      def cache_entity(entity_klass, attributes, &block)
        entity_id = attributes['_id'] || attributes[:_id] # FIXME: in Wagon, we deal with symbols

        return yield if entity_id.blank?

        key = "#{entity_klass.to_s}-#{entity_id}"

        if (entity = @entity_map[key]).nil?
          entity = @entity_map[key] = yield
        end

        entity
      end

    end

  end
end