class OData::Service

The main service class, also known as a Context

Attributes

class_metadata[R]
classes[R]
collections[R]
edmx[R]
function_imports[R]
options[R]
response[R]

Public Class Methods

new(service_uri, options = {}) click to toggle source

Creates a new instance of the Service class

@param [String] service_uri the root URI of the OData service @param [Hash] options the options to pass to the service @option options [String] :username for http basic auth @option options [String] :password for http basic auth @option options [Object] :verify_ssl false if no verification, otherwise mode (OpenSSL::SSL::VERIFY_PEER is default) @option options [Hash] :rest_options a hash of rest-client options that will be passed to all RestClient::Resource.new calls @option options [Hash] :additional_params a hash of query string params that will be passed on all calls @option options [Boolean, true] :eager_partial true if queries should consume partial feeds until the feed is complete, false if explicit calls to next must be performed

# File lib/ruby_odata/service.rb, line 15
def initialize(service_uri, options = {})
  @uri = service_uri.gsub!(/\/?$/, '')
  set_options! options
  default_instance_vars!
  set_namespaces
  build_collections_and_classes
end

Public Instance Methods

delete_object(obj) click to toggle source

Queues an object for deletion. To actually remove it from the server, you must call save_changes as well.

@param [Object] obj the object to mark for deletion

@raise [NotSupportedError] if the `obj` isn't a tracked entity

# File lib/ruby_odata/service.rb, line 49
def delete_object(obj)
  type = obj.class.to_s
  if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
    @save_operations << Operation.new("Delete", type, obj)
  else
    raise OData::NotSupportedError.new "You cannot delete a non-tracked entity"
  end
end
execute() click to toggle source

Performs query operations (Read) against the server. Typically this returns an array of record instances, except in the case of count queries @raise [ServiceError] if there is an error when talking to the service

# File lib/ruby_odata/service.rb, line 98
def execute
  begin
    @response = RestClient::Resource.new(build_query_uri, @rest_options).get
  rescue Exception => e
    handle_exception(e)
  end
  return Integer(@response) if @response =~ /^\d+$/
  handle_collection_result(@response)
end
load_property(obj, nav_prop) click to toggle source

Lazy loads a navigation property on a model

@param [Object] obj the object to fill @param [String] nav_prop the navigation property to fill

@raise [NotSupportedError] if the `obj` isn't a tracked entity @raise [ArgumentError] if the `nav_prop` isn't a valid navigation property

# File lib/ruby_odata/service.rb, line 146
def load_property(obj, nav_prop)
  raise NotSupportedError, "You cannot load a property on an entity that isn't tracked" if obj.send(:__metadata).nil?
  raise ArgumentError, "'#{nav_prop}' is not a valid navigation property" unless obj.respond_to?(nav_prop.to_sym)
  raise ArgumentError, "'#{nav_prop}' is not a valid navigation property" unless @class_metadata[obj.class.to_s][nav_prop].nav_prop
  results = RestClient::Resource.new(build_load_property_uri(obj, nav_prop), @rest_options).get
  prop_results = build_classes_from_result(results)
  obj.send "#{nav_prop}=", (singular?(nav_prop) ? prop_results.first : prop_results)
end
method_missing(name, *args) click to toggle source

Handles the dynamic `AddTo<EntityName>` methods as well as the collections on the service

Calls superclass method
# File lib/ruby_odata/service.rb, line 24
def method_missing(name, *args)
  # Queries
  if @collections.include?(name.to_s)
    @query = build_collection_query_object(name,@additional_params, *args)
    return @query
  # Adds
  elsif name.to_s =~ /^AddTo(.*)/
    type = $1
    if @collections.include?(type)
      @save_operations << Operation.new("Add", $1, args[0])
    else
      super
    end
  elsif @function_imports.include?(name.to_s)
    execute_import_function(name.to_s, args)
  else
    super
  end
end
next() click to toggle source

Retrieves the next resultset of a partial result (if any). Does not honor the `:eager_partial` option.

# File lib/ruby_odata/service.rb, line 129
def next
  return if not partial?
  handle_partial
