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 +4 -4
- data/README.md +1 -0
- data/lib/odata/model.rb +51 -3
- data/lib/odata/service.rb +56 -1
- data/lib/odata/service_registry.rb +10 -0
- data/lib/odata/version.rb +1 -1
- data/lib/odata.rb +1 -0
- data/spec/examples/product_model.rb +2 -2
- data/spec/odata/service_spec.rb +18 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e5c0770f691489dde6cbba87e2414dd75a1aa88
|
4
|
+
data.tar.gz: cfe4aecd0925e4301b0d031ae3f8119d169af98f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0008e948c633b04d6d8609796bcd3262e53ccb8ed82a75754d1edca84e153d0c04371fc695eee7b2e97d77715ea0dbc432eeb52bb533f232e6bb0466d8cf66fc
|
7
|
+
data.tar.gz: 23a3a1ca3bbf5dc67fc79fb15ec48512fd89dc84d7bcfb6cf54c37f3704b03b9c62b8e40dec95de1a8156b050e8f9413e96eb091fd123d2d010613bd89e2731e
|
data/README.md
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
[](https://codeclimate.com/github/plainprogrammer/odata)
|
5
5
|
[](https://codeclimate.com/github/plainprogrammer/odata)
|
6
6
|
[](http://badge.fury.io/rb/odata)
|
7
|
+
[](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,
|
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
|
-
|
42
|
-
|
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
data/lib/odata.rb
CHANGED
@@ -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'
|
data/spec/odata/service_spec.rb
CHANGED
@@ -51,7 +51,23 @@ describe OData::Service do
|
|
51
51
|
end
|
52
52
|
|
53
53
|
describe '#get' do
|
54
|
-
|
55
|
-
|
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
|