wrstudios/frodata

View on GitHub
lib/frodata/query.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'frodata/query/criteria'
require 'frodata/query/in_batches'

module FrOData
  # FrOData::Query provides the query interface for requesting Entities matching
  # specific criteria from an FrOData::EntitySet. This class should not be
  # instantiated directly, but can be. Normally you will access a Query by
  # first asking for one from the FrOData::EntitySet you want to query.
  class Query
    attr_reader :options

    include InBatches

    # Create a new Query for the provided EntitySet
    # @param entity_set [FrOData::EntitySet]
    # @param options [Hash] Query options
    def initialize(entity_set, options = {})
      @entity_set = entity_set
      @options    = options
      setup_empty_criteria_set
    end

    # Instantiates an FrOData::Query::Criteria for the named property.
    # @param property [to_s]
    def [](property)
      property_instance = @entity_set.new_entity.get_property(property)
      property_instance = property if property_instance.nil?
      FrOData::Query::Criteria.new(property: property_instance)
    end

    # Find the Entity with the supplied key value.
    # @param key [to_s] primary key to lookup
    # @return [FrOData::Entity,nil]
    def find(key)
      entity = @entity_set.new_entity
      key_property = entity.get_property(entity.primary_key)
      key_property.value = key

      pathname = "#{entity_set.name}(#{key_property.url_value})"
      query = [pathname, assemble_criteria].compact.join('?')
      execute(query).first
    end

    # Adds a filter criteria to the query.
    # For filter syntax see https://msdn.microsoft.com/en-us/library/gg309461.aspx
    # Syntax:
    #   Property Operator Value
    #
    # For example:
    #   Name eq 'Customer Service'
    #
    # Operators:
    # eq, ne, gt, ge, lt, le, and, or, not
    #
    # Value
    #  can be 'null', can use single quotes
    # @param criteria
    def where(criteria)
      criteria_set[:filter] << criteria
      self
    end

    # Adds a fulltext search term to the query
    # NOTE: May not be implemented by the service
    # @param term [String]
    def search(term)
      criteria_set[:search] << term
      self
    end

    # Adds a filter criteria to the query with 'and' logical operator.
    # @param criteria
    #def and(criteria)
    #
    #end

    # Adds a filter criteria to the query with 'or' logical operator.
    # @param criteria
    #def or(criteria)
    #
    #end

    # Specify properties to order the result by.
    # Can use 'desc' like 'Name desc'
    # @param properties [Array<Symbol>]
    # @return [self]
    def order_by(*properties)
      criteria_set[:orderby] += properties
      self
    end

    # Specify associations to expand in the result.
    # @param associations [Array<Symbol>]
    # @return [self]
    def expand(*associations)
      criteria_set[:expand] += associations
      self
    end

    # Specify properties to select within the result.
    # @param properties [Array<Symbol>]
    # @return [self]
    def select(*properties)
      criteria_set[:select] += properties
      self
    end

    # Add skip criteria to query.
    # @param value [to_i]
    # @return [self]
    def skip(value)
      criteria_set[:skip] = value.to_i
      self
    end

    # Add limit criteria to query.
    # @param value [to_i]
    # @return [self]
    def limit(value)
      criteria_set[:top] = value.to_i
      self
    end

    # Add inline count criteria to query.
    # Not Supported in CRM2011
    # @return [self]
    def include_count
      criteria_set[:inline_count] = true
      self
    end

    # Convert Query to string.
    # @return [String]
    def to_s
      criteria = params.map { |k, v| "#{k}=#{v}" }.join('&')
      [entity_set.name, params.any? ? criteria : nil].compact.join('?')
    end

    # Execute the query.
    # @return [FrOData::Service::Response]
    def execute(url_chunk = entity_set.name, params = assemble_criteria)
      service.execute(url_chunk, options.merge(query: self, params: params))
    end

    # Executes the query to get a count of entities.
    # @return [Integer]
    def count
      response = self.execute("#{entity_set.name}/$count")
      # Some servers (*cough* Microsoft *cough*) seem to
      # return extraneous characters in the response.
      response.body.scan(/\d+/).first.to_i
    end

    # Checks whether a query will return any results by calling #count
    # @return [Boolean]
    def empty?
      self.count == 0
    end

    # The EntitySet for this query.
    # @return [FrOData::EntitySet]
    # @api private
    def entity_set
      @entity_set
    end

    # The parameter hash for this query.
    # @return [Hash] Params hash
    def params
      assemble_criteria || {}
    end

    # The service for this query
    # @return [FrOData::Service]
    # @api private
    def service
      @service ||= entity_set.service
    end

    private

    attr_reader :criteria_set

    def setup_empty_criteria_set
      @criteria_set = {
        filter:       [],
        search:       [],
        select:       [],
        expand:       [],
        orderby:      [],
        skip:         0,
        top:          0,
        inline_count: false
      }
    end

    def assemble_criteria
      [
        filter_criteria,
        search_criteria,
        list_criteria(:orderby),
        list_criteria(:expand),
        list_criteria(:select),
        inline_count_criteria,
        paging_criteria(:skip),
        paging_criteria(:top)
      ].compact.reduce(&:merge)
    end

    def filter_criteria
      return nil if criteria_set[:filter].empty?
      filters = criteria_set[:filter].collect(&:to_s)
      { '$filter' => filters.join(' and ') }
    end

    def search_criteria
      return nil if criteria_set[:search].empty?
      filters = criteria_set[:search].collect(&:to_s)
      { '$search' => filters.join(' AND ') }
    end

    def list_criteria(name)
      return nil if criteria_set[name].empty?
      { "$#{name}" => criteria_set[name].join(',') }
    end

    # inlinecount not supported by Microsoft CRM 2011
    def inline_count_criteria
      criteria_set[:inline_count] ? { '$count' => 'true' } : nil
    end

    def paging_criteria(name)
      criteria_set[name] == 0 ? nil : { "$#{name}" => criteria_set[name] }
    end
  end
end