end
partial?() click to toggle source

Does the most recent collection returned represent a partial collection? Will aways be false if a query hasn't executed, even if the query would have a partial

# File lib/ruby_odata/service.rb, line 135
def partial?
  @has_partial
end
respond_to?(method) click to toggle source

Overridden to identify methods handled by method_missing

Calls superclass method
# File lib/ruby_odata/service.rb, line 109
def respond_to?(method)
  if @collections.include?(method.to_s)
    return true
  # Adds
  elsif method.to_s =~ /^AddTo(.*)/
    type = $1
    if @collections.include?(type)
      return true
    else
      super
    end
  # Function Imports
  elsif @function_imports.include?(method.to_s)
    return true
  else
    super
  end
end
save_changes() click to toggle source

Performs save operations (Create/Update/Delete) against the server

# File lib/ruby_odata/service.rb, line 73
def save_changes
  return nil if @save_operations.empty?

  result = nil

  begin
    if @save_operations.length == 1
      result = single_save(@save_operations[0])
    else
      result = batch_save(@save_operations)
    end

    # TODO: We should probably perform a check here
    # to make sure everything worked before clearing it out
    @save_operations.clear

    return result
  rescue Exception => e
    handle_exception(e)
  end
end
update_object(obj) click to toggle source

Queues an object for update. To actually update it on the server, you must call save_changes as well.

@param [Object] obj the object to queue for update

@raise [NotSupportedError] if the `obj` isn't a tracked entity

# File lib/ruby_odata/service.rb, line 63
def update_object(obj)
  type = obj.class.to_s
  if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
    @save_operations << Operation.new("Update", type, obj)
  else
    raise OData::NotSupportedError.new "You cannot update a non-tracked entity"
  end
end

Private Instance Methods

batch_save(operations) click to toggle source
# File lib/ruby_odata/service.rb, line 630
def batch_save(operations)
  batch_num = generate_guid
  changeset_num = generate_guid
  batch_uri = build_batch_uri

  body = build_batch_body(operations, batch_num, changeset_num)
  result = RestClient::Resource.new( batch_uri, @rest_options).post body, {:content_type => "multipart/mixed; boundary=batch_#{batch_num}"}

  # TODO: More result validation needs to be done.
  # The result returns HTTP 202 even if there is an error in the batch
  return (result.code == 202)
end
build_batch_body(operations, batch_num, changeset_num) click to toggle source
# File lib/ruby_odata/service.rb, line 642
def build_batch_body(operations, batch_num, changeset_num)
  # Header
  body = "--batch_#{batch_num}\n"
  body << "Content-Type: multipart/mixed;boundary=changeset_#{changeset_num}\n\n"

  # Operations
  operations.each do |operation|
    body << build_batch_operation(operation, changeset_num)
    body << "\n"
  end

  # Footer
  body << "\n\n--changeset_#{changeset_num}--\n"
  body << "--batch_#{batch_num}--"

  return body
end
build_batch_operation(operation, changeset_num) click to toggle source
# File lib/ruby_odata/service.rb, line 659
def build_batch_operation(operation, changeset_num)
  accept_headers = "Accept-Charset: utf-8\n"
  accept_headers << "Content-Type: application/json;charset=utf-8\n" unless operation.kind == "Delete"
  accept_headers << "\n"

  content = "--changeset_#{changeset_num}\n"
  content << "Content-Type: application/http\n"
  content << "Content-Transfer-Encoding: binary\n\n"

  if operation.kind == "Add"
    save_uri = "#{@uri}/#{operation.klass_name}"
    json_klass = operation.klass.to_json(:type => :add)

    content << "POST #{save_uri} HTTP/1.1\n"
    content << accept_headers
    content << json_klass
  elsif operation.kind == "Update"
    update_uri = operation.klass.send(:__metadata)[:uri]
    json_klass = operation.klass.to_json

    content << "PUT #{update_uri} HTTP/1.1\n"
    content << accept_headers
    content << json_klass
  elsif operation.kind == "Delete"
    delete_uri = operation.klass.send(:__metadata)[:uri]

    content << "DELETE #{delete_uri} HTTP/1.1\n"
    content << accept_headers
  elsif
    save_uri = build_add_link_uri(operation)
    json_klass = operation.child_klass.to_json(:type => :link)

    content << "POST #{save_uri} HTTP/1.1\n"
    content << accept_headers
    content << json_klass
    link_child_to_parent(operation)
  end

  return content
