odata 0.0.10 → 0.1.0

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: 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