SpontaneousCMS/spontaneous

View on GitHub
lib/spontaneous/model/core/aliases.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# encoding: UTF-8


module Spontaneous::Model::Core
  module Aliases
    extend Spontaneous::Concern

    module ClassMethods
      def alias_of(*args)
        options_list, class_list = args.partition { |e| e.is_a?(Hash) }
        @alias_options = options_list.first || {}
        @alias_classes = class_list
        extend  ClassAliasMethods
        include AliasMethods
        include PieceAliasMethods unless page?
        include PageAliasMethods  if page?
        # alias_method :target,  :__target
        alias_method :target=, :__target=
      end


      alias_method :aliases, :alias_of

      def targets(owner = nil, box = nil, options = {})
        targets = []
        classes = []
        proc_args = [owner, box].compact
        @alias_classes.each do |source|
          case source
          when Proc
            targets.concat(Array(source[*proc_args]))
          else
            type = source.to_s.constantize
            classes.push(type)
            classes.concat(type.subclasses)
          end
        end

        query = content_model.filter(type_sid: classes.map { |c| c.schema_id })

        if (container_procs = @alias_options[:container])
          containers = [container_procs].flatten.map { |p| p[*proc_args] }.flatten
          params = []
          containers.each do |container|
            if container.is_page?
              params << Sequel::SQL::BooleanExpression.new(:'=', :page_id, container.id)
            else
              box_params = [
                [:box_sid, container.schema_id],
                [:owner_id, container.owner.id]
              ]
              params << Sequel::SQL::BooleanExpression.from_value_pairs(box_params, :AND)
            end
          end
          container_query = params[1..-1].inject(params.first) { |q, expr| q = q | expr; q }
          query = query.and(container_query)
        end
        targets.concat(query.all)

        if (filter = @alias_options[:filter]) and filter.is_a?(Proc)
          filtered = []
          targets.each { |target|
            filtered << target if filter[*([target, owner, box][0...(filter.arity)])]
          }
          targets = filtered
        end

        if @alias_options[:unique] && box
          existing = box.map { |entry| entry.target || entry }
          targets.reject! { |target| existing.include?(target) }
        end

        if (query =  options[:search])
          targets.select! { |target| query === target.alias_title }
        end
        targets
      end

      def target_class(class_definition)
      end

      def alias?
        false
      end
    end # ClassMethods


    module ClassAliasMethods
      def for_target(target_id)
        new(target_id: target_id)
      end

      def alias?
        true
      end

      def use_configured_generator(generator_name, *args)
        return nil unless @alias_options.key?(generator_name)
        (_generator = @alias_options[generator_name]).call(*args)
      end

      def lookup_target(target_id)
        use_configured_generator(:lookup, target_id)
      end

      def target_slug(target)
        use_configured_generator(:slug, target)
      end
    end

    # included only in instances that are aliases
    module AliasMethods
      # If we're being created as an alias to a hidden target then we should be
      # born hidden and have our visibility linked to our target.
      def before_create
        set_visible(false, target.hidden_origin || target.id) if target && target.hidden?
        super
      end

      def alias?
        true
      end

      def target
        (_target = self.class.lookup_target(self.target_id)) and return _target
        __target
      end

      def fields
        @field_set ||= Spontaneous::Collections::FieldSet.new(self, field_store, target, :fields)
      end

      def field?(field_name)
        super || (target && target.class.field?(field_name))
      end

      def respond_to_missing?(name, include_private = false)
        (target && target.respond_to?(name, include_private)) || super
      end

      def respond_to?(name, include_private = false)
        super || respond_to_missing?(name, include_private)
      end

      def method_missing(method, *args)
        if target && respond_to_missing?(method)
          if block_given?
            target.__send__(method, *args, &Proc.new)
          else
            target.__send__(method, *args)
          end
        else
          super
        end
      end

      def find_named_style(style_name)
        super or (target && target.find_named_style(style_name))
      end

      # Aliases are unique in that their style depends on the instance as well
      # as the class.
      def style
        Spontaneous::Style::AliasStyle.new(self)
      end

      def styles
        @styles ||= Spontaneous::Collections::PrototypeSet.new(target, :styles)
      end

      def shallow_export(user = nil)
        super.merge({
          target: exported_target(user),
          alias_title: target.alias_title,
          alias_icon: target.exported_alias_icon
        })
      end

      def exported_target(user = nil)
        case target
        when content_model
          target.shallow_export(user)
        else
          target.to_json
        end
      end

      def hidden?
        super || target.nil?
      end
    end

    module PieceAliasMethods
      def path
        target.path
      end
    end

    module PageAliasMethods
      def slug
        return super if target.nil?
        unless target.respond_to?(:slug)
          slug = self.class.target_slug(target) and return slug
        end
        target.slug
      end

      def layout
        # if this alias class has no layouts defined, then just use the one set on the target
        if self.class.layouts.empty? && !target.nil?
          target.resolve_layout(layout_sid)
        else
          # but if it does have layouts defined, use them
          resolve_layout(layout_sid) or target.try(:resolve_layout, layout_sid)
        end
      end

      def find_named_layout(layout_name)
        super or target.try(:find_named_layout, layout_name)
      end
    end

    # InstanceMethods

    # Used as the title for this instance in the list of potential targets for
    # and alias.
    def alias_title
      alias_title_field.to_s
    end

    def alias_title_field
      unless (field = fields[:title])
        field = fields.detect { |f| f.is_a?(Spontaneous::Field::String) }
      end
      field
    end

    def alias_icon_field
      if (field = fields.detect { |f| f.image? })
        field
      else
        nil
      end
    end

    def exported_alias_icon
      return nil unless alias_icon_field
      alias_icon_field.export
    end
  end
end