end
build_batch_uri() click to toggle source
# File lib/ruby_odata/service.rb, line 554
def build_batch_uri
  uri = "#{@uri}/$batch"
  uri << "?#{@additional_params.to_query}" unless @additional_params.empty?
  uri
end
build_classes_from_result(result) click to toggle source

Helper to loop through a result and create an instance for each entity in the results

# File lib/ruby_odata/service.rb, line 415
def build_classes_from_result(result)
  doc = Nokogiri::XML(result)

  is_links = doc.at_xpath("/ds:links", @ds_namespaces)
  return parse_link_results(doc) if is_links

  entries = doc.xpath("//atom:entry[not(ancestor::atom:entry)]", @ds_namespaces)

  extract_partial(doc)

  results = []
  entries.each do |entry|
    results << entry_to_class(entry)
  end
  return results
end
build_collection_query_object(name, additional_parameters, *args) click to toggle source

Constructs a QueryBuilder instance for a collection using the arguments provided.

@param [String] name the name of the collection @param [Hash] additional_parameters the additional parameters @param [Array] args the arguments to use for query

# File lib/ruby_odata/service.rb, line 178
def build_collection_query_object(name, additional_parameters, *args)
  root = "/#{name.to_s}"
  if args.empty?
    #nothing to add
  elsif args.size == 1
    if args.first.to_s =~ /\d+/
      id_metadata = find_id_metadata(name.to_s)
      root << build_id_path(args.first, id_metadata)
    else
      root << "(#{args.first})"
    end
  else
    root << "(#{args.join(',')})"
  end
  QueryBuilder.new(root, additional_parameters)
end
build_collections_and_classes() click to toggle source

Build the classes required by the metadata

# File lib/ruby_odata/service.rb, line 262
def build_collections_and_classes
  @classes = Hash.new
  @class_metadata = Hash.new # This is used to store property information about a class

  # Build complex types first, these will be used for entities
  complex_types = @edmx.xpath("//edm:ComplexType", @ds_namespaces) || []
  complex_types.each do |c|
    name = qualify_class_name(c['Name'])
    props = c.xpath(".//edm:Property", @ds_namespaces)
    methods = props.collect { |p| p['Name'] } # Standard Properties
    @classes[name] = ClassBuilder.new(name, methods, [], self, @namespace).build unless @classes.keys.include?(name)
  end

  entity_types = @edmx.xpath("//edm:EntityType", @ds_namespaces)
  entity_types.each do |e|
    next if e['Abstract'] == "true"
    klass_name = qualify_class_name(e['Name'])
    methods = collect_properties(klass_name, e, @edmx)
    nav_props = collect_navigation_properties(klass_name, e, @edmx)
    @classes[klass_name] = ClassBuilder.new(klass_name, methods, nav_props, self, @namespace).build unless @classes.keys.include?(klass_name)
  end

  # Fill in the collections instance variable
  collections = @edmx.xpath("//edm:EntityContainer/edm:EntitySet", @ds_namespaces)
  collections.each do |c|
    entity_type = c["EntityType"]
    @collections[c["Name"]] = { :edmx_type => entity_type, :type => convert_to_local_type(entity_type) }
  end

  build_function_imports
end
build_function_import_uri(name, params) click to toggle source
# File lib/ruby_odata/service.rb, line 564
def build_function_import_uri(name, params)
  uri = "#{@uri}/#{name}"
  params.merge! @additional_params
  uri << "?#{params.to_query}" unless params.empty?
  uri
end
build_function_imports() click to toggle source

Parses the function imports and fills the @function_imports collection

