odata 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d4292a0e518b0b7733528f80c2b5c2e9097fc80
4
- data.tar.gz: ca790d90d9beb0d6deac504ec66bc563621a5fee
3
+ metadata.gz: 1e5c0770f691489dde6cbba87e2414dd75a1aa88
4
+ data.tar.gz: cfe4aecd0925e4301b0d031ae3f8119d169af98f
5
5
  SHA512:
6
- metadata.gz: af023f47d0d372cf7c3c85cc0cdaef18eca711381e22dd3aa2ebf222b26b4faef3fc6a3d522307dc3577e863fd366d212c749d201157ae28ba1f1307e299a5a7
7
- data.tar.gz: e5837bc7f3a012fd7c94c66f7d9774e90e684f0a7f3d1be93e96c8d0a8e489cb548e4d6f4b4ca9e2ae9dfe9ba0752636a8052c7ede9b00d2b716c85d3d701cc3
6
+ metadata.gz: 0008e948c633b04d6d8609796bcd3262e53ccb8ed82a75754d1edca84e153d0c04371fc695eee7b2e97d77715ea0dbc432eeb52bb533f232e6bb0466d8cf66fc
7
+ data.tar.gz: 23a3a1ca3bbf5dc67fc79fb15ec48512fd89dc84d7bcfb6cf54c37f3704b03b9c62b8e40dec95de1a8156b050e8f9413e96eb091fd123d2d010613bd89e2731e
data/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![Code Climate](https://codeclimate.com/github/plainprogrammer/odata.png)](https://codeclimate.com/github/plainprogrammer/odata)
5
5
  [![Coverage](https://codeclimate.com/github/plainprogrammer/odata/coverage.png)](https://codeclimate.com/github/plainprogrammer/odata)
6
6
  [![Gem Version](https://badge.fury.io/rb/odata.svg)](http://badge.fury.io/rb/odata)
7
+ [![Documentation](http://inch-ci.org/github/plainprogrammer/odata.png?branch=master)](http://rubydoc.info/github/plainprogrammer/odata/master/frames)
7
8
 
8
9
  The OData gem provides a simple wrapper around the OData API protocol. It has
9
10
  the ability to automatically inspect compliant APIs and expose the relevant
data/lib/odata/model.rb CHANGED
@@ -1,4 +1,12 @@
1
1
  module OData
2
+ # Provides a convenient way to represent OData entities as Ruby objects.
3
+ # Instances that include this module gain the ability to define how to map
4
+ # OData properties to attributes as well as the ability to query and persist
5
+ # data to the underlying OData service.
6
+ #
7
+ # If the module detects the presence of Rails, along with ActiveModel::Model
8
+ # it will automatically mixin support ActiveModel::Model to provide better
9
+ # integration with Rails apps.
2
10
  module Model
3
11
  extend ::ActiveSupport::Concern
4
12
  include ::ActiveModel::Model if defined?(::Rails) && defined?(::ActiveModel::Model)
@@ -13,17 +21,49 @@ module OData
13
21
  EOS
14
22
  end
15
23
 
24
+ def convert_value_for_type(value, type)
25
+ return nil if value.nil? || type.nil?
26
+ if type =~ /Int(16|32|64)$/
27
+ value.to_i
28
+ elsif type =~ /Boolean$/
29
+ (value == 'true' || value == '1')
30
+ elsif type =~ /(Decimal|Double|Single)$/
31
+ value.to_f
32
+ elsif type =~ /DateTime$/
33
+ DateTime.parse(value)
34
+ else
35
+ value.to_s
36
+ end
37
+ end
38
+
16
39
  module ClassMethods
40
+ # Returns the class' name
17
41
  def model_name
18
42
  self.to_s.demodulize
19
43
  end
20
44
 
45
+ # Pluralizes #model_name for OData requests
21
46
  def odata_name
22
47
  model_name.pluralize
23
48
  end
24
49
 
50
+ def load_from_feed(feed_hash)
51
+ loaded_instance = self.new
52
+ feed_hash.each do |attribute_name, details|
53
+ loaded_instance.instance_eval do
54
+ @properties ||= {}
55
+ @properties[attribute_name] = convert_value_for_type(details[:value], details[:type])
56
+ end
57
+ end
58
+ loaded_instance
59
+ end
60
+
61
+ # Define a property and it's options
62
+ #
63
+ # @param name [to_s] the literal property name expected by the OData service
64
+ # @param options [Hash] options for setting up the property
25
65
  def property(name, options = {})
26
- register_property(name.to_s.underscore, options.merge(literal_name: name))
66
+ register_property(name.to_s.underscore, {literal_name: name}.merge(options))
27
67
  create_accessors(name.to_s.underscore, options)
28
68
  register_primary_key(name.to_s.underscore) if options[:primary_key]
29
69
  end
@@ -38,8 +78,16 @@ module OData
38
78
 
39
79
  def create_accessors(name, options)
40
80
  self.class_eval do
41
- define_method(name) { @properties[name.to_sym] || nil }
42
- define_method("#{name}=") {|value| @properties[name.to_sym] = value}
81
+ if options[:entity_title]
82
+ define_method(name) { @properties[:title] || nil }
83
+ define_method("#{name}=") {|value| @properties[:title] = value}
84
+ elsif options[:entity_summary]
85
+ define_method(name) { @properties[:summary] || nil }
86
+ define_method("#{name}=") {|value| @properties[:summary] = value}
87
+ else
88
+ define_method(name) { @properties[name.to_sym] || nil }
89
+ define_method("#{name}=") {|value| @properties[name.to_sym] = value}
90
+ end
43
91
  end
44
92
  end
45
93
 
data/lib/odata/service.rb CHANGED
@@ -1,33 +1,55 @@
1
1
  module OData
2
+ # Encapsulates the basic details and functionality needed to interact with an
3
+ # OData service.
2
4
  class Service
5
+ # The OData Service's URL
3
6
  attr_reader :service_url
4
7
 
8
+ # Opens the service based on the requested URL and adds the service to
9
+ # {OData::Registry}
10
+ #
11
+ # @param service_url [String] the URL to the desired OData service
12
+ # @return [OData::Service] an instance of the service
5
13
  def initialize(service_url)
6
14
  @service_url = service_url
7
15
  OData::ServiceRegistry.add(self)
8
16
  self
9
17
  end
10
18
 
19
+ # Opens the service based on the requested URL and adds the service to
20
+ # {OData::Registry}
21
+ #
22
+ # @param service_url [String] the URL to the desired OData service
23
+ # @return [OData::Service] an instance of the service
11
24
  def self.open(service_url)
12
25
  Service.new(service_url)
13
26
  end
14
27
 
28
+ # Returns a list of entities exposed by the service
15
29
  def entities
16
30
  @entities ||= metadata.xpath('//EntityType').collect {|entity| entity.attributes['Name'].value}
17
31
  end
18
32
 
33
+ # Returns a list of ComplexTypes used by the service
19
34
  def complex_types
20
35
  @complex_types ||= metadata.xpath('//ComplexType').collect {|entity| entity.attributes['Name'].value}
21
36
  end
22
37
 
38
+ # Returns the namespace defined on the service's schema
23
39
  def namespace
24
40
  @namespace ||= metadata.xpath('//Schema').first.attributes['Namespace'].value
25
41
  end
26
42
 
43
+ # Returns a more compact inspection of the service object
27
44
  def inspect
28
45
  "#<#{self.class.name}:#{self.object_id} namespace='#{self.namespace}' service_url='#{self.service_url}'>"
29
46
  end
30
47
 
48
+ # Handles getting OData resources from the service.
49
+ #
50
+ # @param model [OData::Model] the type of resource being requested
51
+ # @param criteria [Hash] any criteria to narrow the request
52
+ # @return [Array] instances of the requested model
31
53
  def get(model, criteria = {})
32
54
  request = ::Typhoeus::Request.new(
33
55
  build_request_url(model, criteria),
@@ -36,7 +58,7 @@ module OData
36
58
  request.run
37
59
  response = request.response
38
60
  feed = ::Nokogiri::XML(response.body).remove_namespaces!
39
- feed.xpath('//entry')
61
+ feed.xpath('//entry').collect {|entry| parse_model_from_feed(model, entry)}
40
62
  end
41
63
 
42
64
  private
@@ -58,5 +80,38 @@ module OData
58
80
  request_url += "(#{criteria[:key]})" if criteria[:key]
59
81
  request_url
60
82
  end
83
+
84
+ def parse_model_from_feed(model, entry)
85
+ attributes = {}
86
+
87
+ attributes[:title] = {
88
+ value: entry.xpath('//title').first.content,
89
+ type: entry.xpath('//title').first.attributes['type'].value
90
+ }
91
+
92
+ attributes[:summary] = {
93
+ value: entry.xpath('//summary').first.content,
94
+ type: entry.xpath('//summary').first.attributes['type'].value
95
+ }
96
+
97
+ entry.xpath('//content/properties/*').each do |property|
98
+ if property.attributes['null']
99
+ if property.attributes['null'].value == 'true'
100
+ property_type = nil
101
+ else
102
+ property_type = property.attributes['type'].value
103
+ end
104
+ else
105
+ property_type = property.attributes['type'].value
106
+ end
107
+
108
+ attributes[property.name.underscore.to_sym] = {
109
+ value: property.content,
110
+ type: property_type
111
+ }
112
+ end
113
+
114
+ model.load_from_feed(attributes)
115
+ end
61
116
  end
62
117
  end
@@ -1,9 +1,13 @@
1
1
  require 'singleton'
2
2
 
3
3
  module OData
4
+ # Provides a registry for keeping track of multiple OData::Service instances
4
5
  class ServiceRegistry
5
6
  include Singleton
6
7
 
8
+ # Add a service to the Registry
9
+ #
10
+ # @param service [OData::Service] service to add to the registry
7
11
  def add(service)
8
12
  initialize_instance_variables
9
13
  @services << service if service.is_a?(OData::Service) && !@services.include?(service)
@@ -11,16 +15,22 @@ module OData
11
15
  @services_by_url[service.service_url] = @services.find_index(service)
12
16
  end
13
17
 
18
+ # Lookup a service by URL or namespace
19
+ #
20
+ # @param lookup_key [String] the URL or namespace to lookup
21
+ # @return [OData::Service, nil] the OData::Service or nil
14
22
  def [](lookup_key)
15
23
  initialize_instance_variables
16
24
  index = @services_by_namespace[lookup_key] || @services_by_url[lookup_key]
17
25
  index.nil? ? nil : @services[index]
18
26
  end
19
27
 
28
+ # (see #add)
20
29
  def self.add(service)
21
30
  OData::ServiceRegistry.instance.add(service)
22
31
  end
23
32
 
33
+ # (see #[])
24
34
  def self.[](lookup_key)
25
35
  OData::ServiceRegistry.instance[lookup_key]
26
36
  end
data/lib/odata/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module OData
2
- VERSION = '0.0.6'
2
+ VERSION = '0.0.7'
3
3
  end
data/lib/odata.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'date'
1
2
  require 'nokogiri'
2
3
  require 'typhoeus'
3
4
 
@@ -3,8 +3,8 @@ module Examples
3
3
  include OData::Model
4
4
 
5
5
  property 'ID', primary_key: true
6
- property 'Name'
7
- property 'Description'
6
+ property 'Name', entity_title: true
7
+ property 'Description', entity_summary: true
8
8
  property 'ReleaseDate'
9
9
  property 'DiscontinuedDate'
10
10
  property 'Rating'
@@ -51,7 +51,23 @@ describe OData::Service do
51
51
  end
52
52
 
53
53
  describe '#get' do
54
- it { expect(subject.get(::Examples::Product).size).to eq(11) }
55
- it { expect(subject.get(::Examples::Product, {key: 0}).size).to eq(1) }
54
+ describe 'a set of entities' do
55
+ it { expect(subject.get(::Examples::Product).size).to eq(11) }
56
+ it { expect(subject.get(::Examples::Product).first).to be_instance_of(::Examples::Product) }
57
+ it { expect(subject.get(::Examples::Product).shuffle.first).to be_instance_of(::Examples::Product) }
58
+ it { expect(subject.get(::Examples::Product).last).to be_instance_of(::Examples::Product) }
59
+ end
60
+
61
+ describe 'a specific entity by key' do
62
+ it { expect(subject.get(::Examples::Product, {key: 0}).size).to eq(1) }
63
+ it { expect(subject.get(::Examples::Product, {key: 0}).first).to be_instance_of(::Examples::Product) }
64
+ it { expect(subject.get(::Examples::Product, {key: 0}).first.id).to eq(0) }
65
+ it { expect(subject.get(::Examples::Product, {key: 0}).first.name).to eq('Bread') }
66
+ it { expect(subject.get(::Examples::Product, {key: 0}).first.description).to eq('Whole grain bread') }
67
+ it { expect(subject.get(::Examples::Product, {key: 0}).first.release_date).to eq(DateTime.parse('1992-01-01T00:00:00')) }
68
+ it { expect(subject.get(::Examples::Product, {key: 0}).first.discontinued_date).to eq(nil) }
69
+ it { expect(subject.get(::Examples::Product, {key: 0}).first.rating).to eq(4) }
70
+ it { expect(subject.get(::Examples::Product, {key: 0}).first.price).to eq(2.5) }
71
+ end
56
72
  end
57
73
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: odata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Thompson