odata 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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