wrstudios/odata4

View on GitHub
lib/odata4/entity_set.rb

Summary

Maintainability
A
25 mins
Test Coverage
module OData4
  # This class represents a set of entities within an OData4 service. It is
  # instantiated whenever an OData4::Service is asked for an EntitySet via the
  # OData4::Service#[] method call. It also provides Enumerable behavior so that
  # you can interact with the entities within a set in a very comfortable way.
  #
  # This class also implements a query interface for finding certain entities
  # based on query criteria or limiting the result set returned by the set. This
  # functionality is implemented through transparent proxy objects.
  class EntitySet
    include Enumerable

    # The name of the EntitySet
    attr_reader :name
    # The Entity type for the EntitySet
    attr_reader :type
    # The OData4::Service's namespace
    attr_reader :namespace
    # The OData4::Service's identifiable name
    attr_reader :service_name
    # The EntitySet's container name
    attr_reader :container

    # Sets up the EntitySet to permit querying for the resources in the set.
    #
    # @param options [Hash] the options to setup the EntitySet
    # @return [OData4::EntitySet] an instance of the EntitySet
    def initialize(options = {})
      @name         = options[:name]
      @type         = options[:type]
      @namespace    = options[:namespace]
      @service_name = options[:service_name]
      @container    = options[:container]
    end

    # Provided for Enumerable functionality
    #
    # @param block [block] a block to evaluate
    # @return [OData4::Entity] each entity in turn from this set
    def each(&block)
      query.execute.each(&block)
    end

    # Return the first `n` Entities for the set.
    # If count is 1 it returns the single entity, otherwise its an array of entities
    # @return [OData4::EntitySet]
    def first(count = 1)
      result = query.limit(count).execute
      count == 1 ? result.first : result.to_a
    end

    # Returns the number of entities within the set.
    # Not supported in Microsoft CRM2011
    # @return [Integer]
    def count
      query.count
    end

    # Create a new Entity for this set with the given properties.
    # @param properties [Hash] property name as key and it's initial value
    # @return [OData4::Entity]
    def new_entity(properties = {})
      OData4::Entity.with_properties(properties, entity_options)
    end

    # Returns a query targetted at the current EntitySet.
    # @param options [Hash] query options
    # @return [OData4::Query]
    def query(options = {})
      OData4::Query.new(self, options)
    end

    # Find the Entity with the supplied key value.
    # @param key [to_s] primary key to lookup
    # @return [OData4::Entity,nil]
    def [](key, options={})
      properties_to_expand = if options[:expand] == :all
        new_entity.navigation_property_names
      else
        [ options[:expand] ].compact.flatten
      end

      query.expand(*properties_to_expand).find(key)
    end

    # Write supplied entity back to the service.
    # TODO Test this more with CRM2011
    # @param entity [OData4::Entity] entity to save or update in the service
    # @return [OData4::Entity]
    def <<(entity)
      url_chunk, options = setup_entity_post_request(entity)
      result = execute_entity_post_request(options, url_chunk)
      if entity.is_new?
        doc = ::Nokogiri::XML(result.body).remove_namespaces!
        primary_key_node = doc.xpath("//content/properties/#{entity.primary_key}").first
        entity[entity.primary_key] = primary_key_node.content unless primary_key_node.nil?
      end

      unless result.code.to_s =~ /^2[0-9][0-9]$/
        entity.errors << ['could not commit entity']
      end

      entity
    end

    # The OData4::Service this EntitySet is associated with.
    # @return [OData4::Service]
    # @api private
    def service
      @service ||= OData4::ServiceRegistry[service_name]
    end

    # Options used for instantiating a new OData4::Entity for this set.
    # @return [Hash]
    # @api private
    def entity_options
      {
        service_name: service_name,
        type:         type,
        entity_set:   self
      }
    end

    private

    def execute_entity_post_request(options, url_chunk)
      result = service.execute(url_chunk, options)
      unless result.code.to_s =~ /^2[0-9][0-9]$/
        service.logger.debug <<-EOS
          [ODATA: #{service_name}]
          An error was encountered committing your entity:

            POSTed URL:
            #{url_chunk}

            POSTed Entity:
            #{options[:body]}

            Result Body:
            #{result.body}
        EOS
        service.logger.info "[ODATA: #{service_name}] Unable to commit data to #{url_chunk}"
      end
      result
    end

    def setup_entity_post_request(entity)
      primary_key = entity.get_property(entity.primary_key).url_value
      chunk = entity.is_new? ? name : "#{name}(#{primary_key})"
      options = {
          method: :post,
          body: entity.to_xml.gsub(/\n\s+/, ''),
          headers: {
              'Accept' => 'application/atom+xml',
              'Content-Type' => 'application/atom+xml'
          }
      }
      return chunk, options
    end
  end
end