class Frodo::Entity

An Frodo::Entity represents a single record returned by the service. All Entities have a type and belong to a specific namespace. They are written back to the service via the EntitySet they came from. Frodo::Entity instances should not be instantiated directly; instead, they should either be read or instantiated from their respective Frodo::EntitySet.

Constants

PROPERTY_NOT_LOADED
XML_NAMESPACES

Attributes

entity_set[R]

The entity set this entity belongs to

errors[R]

List of errors on entity

service_name[R]

The Frodo::Service's identifying name

type[R]

The Entity type name

Public Class Methods

from_json(json, options = {}) click to toggle source

Create Entity from JSON document with provided options. @param json [Hash|to_s] @param options [Hash] @return [Frodo::Entity]

# File lib/frodo/entity.rb, line 152
def self.from_json(json, options = {})
  return nil if json.nil?
  json = JSON.parse(json.to_s) unless json.is_a?(Hash)
  metadata = extract_metadata(json)
  options.merge!(context: metadata['@odata.context'])

  entity = with_properties(json, options)
  process_metadata(entity, metadata)
  entity
end
from_xml(xml_doc, options = {}) click to toggle source

Create Entity from XML document with provided options. @param xml_doc [Nokogiri::XML] @param options [Hash] @return [Frodo::Entity]

# File lib/frodo/entity.rb, line 167
def self.from_xml(xml_doc, options = {})
  return nil if xml_doc.nil?
  entity = Frodo::Entity.new(options)
  process_properties(entity, xml_doc)
  process_links(entity, xml_doc)
  entity
end
new(options = {}) click to toggle source

Initializes a bare Entity @param options [Hash]

# File lib/frodo/entity.rb, line 29
def initialize(options = {})
  @id = options[:id]
  @type = options[:type]
  @service_name = options[:service_name]
  @entity_set = options[:entity_set]
  @context = options[:context]
  @links = options[:links]
  @errors = []
end
with_properties(new_properties = {}, options = {}) click to toggle source

Create Entity with provided properties and options. @param new_properties [Hash] @param options [Hash] @param [Frodo::Entity]

# File lib/frodo/entity.rb, line 132
def self.with_properties(new_properties = {}, options = {})
  entity = Frodo::Entity.new(options)
  entity.instance_eval do
    service.properties_for_entity(type).each do |property_name, instance|
      set_property(property_name, instance)
    end

    new_properties.each do |property_name, property_value|
      prop_name, annotation = parse_annotations_from_property_name(property_name)
      # TODO: Do something with the annotation?
      self[prop_name] = property_value
    end
  end
  entity
end

Private Class Methods

extract_metadata(json) click to toggle source
# File lib/frodo/entity.rb, line 320
def self.extract_metadata(json)
  metadata = json.select { |key, val| key =~ /@odata/ }
  json.delete_if { |key, val| key =~ /@odata/ }
  metadata
end
process_metadata(entity, metadata) click to toggle source
# File lib/frodo/entity.rb, line 326
def self.process_metadata(entity, metadata)
  entity.instance_eval do
    new_links = instance_variable_get(:@links) || {}
    schema.navigation_properties[name].each do |nav_name, details|
      href = metadata["#{nav_name}@odata.navigationLink"]
      next if href.nil?
      new_links[nav_name] = {
        type: details.nav_type,
        href: href
      }
    end
    instance_variable_set(:@links, new_links) unless new_links.empty?
  end
end
process_properties(entity, xml_doc) click to toggle source
# File lib/frodo/entity.rb, line 288
def self.process_properties(entity, xml_doc)
  entity.instance_eval do
    unless instance_variable_get(:@context)
      context = xml_doc.xpath('/entry').first.andand['context']
      instance_variable_set(:@context, context)
    end

    xml_doc.xpath('./content/properties/*').each do |property_xml|
      # Doing lazy loading here because instantiating each object takes a long time
      set_property_lazy_load(property_xml.name, property_xml)
    end
  end
end

Public Instance Methods

[](property_name) click to toggle source

Get property value @param property_name [to_s] @return [*]

# File lib/frodo/entity.rb, line 58
def [](property_name)
  if get_property(property_name).is_a?(::Frodo::Properties::Complex)
    get_property(property_name)
  else
    get_property(property_name).value
  end
end
[]=(property_name, value) click to toggle source

Set property value @param property_name [to_s] @param value [*]

# File lib/frodo/entity.rb, line 69
def []=(property_name, value)
  get_property(property_name).value = value
end
any_errors?() click to toggle source
# File lib/frodo/entity.rb, line 233
def any_errors?
  !errors.empty?
end
context() click to toggle source

Returns context URL for this entity @return [String]

# File lib/frodo/entity.rb, line 51
def context
  @context ||= context_url
