odata4 0.7.0 → 0.8.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: 1a49a6ff012ef0bb66a692514d673479303e198b
4
- data.tar.gz: 99cd07d3a97e6a972d81917949a2f4344745a735
3
+ metadata.gz: e28c4147df950462c72f8824c2868601941872c5
4
+ data.tar.gz: 8044f114aaa10c09719c3d3e5743f5f5c96de9d7
5
5
  SHA512:
6
- metadata.gz: f176202b1e8998dc076b21c33bcfde3ff95eb360cbff5fee4073aa9ba3d16334322c26801e3e3ed065101b7895b46bb418c4cddf21b3259f6663cff8f42c185c
7
- data.tar.gz: b0a5b08aa206f1cf4baf90ecf7e2fe19002ae81bfae49b27d29523266ee13dde9ca01e7d1dfd3471572f4bdec61c9613d75f6b60ccfc6ccafab59647c49fd114
6
+ metadata.gz: c10b8c4859b7f2acdbf33d5258f12e43970d24061f558afac558be6212a955e4c5b5118720de78e74ae22231f7bf5479a137591082a0568aee13f5a325350788
7
+ data.tar.gz: 8d73fc1f5877df150a21a28ca10871582e58657f0419726fadb1d2c514c62d09517ff1f7eb2d1559bd49d6aea8fb50435e7280bedd60ed174a9528c26d926f05
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.8.0
4
+
5
+ * [New Feature] Support for multiple schemas
6
+ * [Breaking] `Service#complex_types`, `Service#entity_types`,
7
+ `Service#enum_types` and `Service#entity_sets` now return fully qualified
8
+ type names
9
+ * [New Feature] Optional lenient property validation
10
+ * [Fixed] Incorrect URL representation for Decimal properties
11
+
3
12
  ## 0.7.0
4
13
 
5
14
  Major rewrite
data/README.md CHANGED
@@ -18,7 +18,7 @@ If you need a gem to integration with OData Version 3, you can use James Thompso
18
18
 
19
19
  Add this line to your application's `Gemfile`:
20
20
 
21
- gem 'odata4', git: 'https://github.com/wrstudios/odata4'
21
+ gem 'odata4'
22
22
 
23
23
  And then execute:
24
24
 
@@ -40,14 +40,14 @@ The nice thing about `OData4::Service` is that it automatically registers with t
40
40
  To create an `OData4::Service` simply provide the location of a service endpoint to it like this:
41
41
 
42
42
  ```ruby
43
- OData4::Service.open('http://services.odata.org/V4/OData/OData.service')
43
+ OData4::Service.open('http://services.odata.org/V4/OData/OData.svc')
44
44
  ```
45
45
 
46
46
  You may also provide an options hash after the URL.
47
47
  It is suggested that you supply a name for the service via this hash like so:
48
48
 
49
49
  ```ruby
50
- OData4::Service.open('http://services.odata.org/V4/OData/OData.service', name: 'ODataDemo')
50
+ OData4::Service.open('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo')
51
51
  ```
52
52
 
53
53
  This one call will setup the service and allow for the discovery of everything the other parts of the OData4 gem need to function.
@@ -57,7 +57,7 @@ Both of these methods are available on instances and will allow for lookup in th
57
57
  Using either the service URL or the name provided as an option when creating an `OData4::Service` will allow for quick lookup in the `OData4::ServiceRegistry` like such:
58
58
 
59
59
  ```ruby
60
- OData4::ServiceRegistry['http://services.odata.org/V4/OData/OData.service']
60
+ OData4::ServiceRegistry['http://services.odata.org/V4/OData/OData.svc']
61
61
  OData4::ServiceRegistry['ODataDemo']
62
62
  ```
63
63
 
@@ -65,6 +65,61 @@ Both of the above calls would retrieve the same service from the registry.
65
65
  At the moment there is no protection against name collisions provided in `OData4::ServiceRegistry`.
66
66
  So, looking up services by their service URL is the most exact method, but lookup by name is provided for convenience.
67
67
 
