lib/odata4/entity_set.rb
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