end
get_property(property_name) click to toggle source
# File lib/frodo/entity.rb, line 73
def get_property(property_name)
  prop_name = property_name.to_s
  # Property is lazy loaded
  if properties_xml_value.has_key?(prop_name)
    property = instantiate_property(prop_name, properties_xml_value[prop_name])
    set_property(prop_name, property.dup)
    properties_xml_value.delete(prop_name)
  end

  if properties.has_key? prop_name
    properties[prop_name]
  elsif navigation_properties.has_key? prop_name
    navigation_properties[prop_name]
  else
    raise ArgumentError, "Unknown property: #{property_name}"
  end
end
id() click to toggle source

Returns the canonical URL for this entity @return [String]

# File lib/frodo/entity.rb, line 215
def id
  @id ||= lambda {
    entity_set = self.entity_set.andand.name
    entity_set ||= context.split('#').last.split('/').first
    "#{entity_set}(#{self[primary_key]})"
  }.call
end
is_new?() click to toggle source
# File lib/frodo/entity.rb, line 229
def is_new?
  self[primary_key].nil?
end
name() click to toggle source

Returns name of Entity from Service specified type. @return [String]

# File lib/frodo/entity.rb, line 45
def name
  @name ||= type.split('.').last
end
namespace() click to toggle source
# File lib/frodo/entity.rb, line 39
def namespace
  @namespace ||= type.rpartition('.').first
end
navigation_properties() click to toggle source
navigation_property_names() click to toggle source
parse_annotations_from_property_name(property_name) click to toggle source

strip inline annotations from property names and return separately

# File lib/frodo/entity.rb, line 92
def parse_annotations_from_property_name(property_name)
  prop_name, annotation = property_name.to_s.split('@', 2)
  return prop_name, annotation
end
primary_key() click to toggle source

Returns the primary key for the Entity. @return [String]

# File lib/frodo/entity.rb, line 225
def primary_key
  schema.primary_key_for(name)
end
property_names() click to toggle source
# File lib/frodo/entity.rb, line 97
def property_names
  [
    @properties_xml_value.andand.keys,
    @properties.andand.keys
  ].compact.flatten
end
schema() click to toggle source
# File lib/frodo/entity.rb, line 241
def schema
  @schema ||= service.schemas[namespace]
end
service() click to toggle source
# File lib/frodo/entity.rb, line 237
def service
  @service ||= Frodo::ServiceRegistry[service_name]
end
to_hash() click to toggle source

Converts Entity to a hash. @return [Hash]

# File lib/frodo/entity.rb, line 207
def to_hash
  property_names.map do |name|
    [name, get_property(name).json_value]
  end.to_h
end
to_json() click to toggle source

Converts Entity to its JSON representation. @return [String]

# File lib/frodo/entity.rb, line 200
def to_json
  # TODO: add @odata.context
  to_hash.to_json
end
to_xml() click to toggle source

Converts Entity to its XML representation. @return [String]

# File lib/frodo/entity.rb, line 177
def to_xml
  namespaces = XML_NAMESPACES.merge('xml:base' => service.service_url)
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.entry(namespaces) do
      xml.category(term: type,
                   scheme: 'http://docs.oasis-open.org/odata/ns/scheme')
      xml.author { xml.name }

      xml.content(type: 'application/xml') do
        xml['metadata'].properties do
          property_names.each do |name|
            next if name == primary_key
            get_property(name).to_xml(xml)
          end
        end
      end
    end
  end
  builder.to_xml
end

Private Instance Methods

context_url() click to toggle source

Computes the entity's canonical context URL

# File lib/frodo/entity.rb, line 275
def context_url
  "#{service.service_url}/$metadata##{entity_set.name}/$entity"
end
instantiate_property(property_name, value_xml) click to toggle source
# File lib/frodo/entity.rb, line 247
def instantiate_property(property_name, value_xml)
  prop_type = schema.get_property_type(name, property_name)
  prop_type, value_type = prop_type.split(/\(|\)/)

  if prop_type == 'Collection'
    klass = ::Frodo::Properties::Collection
    options = { value_type: value_type }
  else
    klass = ::Frodo::PropertyRegistry[prop_type]
    options = {}
  end

  if klass.nil?
    raise RuntimeError, "Unknown property type: #{prop_type}"
  else
    klass.from_xml(value_xml, options.merge(service: service))
  end
end
properties() click to toggle source
# File lib/frodo/entity.rb, line 266
def properties
  @properties ||= {}
end
properties_xml_value() click to toggle source
# File lib/frodo/entity.rb, line 270
def properties_xml_value
  @properties_xml_value ||= {}
end
set_property(name, property) click to toggle source
# File lib/frodo/entity.rb, line 279
def set_property(name, property)
  properties[name.to_s] = property
end
set_property_lazy_load(name, xml_value ) click to toggle source

Instantiating properties takes time, so we can lazy load properties by passing xml_value and lookup when needed

# File lib/frodo/entity.rb, line 284
def set_property_lazy_load(name, xml_value )
  properties_xml_value[name.to_s] = xml_value
end