# File lib/ruby_odata/service.rb, line 295
def build_function_imports
  # Fill in the function imports
  functions = @edmx.xpath("//edm:EntityContainer/edm:FunctionImport", @ds_namespaces)
  functions.each do |f|
    http_method_attribute = f.xpath("@m:HttpMethod", @ds_namespaces).first # HttpMethod is no longer required http://www.odata.org/2011/10/actions-in-odata/
    is_side_effecting_attribute = f.xpath("@edm:IsSideEffecting", @ds_namespaces).first

    http_method = 'POST' # default to POST

    if http_method_attribute
      http_method = http_method_attribute.content
    elsif is_side_effecting_attribute
      is_side_effecting = is_side_effecting_attribute.content
      http_method = is_side_effecting ? 'POST' : 'GET'
    end

    return_type = f["ReturnType"]
    inner_return_type = nil
    unless return_type.nil?
      return_type = (return_type =~ /^Collection/) ? Array : convert_to_local_type(return_type)
      if f["ReturnType"] =~ /\((.*)\)/
        inner_return_type = convert_to_local_type($~[1])
      end
    end
    params = f.xpath("edm:Parameter", @ds_namespaces)
    parameters = nil
    if params.length > 0
      parameters = {}
      params.each do |p|
        parameters[p["Name"]] = p["Type"]
      end
    end
    @function_imports[f["Name"]] = {
      :http_method => http_method,
      :return_type => return_type,
      :inner_return_type => inner_return_type,
      :parameters => parameters }
  end
end
build_id_path(id_value, id_metadata) click to toggle source

Builds the ID expression of a given id for query

@param [Object] id_value the actual value to be used @param [PropertyMetadata] id_metadata the property metadata object for the id

# File lib/ruby_odata/service.rb, line 210
def build_id_path(id_value, id_metadata)
  if id_metadata.type == "Edm.Int64"
    "(#{id_value}L)"
  else
    "(#{id_value})"
  end
end
build_inline_class(klass, entry, property_name) click to toggle source
# File lib/ruby_odata/service.rb, line 571
def build_inline_class(klass, entry, property_name)
  # Build the class
  inline_klass = entry_to_class(entry)

  # Add the property
  klass.send "#{property_name}=", inline_klass
end
build_load_property_uri(obj, property) click to toggle source
# File lib/ruby_odata/service.rb, line 559
def build_load_property_uri(obj, property)
  uri = obj.__metadata[:uri].dup
  uri << "/#{property}"
  uri
end
build_metadata_uri() click to toggle source

Build URIs

# File lib/ruby_odata/service.rb, line 530
def build_metadata_uri
  uri = "#{@uri}/$metadata"
  uri << "?#{@additional_params.to_query}" unless @additional_params.empty?
  uri
end
build_property_metadata(props, keys=[]) click to toggle source

Builds the metadata need for each property for things like feed customizations and navigation properties

# File lib/ruby_odata/service.rb, line 353
def build_property_metadata(props, keys=[])
  metadata = {}
  props.each do |property_element|
    prop_meta = PropertyMetadata.new(property_element)
    prop_meta.is_key = keys.include?(prop_meta.name)

    # If this is a navigation property, we need to add the association to the property metadata
    prop_meta.association = Association.new(property_element, @edmx) if prop_meta.nav_prop
    metadata[prop_meta.name] = prop_meta
  end
  metadata
end
build_query_uri() click to toggle source
# File lib/ruby_odata/service.rb, line 535
def build_query_uri
  "#{@uri}#{@query.query}"
end
build_resource_uri(operation) click to toggle source
# File lib/ruby_odata/service.rb, line 549
def build_resource_uri(operation)
  uri = operation.klass.send(:__metadata)[:uri].dup
  uri << "?#{@additional_params.to_query}" unless @additional_params.empty?
  uri
end
build_save_uri(operation) click to toggle source
# File lib/ruby_odata/service.rb, line 538
def build_save_uri(operation)
  uri = "#{@uri}/#{operation.klass_name}"
  uri << "?#{@additional_params.to_query}" unless @additional_params.empty?
  uri
end
collect_navigation_properties(klass_name, element, doc) click to toggle source

Similar to collect_properties, but handles the navigation properties