68
+ ### Exploring a Service
69
+
70
+ Once instantiated, you can request various information about the service, such as the names and types of entity sets it exposes, or the names of the entity types (and custom datatypes) it defines.
71
+
72
+ For example:
73
+
74
+ Get a list of available entity types
75
+
76
+ ```ruby
77
+ service.entity_types
78
+ # => [
79
+ # "ODataDemo.Product",
80
+ # "ODataDemo.FeaturedProduct",
81
+ # "ODataDemo.ProductDetail",
82
+ # "ODataDemo.Category",
83
+ # "ODataDemo.Supplier",
84
+ # "ODataDemo.Person",
85
+ # "ODataDemo.Customer",
86
+ # "ODataDemo.Employee",
87
+ # "ODataDemo.PersonDetail",
88
+ # "ODataDemo.Advertisement"
89
+ # ]
90
+ ```
91
+
92
+ Get a list of entity sets
93
+
94
+ ```ruby
95
+ service.entity_sets
96
+ # => {
97
+ # "Products" => "ODataDemo.Product",
98
+ # "ProductDetails" => "ODataDemo.ProductDetail",
99
+ # "Categories" => "ODataDemo.Category",
100
+ # "Suppliers" => "ODataDemo.Supplier",
101
+ # "Persons" => "ODataDemo.Person",
102
+ # "PersonDetails" => "ODataDemo.PersonDetail",
103
+ # "Advertisements" => "ODataDemo.Advertisement"
104
+ # }
105
+ ```
106
+
107
+ Get a list of complex types
108
+
109
+ ```ruby
110
+ service.complex_types
111
+ # => ["ODataDemo.Address"]
112
+ ```
113
+
114
+ Get a list of enum types
115
+
116
+ ```ruby
117
+ service.enum_types
118
+ # => ["ODataDemo.ProductStatus"]
119
+ ```
120
+
121
+ For more examples, refer to [usage_example_specs.rb](spec/odata4/usage_example_specs.rb).
122
+
68
123
  ### Authentication
69
124
 
70
125
  When authenticating with your service you can set parameters to the Typhoeus gem which uses libcurl.
@@ -73,7 +128,7 @@ Use the **:typhoeus** option to set your authentication.
73
128
  For example using **ntlm** authentication:
74
129
 
75
130
  ```ruby
76
- conn = OData4::Service.open('http://services.odata.org/V4/OData/OData.service', {
131
+ conn = OData4::Service.open('http://services.odata.org/V4/OData/OData.svc', {
77
132
  name: 'ODataDemo',
78
133
  typhoeus: {
79
134
  username: 'username',
@@ -95,7 +150,7 @@ You can speed your load time by forcing the service to load the metadata from a
95
150
  This is only recommended for testing purposes, as the metadata file can change.
96
151
 
97
152
  ```ruby
