odata 0.0.10 → 0.1.0

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: bcb9a746bd19061bb07c0311b4bdc705c084ad5b
4
- data.tar.gz: 1f2c9dae62cbec8852a2f65a29826f3ee5412a33
3
+ metadata.gz: a62f54f63a4c6c397d711b31eea1d31f41383e80
4
+ data.tar.gz: 8a4e945b39e401bfbaaaba43e2e52c884949318a
5
5
  SHA512:
6
- metadata.gz: 28a93be11707fe9adcf4aa51af4a0c48d73bce1b680525ac293ceb89c9cb8b11e4045321da62d4a5e280158efb289fcd50325eaf20b1f2b5d6b5bf198ab3480e
7
- data.tar.gz: 88b1a0bc6fb085880e20f102bebf8e16aad9a4313641cf1a5b6a5f75fde34caa47bc677ad886a473e843c4e82d04509bfea6351c5a504818e445a3eea130d3e4
6
+ metadata.gz: 591263ed5f45b8f12ef3ebf167bed9fff9e3e61c98de34b58601a5c3db2b89f18c4207feabbdccf08d9970a67c93f0f2d340747b7f767f64a615255c54769735
7
+ data.tar.gz: 4bc3b99da624d26e44d77ff8400c1ae5067d77aa56a8f435f739b37de0d50de3dc28967e8aae3fce004491e00808167fdd022133d2d25782347b28296d698cc3
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.1.0
4
+
5
+ * Core read/write behavior for OData v1-3
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # OData
2
2
 