# File lib/ruby_odata/service.rb, line 408
def collect_navigation_properties(klass_name, element, doc)
  nav_props = element.xpath(".//edm:NavigationProperty", @ds_namespaces)
  @class_metadata[klass_name].merge!(build_property_metadata(nav_props))
  nav_props.collect { |p| p['Name'] }
end
collect_properties(klass_name, element, doc) click to toggle source

Loops through the standard properties (non-navigation) for a given class and returns the appropriate list of methods

# File lib/ruby_odata/service.rb, line 391
def collect_properties(klass_name, element, doc)
  props = element.xpath(".//edm:Property", @ds_namespaces)
  key_elemnts = element.xpath(".//edm:Key//edm:PropertyRef", @ds_namespaces)
  keys = key_elemnts.collect { |k| k['Name'] }
  @class_metadata[klass_name] = build_property_metadata(props, keys)
  methods = props.collect { |p| p['Name'] }
  unless element["BaseType"].nil?
    base = element["BaseType"].split(".").last()
    baseType = doc.xpath("//edm:EntityType[@Name=\"#{base}\"]", @ds_namespaces).first()
    props = baseType.xpath(".//edm:Property", @ds_namespaces)
    @class_metadata[klass_name].merge!(build_property_metadata(props))
    methods = methods.concat(props.collect { |p| p['Name']})
  end
  methods
end
complex_type_to_class(complex_type_xml) click to toggle source

Complex Types

# File lib/ruby_odata/service.rb, line 701
def complex_type_to_class(complex_type_xml)
  type = Helpers.get_namespaced_attribute(complex_type_xml, 'type', 'm')

  is_collection = false
  # Extract the class name in case this is a Collection
  if type =~ /\(([^)]*)\)/m
      type = $~[1]
    is_collection = true
    collection = []
  end

  klass_name = qualify_class_name(type.split('.')[-1])

  if is_collection
    # extract the elements from the collection
    elements = complex_type_xml.xpath(".//d:element", @namespaces)
    elements.each do |e|
      if type.match(/^Edm/)
        collection << parse_value(e.content, type)
      else
        element = @classes[klass_name].new
        fill_complex_type_properties(e, element)
        collection << element
      end
    end
    return collection
  else
    klass = @classes[klass_name].new
    # Fill in the properties
    fill_complex_type_properties(complex_type_xml, klass)
    return klass
  end
end
convert_to_local_type(edmx_type) click to toggle source

Converts the EDMX model type to the local model type

# File lib/ruby_odata/service.rb, line 336
def convert_to_local_type(edmx_type)
  return edm_to_ruby_type(edmx_type) if edmx_type =~ /^Edm/
  klass_name = qualify_class_name(edmx_type.split('.').last)
  klass_name.camelize.constantize
end
default_instance_vars!() click to toggle source
# File lib/ruby_odata/service.rb, line 230
def default_instance_vars!
  @collections = {}
  @function_imports = {}
  @save_operations = []
  @has_partial = false
  @next_uri = nil
end
edm_to_ruby_type(edm_type) click to toggle source

Converts an edm type (string) to a ruby type

# File lib/ruby_odata/service.rb, line 804
def edm_to_ruby_type(edm_type)
  return String if edm_type =~ /Edm.String/
  return Fixnum if edm_type =~ /^Edm.Int/
  return Float if edm_type =~ /Edm.Decimal/
  return Time if edm_type =~ /Edm.DateTime/
  return String
end
entry_to_class(entry) click to toggle source

Converts an XML Entry into a class