98
- conn = OData4::Service.open('http://services.odata.org/V4/OData/OData.service', {
153
+ conn = OData4::Service.open('http://services.odata.org/V4/OData/OData.svc', {
99
154
  name: 'ODataDemo',
100
155
  metadata_file: "metadata.xml",
101
156
  })
@@ -106,7 +161,7 @@ This is only recommended for testing purposes, as the metadata file can change.
106
161
  You can set the headers with the **:typhoeus** param like so:
107
162
 
108
163
  ```ruby
109
- conn = OData4::Service.open('http://services.odata.org/V4/OData/OData.service', {
164
+ conn = OData4::Service.open('http://services.odata.org/V4/OData/OData.svc', {
110
165
  name: 'ODataDemo',
111
166
  typhoeus: {
112
167
  headers: {
@@ -123,7 +178,7 @@ Under normal circumstances you should never need to worry about an `OData4::Enti
123
178
  For example, to get an `OData4::EntitySet` for the products in the ODataDemo service simply access the entity set through the service like this:
124
179
 
125
180
  ```ruby
126
- service = OData4::Service.open('http://services.odata.org/V4/OData/OData.service')
181
+ service = OData4::Service.open('http://services.odata.org/V4/OData/OData.svc')
127
182
  products = service['ProductsSet'] # => OData4::EntitySet
128
183
  ```
129
184
 
@@ -172,7 +227,7 @@ The first entity object returns a single entity object.
172
227
  #### Find a certain Entity
173
228
 
174
229
  ```ruby
175
- service['ProductsSet']['<guid of entity>']
230
+ service['ProductsSet']['<primary key of entity>']
176
231
  ```
177
232
 
178
233
  With certain navigation properties expanded (i.e. eagerly loaded):
@@ -239,6 +294,21 @@ or, you can get a hold of the property class instance using
239
294
 
240
295
  This will parse and instantiate the property if it hasn't done so yet.
241
296
 
297
+ ##### Lenient Property Validation
298
+
299
+ By default, we use strict property validation, meaning that any property validation errors in the data will raise an exception.
300
+ However, you may encounter OData implementations in the wild that break the specs in strange and surprising ways (shocking, I know!).
301
+
302
+ Since it's often better to get *some* data instead of nothing at all, you can optionally make the property validation lenient.
303
+ Simply add `strict: false` to the service constructor options.
304
+ In this mode, any property validation error will log a warning instead of raising an exception. The corresponding property value will be `nil` (even if the property is declared as not allowing NULL values).
305
+
306
+ ```ruby
307
+ service = OData4::Service.open('http://services.odata.org/V4/OData/OData.svc', strict: false)
308
+ # -- alternatively, for an existing service instance --
309
+ service.options[:strict] = false
310
+ ```
311
+
242
312
  ### Queries
243
313
 
244
314
  `OData4::Query` instances form the base for finding specific entities within an `OData4::EntitySet`.
@@ -266,6 +336,17 @@ You should refer to the published RubyDocs for full details on the various capab
266
336
  * [OData4::Query](http://rubydoc.info/github/wrstudios/odata4/master/OData4/Query)
267
337
  * [OData4::Query::Criteria](http://rubydoc.info/github/wrstudios/odata4/master/OData4/Query/Criteria)
268
338
 
339
+ ## To Do
340
+
341
+ [x] ~Lenient property validation~
342
+ [ ] Write support (create/update/delete)
343
+ [ ] Support for invoking [Operations][odata-ops] (Functions/Actions)
344
+ [ ] [Property facets][odata-facets]
345
+ [ ] Annotations
346
+
347
+ [odata-facets]: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752528
348
+ [odata-ops]: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752307
349
+
269
350
  ## Contributing
270
351
 
271
352
  1. Fork it (`https://github.com/[my-github-username]/odata/fork`)
@@ -19,11 +19,13 @@ require 'odata4/navigation_property'
19
19
  require 'odata4/complex_type'
20
20
  require 'odata4/enum_type'
21
21
  require 'odata4/entity'
22
+ require 'odata4/entity_container'
22
23
  require 'odata4/entity_set'
23
24
  require 'odata4/query/criteria'
24
25
  require 'odata4/query/result'
25
26
  require 'odata4/query/in_batches'
26
27
  require 'odata4/query'
28
+ require 'odata4/schema'
27
29
  require 'odata4/service'
28
30
  require 'odata4/service_registry'
29
31
 
@@ -9,10 +9,9 @@ module OData4
9
9
  # Creates a new ComplexType based on the supplied options.
10
10
  # @param type_xml [Nokogiri::XML::Element]
11
11
  # @param service [OData4::Service]
12
- # @return [self]
13
- def initialize(type_definition, service)
12
+ def initialize(type_definition, schema)
14
13
  @type_definition = type_definition
15
- @service = service
14
+ @schema = schema
16
15
  end
17
16
 
18
17
  # The name of the ComplexType
@@ -58,8 +57,12 @@ module OData4
58
57
 
59
58
  private
60
59
 
60
+ def schema
61
+ @schema
62
+ end
63
+
61
64
  def service
62
- @service
65
+ @schema.service
63
66
  end
64
67
 
65
68
  def type_definition
@@ -68,7 +71,7 @@ module OData4
68
71
 
69
72
  def collect_properties
70
73
  Hash[type_definition.xpath('./Property').map do |property_xml|
71
- property_name, property = service.send(:process_property_from_xml,property_xml)
74
+ property_name, property = schema.send(:process_property_from_xml, property_xml)
72
75
  [property_name, property]
73
76
  end]
74
77
  end
@@ -7,8 +7,6 @@ module OData4
7
7
  class Entity
8
8
  # The Entity type name
9
9
  attr_reader :type
10
- # The OData4::Service's namespace
11
- attr_reader :namespace
12
10
  # The OData4::Service's identifying name
13
11
  attr_reader :service_name
14
12
  # The entity set this entity belongs to
@@ -31,7 +29,6 @@ module OData4
31
29
  def initialize(options = {})
32
30
  @id = options[:id]
33
31
  @type = options[:type]
34
- @namespace = options[:namespace]
35
32
  @service_name = options[:service_name]
36
33
  @entity_set = options[:entity_set]
37
34
  @context = options[:context]
@@ -39,10 +36,14 @@ module OData4
39
36
  @errors = []
40
37
  end
41
38
 
39
+ def namespace
40
+ @namespace ||= type.rpartition('.').first
41
+ end
42
+
42
43
  # Returns name of Entity from Service specified type.
43
44
  # @return [String]
44
45
  def name
45
- @name ||= type.gsub(/#{namespace}\./, '')
46
+ @name ||= type.split('.').last
46
47
  end
47
48
 
48
49
  # Returns context URL for this entity
@@ -110,7 +111,7 @@ module OData4
110
111
  # Links to other OData4 entitites
111
112
  # @return [Hash]
112
113
  def links
113
- @links ||= service.navigation_properties[name].map do |nav_name, details|
114
+ @links ||= schema.navigation_properties[name].map do |nav_name, details|
114
115
  [
115
116
  nav_name,
116
117
  { type: details.nav_type, href: "#{id}/#{nav_name}" }
@@ -125,7 +126,7 @@ module OData4
125
126
  def self.with_properties(new_properties = {}, options = {})
126
127
  entity = OData4::Entity.new(options)
127
128
  entity.instance_eval do
128
- service.properties_for_entity(name).each do |property_name, instance|
129
+ service.properties_for_entity(type).each do |property_name, instance|
129
130
  set_property(property_name, instance)
130
131
  end
131
132
 
@@ -213,7 +214,7 @@ module OData4
213
214
  # Returns the primary key for the Entity.
214
215
  # @return [String]
215
216
  def primary_key
216
- service.primary_key_for(name)
217
+ schema.primary_key_for(name)
217
218
  end
218
219
 
219
220
  def is_new?
@@ -228,16 +229,20 @@ module OData4
228
229
  @service ||= OData4::ServiceRegistry[service_name]
229
230
  end
230
231
 
232
+ def schema
233
+ @schema ||= service.schemas[namespace]
234
+ end
235
+
231
236
  private
232
237
 
233
238
  def instantiate_property(property_name, value_xml)
234
- value_type = service.get_property_type(name, property_name)
239
+ value_type = schema.get_property_type(name, property_name)
235
240
  klass = ::OData4::PropertyRegistry[value_type]
236
241
 
237
242
  if klass.nil?
238
243
  raise RuntimeError, "Unknown property type: #{value_type}"
239
244
  else
240
- klass.from_xml(value_xml)
245
+ klass.from_xml(value_xml, service: service)
241
246
  end
242
247
  end
243
248
 
@@ -280,7 +285,7 @@ module OData4
280
285
  def self.process_links(entity, xml_doc)
281
286
  entity.instance_eval do
282
287
  new_links = instance_variable_get(:@links) || {}
283
- service.navigation_properties[name].each do |nav_name, details|
288
+ schema.navigation_properties[name].each do |nav_name, details|
284
289
  xml_doc.xpath("./link[@title='#{nav_name}']").each do |node|
285
290
  next if node.attributes['type'].nil?
286
291
  next unless node.attributes['type'].value =~ /^application\/atom\+xml;type=(feed|entry)$/i
@@ -304,7 +309,7 @@ module OData4
304
309
  def self.process_metadata(entity, metadata)
305
310
  entity.instance_eval do
306
311
  new_links = instance_variable_get(:@links) || {}
307
- service.navigation_properties[name].each do |nav_name, details|
312
+ schema.navigation_properties[name].each do |nav_name, details|
308
313
  href = metadata["#{nav_name}@odata.navigationLink"]
309
314
  next if href.nil?
310
315
  new_links[nav_name] = {
@@ -0,0 +1,75 @@
1
+ module OData4
2
+ #
3
+ class EntityContainer
4
+ # The EntityContainer's parent service
5
+ attr_reader :service
6
+ # The EntityContainer's metadata
7
+ attr_reader :metadata
8
+
9
+ # Creates a new EntityContainer
10
+ # @param service [OData4::Service] The entity container's parent service
11
+ def initialize(service)
12
+ @metadata = service.metadata.xpath('//EntityContainer').first
13
+ @service = service
14
+ end
15
+
16
+ # The EntityContainer's surrounding Schema
17
+ # @return [Nokogiri::XML]
18
+ def schema
19
+ @schema ||= metadata.ancestors('Schema').first
20
+ end
21
+
22
+ # Returns the EntityContainer's namespace.
23
+ # @return [String]
24
+ def namespace
25
+ @namespace ||= schema.attributes['Namespace'].value
26
+ end
27
+
28
+ # Returns the EntityContainer's name.
29
+ # @return [String]
30
+ def name
31
+ @name ||= metadata.attributes['Name'].value
32
+ end
33
+
34
+ # Returns a hash of EntitySet names and their respective EntityTypes.
35
+ # @return [Hash<String, String>]
36
+ def entity_sets
37
+ @entity_sets ||= metadata.xpath('//EntitySet').map do |entity|
38
+ [
39
+ entity.attributes['Name'].value,
40
+ entity.attributes['EntityType'].value
41
+ ]
42
+ end.to_h
43
+ end
44
+
45
+ # Retrieves the EntitySet associated with a specific EntityType by name
46
+ #
47
+ # @param entity_set_name [to_s] the name of the EntitySet desired
48
+ # @return [OData4::EntitySet] an OData4::EntitySet to query
49
+ def [](entity_set_name)
50
+ xpath_query = "//EntitySet[@Name='#{entity_set_name}']"
51
+ entity_set_node = metadata.xpath(xpath_query).first
52
+ raise ArgumentError, "Unknown Entity Set: #{entity_set_name}" if entity_set_node.nil?
53
+ entity_type = entity_set_node.attributes['EntityType'].value
54
+ OData4::EntitySet.new(
55
+ name: entity_set_name,
56
+ namespace: namespace,
57
+ type: entity_type,
58
+ service_name: service.name,
59
+ container: name
60
+ )
61
+ end
62
+
63
+ def singletons
64
+ # TODO return singletons exposed by this EntityContainer
65
+ end
66
+
67
+ def actions
68
+ # TODO return action imports exposed by this EntityContainer
69
+ end
70
+
71
+ def functions
72
+ # TODO return function imports exposed by this EntityContainer
73
+ end
74
+ end
75
+ end
@@ -115,7 +115,6 @@ module OData4
115
115
  # @api private
116
116
  def entity_options
117
117
  {
118
- namespace: namespace,
119
118
  service_name: service_name,
120
119
  type: type,
121
120
  entity_set: self
@@ -8,9 +8,9 @@ module OData4
8
8
  # @param type_xml [Nokogiri::XML::Element]
9
9
  # @param service [OData4::Service]
10
10
  # @return [self]
11
- def initialize(type_definition, service)
11
+ def initialize(type_definition, schema)
12
12
  @type_definition = type_definition
13
- @service = service
13
+ @schema = schema
14
14
  end
15
15
 
16
16
  # The name of the EnumType
@@ -71,7 +71,7 @@ module OData4
71
71
  private
72
72
 
73
73
  def service
74
- @service
74
+ @schema.service
75
75
  end
76
76
 
77
77
  def type_definition