47colborne/cloud-sesame

View on GitHub
lib/cloud_sesame/domain/base.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module CloudSesame
  module Domain
    class Base
      extend Forwardable

      def_delegators :client, :config, :caching_with

      attr_reader :searchable

      def self.definitions
        @definitions ||= {}
      end

      def initialize(searchable)
        @searchable = searchable
        @builder = Query::Builder.construct_class(searchable)
      end

      def builder
        @builder.new context, searchable
      end

      def client
        @client ||= Client.new searchable
      end

      def context
        @context ||= Context.new
      end

      # DEFAULT CONTEXT METHODS
      # =========================================

      def default_size(value)
        context_page[:size] = value.to_i
      end

      def define_sloppiness(value)
        context_query[:sloppiness] = Query::Node::Sloppiness.new(value)
      end

      def define_fuzziness(&block)
        context_query[:fuzziness] = Query::Node::Fuzziness.new(&block)
      end

      def field(name, options = {})
        field_name = (options[:as] || name).to_sym

        add_sort_alias(name, options) if options[:as]
        add_query_options(field_name, options) if options[:query]
        add_facet(field_name, options) if options[:facet]
        add_field(name, options)
      end

      def scope(name, proc = nil, &block)
        block = proc unless block_given?
        filter_query_scopes[name.to_sym] = block if block
      end

      private

      def add_sort_alias(name, options)
        context_sort_fields[name] = { as: options[:as] }
      end

      def add_query_options(name, options)
        context_query_options[name] = to_hash(options.delete(:query))
      end

      def add_facet(name, options)
        context_facet[name] = to_hash(options.delete(:facet))
      end

      def add_field(name, options)
        options = merge_with_as_field options if options[:as]
        options[:type] = set_type(options[:type])
        create_accessor name
        create_default_literal name, options
        filter_query_fields[name] = options
      end

      def set_type(type)
        Query::AST::Value.map_type(type)
      end

      def merge_with_as_field(options)
        (existing = filter_query_fields.delete(options[:as])) ? existing.merge(options) : options
      end

      def create_accessor(name)
        @builder.literal_methods.__define_accessor__(name)
      end

      def create_default_literal(name, options)
        if (block = options.delete(:default))
          caller = block.binding.eval "self"
          node = Query::Domain::Literal.new(name, options, caller)._eval(&block)
          filter_query_defaults << node if node
        end
      end

      def to_hash(options)
        options.is_a?(Hash) ? options : {}
      end

      def method_missing(name, *args, &block)
        if builder.respond_to?(name, true) || filter_query_scopes[name]
          builder.send(name, *args, &block)
        elsif searchable.respond_to?(name, true)
         searchable.send(name, *args, &block)
        else
          super
        end
      end

      # CONTEXT INITIALIZERS
      # =========================================

      def context_page
        context[:page] ||= {}
      end

      def context_query
        context[:query] ||= {}
      end

      def context_query_options
        (context[:query_options] ||= {})[:fields] ||= {}
      end

      def context_facet
        context[:facet] ||= {}
      end

      def context_sort_fields
        (context[:sort] ||= {})[:fields] ||= {}
      end

      def filter_query_fields
        (context[:filter_query] ||= {})[:fields] ||= {}
      end

      def filter_query_defaults
        (context[:filter_query] ||= {})[:defaults] ||= []
      end

      def filter_query_scopes
        (context[:filter_query] ||= {})[:scopes] ||= {}
      end

    end
  end
end