# File lib/ruby_odata/service.rb, line 433
def entry_to_class(entry)
  # Retrieve the class name from the fully qualified name (the last string after the last dot)
  klass_name = entry.xpath("./atom:category/@term", @ds_namespaces).to_s.split('.')[-1]

  # Is the category missing? See if there is a title that we can use to build the class
  if klass_name.nil?
    title = entry.xpath("./atom:title", @ds_namespaces).first
    return nil if title.nil?
    klass_name = title.content.to_s
  end

  return nil if klass_name.nil?

  properties = entry.xpath("./atom:content/m:properties/*", @ds_namespaces)

  klass = @classes[qualify_class_name(klass_name)].new

  # Fill metadata
  meta_id = entry.xpath("./atom:id", @ds_namespaces)[0].content
  klass.send :__metadata=, { :uri => meta_id }

  # Fill properties
  for prop in properties
    prop_name = prop.name
    klass.send "#{prop_name}=", parse_value_xml(prop)
  end

  # Fill properties represented outside of the properties collection
  @class_metadata[qualify_class_name(klass_name)].select { |k,v| v.fc_keep_in_content == false }.each do |k, meta|
    if meta.fc_target_path == "SyndicationTitle"
      title = entry.xpath("./atom:title", @ds_namespaces).first
      klass.send "#{meta.name}=", title.content
    elsif meta.fc_target_path == "SyndicationSummary"
      summary = entry.xpath("./atom:summary", @ds_namespaces).first
      klass.send "#{meta.name}=", summary.content
    end
  end

  inline_links = entry.xpath("./atom:link[m:inline]", @ds_namespaces)

  for link in inline_links
    # TODO: Use the metadata's associations to determine the multiplicity instead of this "hack"
    property_name = link.attributes['title'].to_s
    if singular?(property_name)
      inline_entry = link.xpath("./m:inline/atom:entry", @ds_namespaces).first
      inline_klass = build_inline_class(klass, inline_entry, property_name)
      klass.send "#{property_name}=", inline_klass
    else
      inline_classes, inline_entries = [], link.xpath("./m:inline/atom:feed/atom:entry", @ds_namespaces)
      for inline_entry in inline_entries
        # Build the class
        inline_klass = entry_to_class(inline_entry)

        # Add the property to the temp collection
        inline_classes << inline_klass
      end

      # Assign the array of classes to the property
      property_name = link.xpath("@title", @ds_namespaces)
      klass.send "#{property_name}=", inline_classes
    end
  end

  klass
end
execute_import_function(name, *args) click to toggle source

Executes an import function

# File lib/ruby_odata/service.rb, line 815
def execute_import_function(name, *args)
  func = @function_imports[name]

  # Check the args making sure that more weren't passed in than the function needs
  param_count = func[:parameters].nil? ? 0 : func[:parameters].count
  arg_count = args.nil? ? 0 : args[0].count
  if arg_count > param_count
    raise ArgumentError, "wrong number of arguments (#{arg_count} for #{param_count})"
  end

  # Convert the parameters to a hash
  params = {}
  func[:parameters].keys.each_with_index { |key, i| params[key] = args[0][i] } unless func[:parameters].nil?

  function_uri = build_function_import_uri(name, params)
  result = RestClient::Resource.new(function_uri, @rest_options).send(func[:http_method].downcase, {})

  # Is this a 204 (No content) result?
  return true if result.code == 204

  # No? Then we need to parse the results. There are 4 kinds...
  if func[:return_type] == Array
    # a collection of entites
    return build_classes_from_result(result) if @classes.include?(func[:inner_return_type].to_s)
    # a collection of native types
    elements = Nokogiri::XML(result).xpath("//ds:element", @ds_namespaces)
    results = []
    elements.each do |e|
      results << parse_primative_type(e.content, func[:inner_return_type])
    end
    return results
  end

  # a single entity
  if @classes.include?(func[:return_type].to_s)
    entry = Nokogiri::XML(result).xpath("atom:entry[not(ancestor::atom:entry)]", @ds_namespaces)
    return entry_to_class(entry)
  end

  # or a single native type
  unless func[:return_type].nil?
    e = Nokogiri::XML(result).xpath("/*").first
    return parse_primative_type(e.content, func[:return_type])
  end

  # Nothing could be parsed, so just return if we got a 200 or not
  return (result.code == 200)
end
extract_partial(doc) click to toggle source

Tests for and extracts the next href of a partial

# File lib/ruby_odata/service.rb, line 500
def extract_partial(doc)
  next_links = doc.xpath('//atom:link[@rel="next"]', @ds_namespaces)
  @has_partial = next_links.any?
  if @has_partial
    uri = Addressable::URI.parse(next_links[0]['href'])
    uri.query_values = uri.query_values.merge @additional_params unless @additional_params.empty?
    @next_uri = uri.to_s
  end