3
- [![Build Status](https://travis-ci.org/plainprogrammer/odata.svg?branch=master)](https://travis-ci.org/plainprogrammer/odata)
4
- [![Code Climate](https://codeclimate.com/github/plainprogrammer/odata.png)](https://codeclimate.com/github/plainprogrammer/odata)
5
- [![Coverage](https://codeclimate.com/github/plainprogrammer/odata/coverage.png)](https://codeclimate.com/github/plainprogrammer/odata)
3
+ [![Build Status](https://travis-ci.org/ruby-odata/odata.svg?branch=master)](https://travis-ci.org/ruby-odata/odata)
4
+ [![Code Climate](https://codeclimate.com/github/ruby-odata/odata.png)](https://codeclimate.com/github/ruby-odata/odata)
5
+ [![Coverage](https://codeclimate.com/github/ruby-odata/odata/coverage.png)](https://codeclimate.com/github/ruby-odata/odata)
6
6
  [![Gem Version](https://badge.fury.io/rb/odata.svg)](http://badge.fury.io/rb/odata)
7
- [![Dependency Status](https://gemnasium.com/plainprogrammer/odata.svg)](https://gemnasium.com/plainprogrammer/odata)
8
- [![Documentation](http://inch-ci.org/github/plainprogrammer/odata.png?branch=master)](http://rubydoc.info/github/plainprogrammer/odata/master/frames)
7
+ [![Dependency Status](https://gemnasium.com/ruby-odata/odata.svg)](https://gemnasium.com/ruby-odata/odata)
8
+ [![Documentation](http://inch-ci.org/github/ruby-odata/odata.png?branch=master)](http://rubydoc.info/github/ruby-odata/odata/master/frames)
9
9
 
10
10
  The OData gem provides a simple wrapper around the OData API protocol. It has
11
11
  the ability to automatically inspect compliant APIs and expose the relevant
data/lib/odata.rb CHANGED
@@ -9,14 +9,18 @@ require 'active_support/core_ext'
9
9
  require 'active_support/concern'
10
10
 
11
11
  require 'odata/version'
12
+ require 'odata/property'
13
+ require 'odata/properties'
12
14
  require 'odata/entity'
13
15
  require 'odata/entity_set'
14
16
  require 'odata/service'
15
17
  require 'odata/service_registry'
16
- require 'odata/model'
17
18
 
18
19
  require 'odata/railtie' if defined?(::Rails)
19
20
 
21
+ # The OData gem provides a convenient way to interact with OData services from
22
+ # Ruby. Please look to the {file:README.md README} for how to get started using
23
+ # the OData gem.
20
24
  module OData
21
25
  # Your code goes here...
22
26
  end
data/lib/odata/entity.rb CHANGED
@@ -5,7 +5,6 @@ module OData
5
5
  def initialize(options = {})
6
6
  @type = options[:type]
7
7
  @namespace = options[:namespace]
8
- @properties = {}
9
8
  end
10
9
 
11
10
  def name
@@ -13,7 +12,34 @@ module OData
13
12
  end
14
13
 
15
14
  def [](property_name)
16
- @properties[property_name.to_s].value
15
+ begin
16
+ properties[property_name.to_s].value
17
+ rescue NoMethodError
18
+ raise ArgumentError, "Unknown property: #{property_name}"
19
+ end
20
+ end
21
+
22
+ def []=(property_name, value)
23
+ begin
24
+ properties[property_name.to_s].value = value
25
+ rescue NoMethodError
26
+ raise ArgumentError, "Unknown property: #{property_name}"
27
+ end
28
+ end
29
+
30
+ def self.with_properties(new_properties = {}, options = {})
31
+ entity = OData::Entity.new(options)
32
+ entity.instance_eval do
33
+ # TODO Define the properties
34
+ service.properties_for(name).each do |name, instance|
35
+ set_property(name, instance)
36
+ end
37
+
38
+ new_properties.each do |property_name, property_value|
39
+ self[property_name] = property_value
40
+ end
41
+ end
42
+ entity
17
43
  end
18
44
 
19
45
  def self.from_xml(xml_doc, options = {})
@@ -55,14 +81,56 @@ module OData
55
81
  entity
56
82
  end
57
83
 
84
+ def to_xml
85
+ builder = Nokogiri::XML::Builder.new do |xml|
86
+ xml.entry('xmlns' => 'http://www.w3.org/2005/Atom',
87
+ 'xmlns:data' => 'http://schemas.microsoft.com/ado/2007/08/dataservices',
88
+ 'xmlns:metadata' => 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata',
89
+ 'xmlns:georss' => 'http://www.georss.org/georss',
90
+ 'xmlns:gml' => 'http://www.opengis.net/gml',
91
+ 'xml:base' => 'http://services.odata.org/OData/OData.svc/') do
92
+ xml.category(term: "#{namespace}.#{type}",
93
+ scheme: 'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme')
94
+ xml.author { xml.name }
95
+
96
+ xml.content(type: 'application/xml') do
97
+ xml['metadata'].properties do
98
+ properties.each do |name, property|
99
+ next if name == primary_key
100
+ attributes = {
101
+ 'metadata:type' => property.type,
102
+ }
103
+
104
+ if property.value.nil?
105
+ attributes['metadata:null'] = 'true'
106
+ xml['data'].send(name.to_sym, attributes)
107
+ else
108
+ xml['data'].send(name.to_sym, attributes, property.xml_value)
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ builder.to_xml
116
+ end
117
+
118
+ def primary_key
119
+ service.primary_key_for(name)
120
+ end
121
+
58
122
  private
59
123
 
124
+ def properties
125
+ @properties ||= {}
126
+ end
127
+
60
128
  def service
61
129
  @service ||= OData::ServiceRegistry[namespace]
62
130
  end
63
131
 
64
132
  def set_property(name, odata_property)
65
- @properties[name.to_s] = odata_property.dup
133
+ properties[name.to_s] = odata_property.dup
66
134
  end
67
135
  end
68
136
  end
@@ -39,7 +39,7 @@ module OData
39
39
  page_position = 0
40
40
  end
41
41
 
42
- entity = OData::Entity.from_xml(entities[page_position], namespace: namespace, type: type)
42
+ entity = OData::Entity.from_xml(entities[page_position], entity_options)
43
43
  block_given? ? block.call(entity) : yield(entity)
44
44
 
45
45
  counter += 1
@@ -47,17 +47,61 @@ module OData
47
47
  end
48
48
  end
49
49
 
50
+ def first
51
+ result = service.execute("#{name}?$skip=0&$top=1")
52
+ entities = service.find_entities(result)
53
+ OData::Entity.from_xml(entities[0], entity_options)
54
+ end
55
+
50
56
  # Returns the number of entities within the set
51
57
  def count
52
58
  service.execute("#{name}/$count").body.to_i
53
59
  end
54
60
 
61
+ def new_entity(properties = {})
62
+ OData::Entity.with_properties(properties, entity_options)
63
+ end
64
+
65
+ def <<(entity)
66
+ new_entity = entity[entity.primary_key].nil?
67
+
68
+ url_chunk = name
69
+ url_chunk += "(#{entity[entity.primary_key]})" unless new_entity
70
+
71
+ options = {
72
+ method: :post,
73
+ body: entity.to_xml.gsub(/\n\s+/, ''),
74
+ headers: {
75
+ Accept: 'application/atom+xml',
76
+ 'Content-Type' => 'application/atom+xml'
77
+ }
78
+ }
79
+
80
+ result = service.execute(url_chunk, options)
81
+ if result.code.to_s =~ /^2[0-9][0-9]$/
82
+ if new_entity
83
+ doc = ::Nokogiri::XML(result.body).remove_namespaces!
84
+ entity[entity.primary_key] = doc.xpath("//content/properties/#{entity.primary_key}").first.content
85
+ end
86
+ else
87
+ raise StandardError, 'Something went wrong committing your entity'
88
+ end
89
+ entity
90
+ end
91
+
55
92
  private
56
93
 
57
94
  def service
58
95
  @service ||= OData::ServiceRegistry[namespace]
59
96
  end
60
97
 
98
+ def entity_options
99
+ {
100
+ namespace: namespace,
101
+ type: type
102
+ }
103
+ end
104
+
61
105
  def get_paginated_entities(per_page, page)
62
106
  result = service.execute("#{name}?$inlinecount=allpages&$skip=#{per_page * page}&$top=#{per_page}")
63
107
  entities = service.find_entities(result)
@@ -26,14 +26,16 @@ module OData
26
26
 
27
27
  def validate(value)
28
28
  begin
29
+ return if value.nil? && allows_nil?
29
30
  return if value.is_a?(::DateTime)
30
31
  ::DateTime.strptime(value, '%Y-%m-%dT%H:%M:%S.%L')
31
- rescue => e
32
+ rescue
32
33
  raise ArgumentError, 'Value is not a date time format that can be parsed'
33
34
  end
34
35
  end
35
36
 
36
37
  def parse_value(value)
38
+ return value if value.nil? && allows_nil?
37
39
  parsed_value = value
38
40
  parsed_value = ::DateTime.parse(value) unless value.is_a?(::DateTime)
39
41
  parsed_value.strftime('%Y-%m-%dT%H:%M:%S.%L')
@@ -25,6 +25,10 @@ module OData
25
25
  @concurrecy_mode ||= options[:concurrency_mode]
26
26
  end
27
27
 
28
+ def xml_value
29
+ @value
30
+ end
31
+
28
32
  private
29
33
 
30
34
  def default_options
data/lib/odata/service.rb CHANGED
@@ -61,27 +61,9 @@ module OData
61
61
  "#<#{self.class.name}:#{self.object_id} namespace='#{self.namespace}' service_url='#{self.service_url}'>"
62
62
  end
63
63
 
64
- # Handles getting OData resources from the service.
65
- #
66
- # @param model [OData::Model] the type of resource being requested
67
- # @param criteria [Hash] any criteria to narrow the request
68
- # @return [Array] instances of the requested model
69
- def get(model, criteria = {})
70
- request = ::Typhoeus::Request.new(
71
- build_request_url(model, criteria),
72
- options[:typhoeus].merge({
73
- method: :get
74
- })
75
- )
76
- request.run
77
- response = request.response
78
- feed = ::Nokogiri::XML(response.body).remove_namespaces!
79
- feed.xpath('//entry').collect {|entry| parse_model_from_feed(model, entry)}
80
- end
81
-
82
64
  # Retrieves the EntitySet associated with a specific EntityType by name
83
65
  #
84
- # @param entity_type_name [to_s] the name of the EntityType you want the EntitySet of
66
+ # @param entity_set_name [to_s] the name of the EntitySet desired
85
67
  # @return [OData::EntitySet] an OData::EntitySet to query
86
68
  def [](entity_set_name)
87
69
  xpath_query = "//EntityContainer/EntitySet[@Name='#{entity_set_name}']"
@@ -95,6 +77,11 @@ module OData
95
77
  container: container_name)
96
78
  end
97
79
 
80
+ # Execute a request against the service
81
+ #
82
+ # @param url_chunk [to_s] string to append to service url
83
+ # @param additional_options [Hash] options to pass to Typhoeus
84
+ # @return [Typhoeus::Response]
98
85
  def execute(url_chunk, additional_options = {})
99
86
  request = ::Typhoeus::Request.new(
100
87
  "#{service_url}/#{url_chunk}",
@@ -106,30 +93,70 @@ module OData
106
93
  request.response
107
94
  end
108
95
 
96
+ # Find a specific node in the given result set
97
+ #
98
+ # @param results [Typhoeus::Response]
99
+ # @return [Nokogiri::XML::Element]
109
100
  def find_node(results, node_name)
110
101
  document = ::Nokogiri::XML(results.body)
111
102
  document.remove_namespaces!
112
103
  document.xpath("//#{node_name}").first
113
104
  end
114
105
 
106
+ # Find entity entries in a result set
107
+ #
108
+ # @param results [Typhoeus::Response]
109
+ # @return [Nokogiri::XML::NodeSet]
115
110
  def find_entities(results)
116
111
  document = ::Nokogiri::XML(results.body)
117
112
  document.remove_namespaces!
118
113
  document.xpath('//entry')
119
114
  end
120
115
 
116
+ # Get the property type for an entity from metadata.
117
+ #
118
+ # @param entity_name [to_s] the name of the relevant entity
119
+ # @param property_name [to_s] the property name needed
120
+ # @return [String] the name of the property's type
121
121
  def get_property_type(entity_name, property_name)
122
122
  metadata.xpath("//EntityType[@Name='#{entity_name}']/Property[@Name='#{property_name}']").first.attributes['Type'].value
123
123
  end
124
124
 
125
+ # Get the property used as the title for an entity from metadata.
126
+ #
127
+ # @param entity_name [to_s] the name of the relevant entity
128
+ # @return [String] the name of the property used as the entity title
125
129
  def get_title_property_name(entity_name)
126
130
  metadata.xpath("//EntityType[@Name='#{entity_name}']/Property[@FC_TargetPath='SyndicationTitle']").first.attributes['Name'].value
127
131
  end
128
132
 
133
+ # Get the property used as the summary for an entity from metadata.
134
+ #
135
+ # @param entity_name [to_s] the name of the relevant entity
136
+ # @return [String] the name of the property used as the entity summary
129
137
  def get_summary_property_name(entity_name)
130
138
  metadata.xpath("//EntityType[@Name='#{entity_name}']/Property[@FC_TargetPath='SyndicationSummary']").first.attributes['Name'].value
131
139
  end
132
140
 
141
+ def primary_key_for(entity_name)
142
+ metadata.xpath("//EntityType[@Name='#{entity_name}']/Key/PropertyRef").first.attributes['Name'].value
143
+ end
144
+
145
+ def properties_for(entity_name)
146
+ type_definition = metadata.xpath("//EntityType[@Name='#{entity_name}']").first
147
+ raise ArgumentError, "Unknown EntityType: #{entity_name}" if type_definition.nil?
148
+ properties_to_return = {}
149
+ type_definition.xpath('./Property').each do |property_xml|
150
+ property_name = property_xml.attributes['Name'].value
151
+ value_type = property_xml.attributes['Type'].value
152
+ property_options = {}
153
+ property_options[:allows_nil] = false if property_xml.attributes['Nullable'] == 'false'
154
+ klass_name = value_type.gsub(/^Edm\./, '')
155
+ properties_to_return[property_name] = "OData::Properties::#{klass_name}".constantize.new(property_name, nil, property_options)
156
+ end
157
+ properties_to_return
158
+ end
159
+
133
160
  private
134
161
 
135
162
  def default_options
@@ -151,41 +178,5 @@ module OData
151
178
  ::Nokogiri::XML(response.body).remove_namespaces!
152
179
  }.call
153
180
  end
154
-
155
- def build_request_url(model, criteria)
156
- request_url = "#{service_url}/#{model.odata_name}"
157
- request_url += "(#{criteria[:key]})" if criteria[:key]
158
- request_url
159
- end
160
-
161
- def parse_model_from_feed(model, entry)
162
- attributes = {}
163
-
164
- %w{title summary}.each do |attribute_name|
165
- attributes[attribute_name.to_sym] = {
166
- value: entry.xpath("//#{attribute_name}").first.content,
167
- type: entry.xpath("//#{attribute_name}").first.attributes['type'].value
168
- }
169
- end
170
-
171
- entry.xpath('//content/properties/*').each do |property|
172
- if property.attributes['null']
173
- if property.attributes['null'].value == 'true'
174
- property_type = nil
175
- else
176
- property_type = property.attributes['type'].value
177
- end
178
- else
179
- property_type = property.attributes['type'].value
180
- end
181
-
182
- attributes[property.name.underscore.to_sym] = {
183
- value: property.content,
184
- type: property_type
185
- }
186
- end
187
-
188
- model.load_from_feed(attributes)
189
- end
190
181
  end
191
182
  end
data/lib/odata/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module OData
2
- VERSION = '0.0.10'
2
+ VERSION = '0.1.0'
3
3
  end
data/odata.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'codeclimate-test-reporter'
25
25
  spec.add_development_dependency 'rspec', '~> 3.0.0'
26
26
  spec.add_development_dependency 'webmock', '~> 1.18.0'
27
+ spec.add_development_dependency 'timecop', '~> 0.7.1'
27
28
 
28
29
  spec.add_dependency 'backports', '~> 3.6.0'
29
30
  spec.add_dependency 'nokogiri', '~> 1.6.2'
@@ -0,0 +1,42 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <feed xml:base="http://services.odata.org/OData/OData.svc/" xmlns="http://www.w3.org/2005/Atom"
3
+ xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
4
+ xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
5
+ xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
6
+ <id>http://services.odata.org/OData/OData.svc/Products</id>
7
+ <title type="text">Products</title>
8
+ <updated>2014-07-07T20:01:27Z</updated>
9
+ <link rel="self" title="Products" href="Products"/>
10
+ <entry>
11
+ <id>http://services.odata.org/OData/OData.svc/Products(0)</id>
12
+ <category term="ODataDemo.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
13
+ <link rel="edit" title="Product" href="Products(0)"/>
14
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Categories"
15
+ type="application/atom+xml;type=feed" title="Categories" href="Products(0)/Categories"/>
16
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Supplier"
17
+ type="application/atom+xml;type=entry" title="Supplier" href="Products(0)/Supplier"/>
18
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ProductDetail"
19
+ type="application/atom+xml;type=entry" title="ProductDetail" href="Products(0)/ProductDetail"/>
20
+ <title type="text">Bread</title>
21
+ <summary type="text">Whole grain bread</summary>
22
+ <updated>2014-07-07T20:01:27Z</updated>
23
+ <author>
24
+ <name/>
25
+ </author>
26
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/Categories" type="application/xml"
27
+ title="Categories" href="Products(0)/$links/Categories"/>
28
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/Supplier" type="application/xml"
29
+ title="Supplier" href="Products(0)/$links/Supplier"/>
30
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/ProductDetail" type="application/xml"
31
+ title="ProductDetail" href="Products(0)/$links/ProductDetail"/>
32
+ <content type="application/xml">
33
+ <m:properties>
34
+ <d:ID m:type="Edm.Int32">0</d:ID>
35
+ <d:ReleaseDate m:type="Edm.DateTime">1992-01-01T00:00:00</d:ReleaseDate>
36
+ <d:DiscontinuedDate m:null="true"/>
37
+ <d:Rating m:type="Edm.Int16">4</d:Rating>
38
+ <d:Price m:type="Edm.Double">2.5</d:Price>
39
+ </m:properties>
40
+ </content>
41
+ </entry>
42
+ </feed>
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <entry xml:base="http://services.odata.org/OData/OData.svc/"
3
+ xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
4
+ xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
5
+ xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
6
+ <id>http://services.odata.org/OData/OData.svc/Products(9999)</id>
7
+ <category term="ODataDemo.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
8
+ <link rel="edit" title="Product" href="Products(9999)"/>
9
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Categories" type="application/atom+xml;type=feed" title="Categories" href="Products(9999)/Categories"/>
10
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Supplier" type="application/atom+xml;type=entry" title="Supplier" href="Products(9999)/Supplier"/>
11
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ProductDetail" type="application/atom+xml;type=entry" title="ProductDetail" href="Products(9999)/ProductDetail"/>
12
+ <title type="text">Widget</title>
13
+ <summary type="text">A simple widget</summary>
14
+ <updated>2014-07-07T18:47:05Z</updated>
15
+ <author>
16
+ <name/>
17
+ </author>
18
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/Categories" type="application/xml" title="Categories" href="Products(9999)/$links/Categories"/>
19
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/Supplier" type="application/xml" title="Supplier" href="Products(9999)/$links/Supplier"/>
20
+ <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/ProductDetail" type="application/xml" title="ProductDetail" href="Products(9999)/$links/ProductDetail"/>
21
+ <m:action
22
+ metadata="http://services.odata.org/OData/OData.svc/$metadata#DemoService.Discount"
23
+ title="Discount"
24
+ target="http://services.odata.org/OData/OData.svc/Products(9999)/Discount"/>
25
+ <content type="application/xml">
26
+ <m:properties>
27
+ <d:ID m:type="Edm.Int32">9999</d:ID>
28
+ <d:ReleaseDate m:type="Edm.DateTime">2014-07-07T12:44:13</d:ReleaseDate>
29
+ <d:DiscontinuedDate m:null="true"/>
30
+ <d:Rating m:type="Edm.Int16">4</d:Rating>
31
+ <d:Price m:type="Edm.Double">3.5</d:Price>
32
+ </m:properties>
33
+ </content>
34
+ </entry>
@@ -11,8 +11,7 @@ describe OData::EntitySet do
11
11
  OData::Service.open('http://services.odata.org/OData/OData.svc')
12
12
  end
13
13
 
14
- # Basic Instance Methods
15
- it { expect(subject).to respond_to(:name, :type, :container, :namespace) }
14
+ it { expect(subject).to respond_to(:name, :type, :container, :namespace, :new_entity) }
16
15
 
17
16
  it { expect(subject.name).to eq('Products') }
18
17
  it { expect(subject.container).to eq('DemoService') }
@@ -37,4 +36,65 @@ describe OData::EntitySet do
37
36
  it { expect(subject).to respond_to(:count) }
38
37
  it { expect(subject.count).to eq(11) }
39
38
  end
39
+
40
+ describe '#new_entity' do
41
+ let(:new_entity) { subject.new_entity(properties) }
42
+ let(:release_date) { DateTime.new(2014,7,5) }
43
+ let(:properties) { {
44
+ Name: 'Widget',
45
+ Description: 'Just a simple widget',
46
+ ReleaseDate: release_date,
47
+ DiscontinuedDate: nil,
48
+ Rating: 4,
49
+ Price: 3.5
50
+ } }
51
+
52
+ it { expect(new_entity['ID']).to be_nil }
53
+ it { expect(new_entity['Name']).to eq('Widget') }
54
+ it { expect(new_entity['Description']).to eq('Just a simple widget') }
55
+ it { expect(new_entity['ReleaseDate']).to eq(release_date) }
56
+ it { expect(new_entity['DiscontinuedDate']).to be_nil }
57
+ it { expect(new_entity['Rating']).to eq(4) }
58
+ it { expect(new_entity['Price']).to eq(3.5) }
59
+ end
60
+
61
+ describe '#<<' do
62
+ let(:new_entity) { subject.new_entity(properties) }
63
+ let(:bad_entity) { subject.new_entity }
64
+ let(:existing_entity) { subject.first }
65
+ let(:properties) { {
66
+ Name: 'Widget',
67
+ Description: 'Just a simple widget',
68
+ ReleaseDate: DateTime.now.new_offset(0),
69
+ DiscontinuedDate: nil,
70
+ Rating: 4,
71
+ Price: 3.5
72
+ } }
73
+
74
+ it { expect(subject).to respond_to(:<<) }
75
+
76
+ it 'with an existing entity' do
77
+ WebMock.stub_request(:post, 'http://services.odata.org/OData/OData.svc/Products(0)').
78
+ to_return(status: 200, body: File.open('spec/fixtures/sample_service/product_0.xml'))
79
+
80
+ expect {subject << existing_entity}.to_not raise_error
81
+ end
82
+
83
+ it 'with a new entity' do
84
+ WebMock.stub_request(:post, 'http://services.odata.org/OData/OData.svc/Products').
85
+ to_return(status: 201, body: File.open('spec/fixtures/sample_service/product_9999.xml'))
86
+
87
+ expect(new_entity['ID']).to be_nil
88
+ expect {subject << new_entity}.to_not raise_error
89
+ expect(new_entity['ID']).to_not be_nil
90
+ expect(new_entity['ID']).to eq(9999)
91
+ end
92
+
93
+ it 'with a bad entity' do
94
+ WebMock.stub_request(:post, 'http://services.odata.org/OData/OData.svc/Products').
95
+ to_return(status: 400, body: nil)
96
+
97
+ expect {subject << bad_entity}.to raise_error(StandardError, 'Something went wrong committing your entity')
98
+ end
99
+ end
40
100
  end
@@ -39,5 +39,8 @@ describe OData::Entity do
39
39
  it { expect(subject['DiscontinuedDate']).to be_nil }
40
40
  it { expect(subject['Rating']).to eq(4) }
41
41
  it { expect(subject['Price']).to eq(2.5) }
42
+
43
+ it { expect {subject['NonExistant']}.to raise_error(ArgumentError) }
44
+ it { expect {subject['NonExistant'] = 5}.to raise_error(ArgumentError) }
42
45
  end
43
46
  end
@@ -59,27 +59,6 @@ describe OData::Service do
59
59
  it { expect(subject.namespace).to eq('ODataDemo') }
60
60
  end
61
61
 
62
- describe '#get' do
63
- describe 'a set of entities' do
64
- it { expect(subject.get(::Examples::Product).size).to eq(11) }
65
- it { expect(subject.get(::Examples::Product).first).to be_instance_of(::Examples::Product) }
66
- it { expect(subject.get(::Examples::Product).shuffle.first).to be_instance_of(::Examples::Product) }
67
- it { expect(subject.get(::Examples::Product).last).to be_instance_of(::Examples::Product) }
68
- end
69
-
70
- describe 'a specific entity by key' do
71
- it { expect(subject.get(::Examples::Product, {key: 0}).size).to eq(1) }
72
- it { expect(subject.get(::Examples::Product, {key: 0}).first).to be_instance_of(::Examples::Product) }
73
- it { expect(subject.get(::Examples::Product, {key: 0}).first.id).to eq(0) }
74
- it { expect(subject.get(::Examples::Product, {key: 0}).first.name).to eq('Bread') }
75
- it { expect(subject.get(::Examples::Product, {key: 0}).first.description).to eq('Whole grain bread') }
76
- it { expect(subject.get(::Examples::Product, {key: 0}).first.release_date).to eq(DateTime.parse('1992-01-01T00:00:00')) }
77
- it { expect(subject.get(::Examples::Product, {key: 0}).first.discontinued_date).to eq(nil) }
78
- it { expect(subject.get(::Examples::Product, {key: 0}).first.rating).to eq(4) }
79
- it { expect(subject.get(::Examples::Product, {key: 0}).first.price).to eq(2.5) }
80
- end
81
- end
82
-
83
62
  describe '#[]' do
84
63
  it { expect(subject['Products']).to be_a(OData::EntitySet) }
85
64
  it { expect {subject['Nonexistant']}.to raise_error(ArgumentError) }
data/spec/spec_helper.rb CHANGED
@@ -17,6 +17,8 @@ else
17
17
  WebMock.disable_net_connect!(allow_localhost: true, allow: 'codeclimate.com')
18
18
  end
19
19
 
20
+ require 'timecop'
21
+
20
22
  RSpec.configure do |config|
21
23
  if config.files_to_run.one?
22
24
  config.default_formatter = 'doc'
@@ -52,6 +54,9 @@ RSpec.configure do |config|
52
54
  WebMock.stub_request(:get, 'http://services.odata.org/OData/OData.svc/Products?$inlinecount=allpages&$skip=10&$top=5').
53
55
  to_return(status: 200, body: File.open('spec/fixtures/sample_service/products_skip10_top5.xml'))
54
56
 
57
+ WebMock.stub_request(:get, 'http://services.odata.org/OData/OData.svc/Products?$skip=0&$top=1').
58
+ to_return(status: 200, body: File.open('spec/fixtures/sample_service/first_product.xml'))
59
+
55
60
  WebMock.stub_request(:get, 'http://services.odata.org/OData/OData.svc/Products/$count').
56
61
  to_return(status: 200, :body => '11')
57
62
 
@@ -64,5 +69,6 @@ RSpec.configure do |config|
64
69
  # We're calling this as a private method because there should not be any
65
70
  # reasons to have to flush the service registry except in testing.
66
71
  OData::ServiceRegistry.instance.send(:flush)
72
+ WebMock.reset!
67
73
  end
68
74
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: odata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Thompson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-05 00:00:00.000000000 Z
11
+ date: 2014-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.18.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: timecop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.7.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.7.1
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: backports
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -161,6 +175,7 @@ files:
161
175
  - ".rspec"
162
176
  - ".ruby-version"
163
177
  - ".travis.yml"
178
+ - CHANGELOG.md
164
179
  - Gemfile
165
180
  - LICENSE.txt
166
181
  - README.md
@@ -168,7 +183,6 @@ files:
168
183
  - lib/odata.rb
169
184
  - lib/odata/entity.rb
170
185
  - lib/odata/entity_set.rb
171
- - lib/odata/model.rb
172
186
  - lib/odata/properties.rb
173
187
  - lib/odata/properties/binary.rb
174
188
  - lib/odata/properties/boolean.rb
@@ -187,16 +201,16 @@ files:
187
201
  - lib/odata/service_registry.rb
188
202
  - lib/odata/version.rb
189
203
  - odata.gemspec
190
- - spec/examples/product_model.rb
204
+ - spec/fixtures/sample_service/first_product.xml
191
205
  - spec/fixtures/sample_service/metadata.xml
192
206
  - spec/fixtures/sample_service/product_0.xml
207
+ - spec/fixtures/sample_service/product_9999.xml
193
208
  - spec/fixtures/sample_service/products.xml
194
209
  - spec/fixtures/sample_service/products_skip0_top5.xml
195
210
  - spec/fixtures/sample_service/products_skip10_top5.xml
196
211
  - spec/fixtures/sample_service/products_skip5_top5.xml
197
212
  - spec/odata/entity_set_spec.rb
198
213
  - spec/odata/entity_spec.rb
199
- - spec/odata/model_spec.rb
200
214
  - spec/odata/properties/binary_spec.rb
201
215
  - spec/odata/properties/boolean_spec.rb
202
216
  - spec/odata/properties/date_time_offset_spec.rb
@@ -236,16 +250,16 @@ signing_key:
236
250
  specification_version: 4
237
251
  summary: Simple OData library
238
252
  test_files:
239
- - spec/examples/product_model.rb
253
+ - spec/fixtures/sample_service/first_product.xml
240
254
  - spec/fixtures/sample_service/metadata.xml
241
255
  - spec/fixtures/sample_service/product_0.xml
256
+ - spec/fixtures/sample_service/product_9999.xml
242
257
  - spec/fixtures/sample_service/products.xml
243
258
  - spec/fixtures/sample_service/products_skip0_top5.xml
244
259
  - spec/fixtures/sample_service/products_skip10_top5.xml
245
260
  - spec/fixtures/sample_service/products_skip5_top5.xml
246
261
  - spec/odata/entity_set_spec.rb
247
262
  - spec/odata/entity_spec.rb
248
- - spec/odata/model_spec.rb
249
263
  - spec/odata/properties/binary_spec.rb
250
264
  - spec/odata/properties/boolean_spec.rb
251
265
  - spec/odata/properties/date_time_offset_spec.rb
data/lib/odata/model.rb DELETED
@@ -1,116 +0,0 @@
1
- require 'odata/property'
2
- require 'odata/properties'
3
-
4
- module OData
5
- # Provides a convenient way to represent OData entities as Ruby objects.
6
- # Instances that include this module gain the ability to define how to map
7
- # OData properties to attributes as well as the ability to query and persist
8
- # data to the underlying OData service.
9
- #
10
- # If the module detects the presence of Rails, along with ActiveModel::Model
11
- # it will automatically mixin support ActiveModel::Model to provide better
12
- # integration with Rails apps.
13
- module Model
14
- extend ::ActiveSupport::Concern
15
- include ::ActiveModel::Model if defined?(::Rails) && defined?(::ActiveModel::Model)
16
-
17
- included do
18
- cattr_accessor :registered_properties
19
- cattr_accessor :primary_key
20
-
21
- self.class_eval <<-EOS
22
- @@registered_properties = {}
23
- @@primary_key = nil
24
- EOS
25
- end
26
-
27
- # Converts supplied value based on its type
28
- #
29
- # @param value [*] usually a string representation of the value
30
- # @param type [String] OData type name as string
31
- # @return [*]
32
- def convert_value_for_type(value, type)
33
- return nil if value.nil? || type.nil?
34
- if type =~ /Int(16|32|64)$/
35
- value.to_i
36
- elsif type =~ /Boolean$/
37
- (value == 'true' || value == '1')
38
- elsif type =~ /(Decimal|Double|Single)$/
39
- value.to_f
40
- elsif type =~ /DateTime$/
41
- DateTime.parse(value)
42
- else
43
- value.to_s
44
- end
45
- end
46
-
47
- module ClassMethods
48
- # Returns the class' name
49
- def model_name
50
- self.to_s.demodulize
51
- end
52
-
53
- # Pluralizes #model_name for OData requests
54
- def odata_name
55
- model_name.pluralize
56
- end
57
-
58
- # Instantiates an OData::Model instance from a hash of attributes and
59
- # their details.
60
- #
61
- # @param feed_hash [Hash] attribute names as keys, and details as values
62
- # @return [OData::Model]
63
- def load_from_feed(feed_hash)
64
- loaded_instance = self.new
65
- feed_hash.each do |attribute_name, details|
66
- loaded_instance.instance_eval do
67
- @properties ||= {}
68
- @properties[attribute_name] = convert_value_for_type(details[:value], details[:type])
69
- end
70
- end
71
- loaded_instance
72
- end
73
-
74
- # Define a property and it's options
75
- #
76
- # @param name [to_s] the literal property name expected by the OData service
77
- # @param options [Hash] options for setting up the property
78
- def property(name, options = {})
79
- register_property(name.to_s.underscore, {literal_name: name}.merge(options))
80
- create_accessors(name.to_s.underscore, options)
81
- register_primary_key(name.to_s.underscore) if options[:primary_key]
82
- end
83
-
84
- private
85
-
86
- def register_property(name, options)
87
- new_registered_properties = registered_properties
88
- new_registered_properties[name.to_sym] = options
89
- registered_properties = new_registered_properties
90
- end
91
-
92
- def create_accessors(name, options)
93
- self.class_eval do
94
- if options[:entity_title]
95
- define_method(name) { @properties[:title] || nil }
96
- define_method("#{name}=") {|value| @properties[:title] = value}
97
- elsif options[:entity_summary]
98
- define_method(name) { @properties[:summary] || nil }
99
- define_method("#{name}=") {|value| @properties[:summary] = value}
100
- else
101
- define_method(name) { @properties[name.to_sym] || nil }
102
- define_method("#{name}=") {|value| @properties[name.to_sym] = value}
103
- end
104
- end
105
- end
106
-
107
- def register_primary_key(name)
108
- if primary_key.nil?
109
- self.class_eval <<-EOS
110
- @@primary_key = :#{name}
111
- EOS
112
- end
113
- end
114
- end
115
- end
116
- end
@@ -1,13 +0,0 @@
1
- module Examples
2
- class Product
3
- include OData::Model
4
-
5
- property 'ID', primary_key: true
6
- property 'Name', entity_title: true
7
- property 'Description', entity_summary: true
8
- property 'ReleaseDate'
9
- property 'DiscontinuedDate'
10
- property 'Rating'
11
- property 'Price'
12
- end
13
- end
@@ -1,25 +0,0 @@
1
- require 'spec_helper'
2
- require 'examples/product_model'
3
-
4
- describe OData::Model do
5
- describe 'class methods' do
6
- it { expect(::Examples::Product).to respond_to(:model_name) }
7
- it { expect(::Examples::Product.model_name).to eq('Product') }
8
- it { expect(::Examples::Product.odata_name).to eq('Products') }
9
- end
10
-
11
- describe 'defining properties' do
12
- let(:subject) { ::Examples::Product.new }
13
-
14
- it { expect(subject).to respond_to(:id) }
15
- it { expect(subject).to respond_to(:name) }
16
- it { expect(subject).to respond_to(:description) }
17
- it { expect(subject).to respond_to(:release_date) }
18
- it { expect(subject).to respond_to(:discontinued_date) }
19
- it { expect(subject).to respond_to(:rating) }
20
- it { expect(subject).to respond_to(:price) }
21
-
22
- it { expect(::Examples::Product).to respond_to(:primary_key) }
23
- it { expect(::Examples::Product.primary_key).to eq(:id)}
24
- end
25
- end