end
fill_complex_type_properties(complex_type_xml, klass) click to toggle source

Helper method for complex_type_to_class

# File lib/ruby_odata/service.rb, line 736
def fill_complex_type_properties(complex_type_xml, klass)
  properties = complex_type_xml.xpath(".//*")
  properties.each do |prop|
    klass.send "#{prop.name}=", parse_value_xml(prop)
  end
end
find_id_metadata(collection_name) click to toggle source

Finds the metadata associated with the given collection's first id property Remarks: This is used for single item lookup queries using the ID, e.g. Products(1), not complex primary keys

@param [String] collection_name the name of the collection

# File lib/ruby_odata/service.rb, line 199
def find_id_metadata(collection_name)
  collection_data = @collections.fetch(collection_name)
  class_metadata = @class_metadata.fetch(collection_data[:type].to_s)
  key = class_metadata.select{|k,h| h.is_key }.collect{|k,h| h.name }[0]
  class_metadata[key]
end
generate_guid() click to toggle source

Batch Saves

# File lib/ruby_odata/service.rb, line 627
def generate_guid
  rand(36**12).to_s(36).insert(4, "-").insert(9, "-")
end
get_verify_mode() click to toggle source

Gets ssl certificate verification mode, or defaults to verify_peer

# File lib/ruby_odata/service.rb, line 253
def get_verify_mode
  if @options[:verify_ssl].nil?
    return OpenSSL::SSL::VERIFY_PEER
  else
    return @options[:verify_ssl]
  end
end
handle_collection_result(result) click to toggle source

Handle parsing of OData Atom result and return an array of Entry classes

# File lib/ruby_odata/service.rb, line 367
def handle_collection_result(result)
  results = build_classes_from_result(result)
  while partial? && @options[:eager_partial]
    results.concat handle_partial
  end
  results
end
handle_exception(e) click to toggle source

Handles errors from the OData service

# File lib/ruby_odata/service.rb, line 376
def handle_exception(e)
  raise e unless defined? e.response

  code = e.http_code
  error = Nokogiri::XML(e.response)

  message = if error.xpath("m:error/m:message", @ds_namespaces).first
              error.xpath("m:error/m:message", @ds_namespaces).first.content
            else
              "Server returned error but no message."
            end
  raise ServiceError.new(code), message
end
handle_partial() click to toggle source
# File lib/ruby_odata/service.rb, line 510
def handle_partial
  if @next_uri
    result = RestClient::Resource.new(@next_uri, @rest_options).get
    results = handle_collection_result(result)
  end
  results
end
parse_date(sdate) click to toggle source

Handles parsing datetimes from a string

# File lib/ruby_odata/service.rb, line 746
def parse_date(sdate)
  # Assume this is UTC if no timezone is specified
  sdate = sdate + "Z" unless sdate.match(/Z|([+|-]\d{2}:\d{2})$/)

  # This is to handle older versions of Ruby (e.g. ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mingw32])
  # See http://makandra.com/notes/1017-maximum-representable-value-for-a-ruby-time-object
  # In recent versions of Ruby, Time has a much larger range
  begin
    result = Time.parse(sdate)
  rescue ArgumentError
    result = DateTime.parse(sdate)
  end

  return result
end
parse_primative_type(value, return_type) click to toggle source

Parses a value into the proper type based on a specified return type

# File lib/ruby_odata/service.rb, line 796
def parse_primative_type(value, return_type)
  return value.to_i if return_type == Fixnum
  return value.to_d if return_type == Float
  return parse_date(value.to_s) if return_type == Time
  return value.to_s
end
parse_value(content, property_type = nil, property_null = nil) click to toggle source
# File lib/ruby_odata/service.rb, line 774
def parse_value(content, property_type = nil, property_null = nil)
  # Handle anything marked as null
  return nil if !property_null.nil? && property_null == "true"

  # Handle a nil property type, this is a string
  return content if property_type.nil?

  # Handle integers
  return content.to_i if property_type.match(/^Edm.Int/)

  # Handle decimals
  return content.to_d if property_type.match(/Edm.Decimal/)

  # Handle DateTimes
  # return Time.parse(property_xml.content) if property_type.match(/Edm.DateTime/)
  return parse_date(content) if property_type.match(/Edm.DateTime/)

  # If we can't parse the value, just return the element's content
  content
end
parse_value_xml(property_xml) click to toggle source

Parses a value into the proper type based on an xml property element

# File lib/ruby_odata/service.rb, line 763
def parse_value_xml(property_xml)
  property_type = Helpers.get_namespaced_attribute(property_xml, 'type', 'm')
  property_null = Helpers.get_namespaced_attribute(property_xml, 'null', 'm')

  if property_type.nil? || (property_type && property_type.match(/^Edm/))
    return parse_value(property_xml.content, property_type, property_null)
  end

  complex_type_to_class(property_xml)
end
qualify_class_name(klass_name) click to toggle source

Converts a class name to its fully qualified name (if applicable) and returns the new name

# File lib/ruby_odata/service.rb, line 343
def qualify_class_name(klass_name)
  unless @namespace.nil? || @namespace.blank? || klass_name.include?('::')
    namespaces = @namespace.split(/\.|::/)
    namespaces << klass_name
    klass_name = namespaces.join '::'
  end
  klass_name.camelize
end
set_namespaces() click to toggle source
# File lib/ruby_odata/service.rb, line 238
def set_namespaces
  @edmx = Nokogiri::XML(RestClient::Resource.new(build_metadata_uri, @rest_options).get)
  @ds_namespaces = {
    "m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata",
    "edmx" => "http://schemas.microsoft.com/ado/2007/06/edmx",
    "ds" => "http://schemas.microsoft.com/ado/2007/08/dataservices",
    "atom" => "http://www.w3.org/2005/Atom"
  }

  # Get the edm namespace from the edmx
  edm_ns = @edmx.xpath("edmx:Edmx/edmx:DataServices/*", @namespaces).first.namespaces['xmlns'].to_s
  @ds_namespaces.merge! "edm" => edm_ns
end
set_options!(options) click to toggle source
# File lib/ruby_odata/service.rb, line 218
def set_options!(options)
  @options = options
  if @options[:eager_partial].nil?
    @options[:eager_partial] = true
  end
  @rest_options = { :verify_ssl => get_verify_mode, :user => @options[:username], :password => @options[:password] }
  @rest_options.merge!(options[:rest_options] || {})
  @additional_params = options[:additional_params] || {}
  @namespace = options[:namespace]
  @json_type = options[:json_type] || :json
end
single_save(operation) click to toggle source
# File lib/ruby_odata/service.rb, line 599
def single_save(operation)
  if operation.kind == "Add"
    save_uri = build_save_uri(operation)
    json_klass = operation.klass.to_json(:type => :add)
    post_result = RestClient::Resource.new(save_uri, @rest_options).post json_klass, {:content_type => @json_type}
    return build_classes_from_result(post_result)
  elsif operation.kind == "Update"
    update_uri = build_resource_uri(operation)
    json_klass = operation.klass.to_json
    update_result = RestClient::Resource.new(update_uri, @rest_options).put json_klass, {:content_type => @json_type}
    return (update_result.code == 204)
  elsif operation.kind == "Delete"
    delete_uri = build_resource_uri(operation)
    delete_result = RestClient::Resource.new(delete_uri, @rest_options).delete
    return (delete_result.code == 204)
  elsif operation.kind == "AddLink"
    save_uri = build_add_link_uri(operation)
    json_klass = operation.child_klass.to_json(:type => :link)
    post_result = RestClient::Resource.new(save_uri, @rest_options).post json_klass, {:content_type => @json_type}

    # Attach the child to the parent
    link_child_to_parent(operation) if (post_result.code == 204)

    return(post_result.code == 204)
  end
end
singular?(value) click to toggle source

Helpers

# File lib/ruby_odata/service.rb, line 865
def singular?(value)
  value.singularize == value
end