ruby_odata 0.1.0 → 0.1.1

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.
Files changed (62) hide show
  1. data/.gitignore +3 -2
  2. data/.travis.yml +2 -1
  3. data/.yardopts +6 -0
  4. data/CHANGELOG.md +102 -0
  5. data/Guardfile +14 -0
  6. data/README.md +285 -0
  7. data/Rakefile +0 -7
  8. data/features/basic_auth.feature +3 -2
  9. data/features/batch_request.feature +7 -6
  10. data/features/cassettes/basic_auth_protected_resource.yml +57 -0
  11. data/features/cassettes/batch_request_additions.yml +69 -0
  12. data/features/cassettes/batch_request_deletes.yml +69 -0
  13. data/features/cassettes/batch_request_updates.yml +69 -0
  14. data/features/cassettes/clean_database_for_testing.yml +46 -0
  15. data/features/cassettes/cucumber_tags/basic_auth.yml +297 -0
  16. data/features/cassettes/cucumber_tags/batch_request.yml +1459 -0
  17. data/features/cassettes/cucumber_tags/complex_types.yml +326 -0
  18. data/features/cassettes/cucumber_tags/error_handling.yml +64 -0
  19. data/features/cassettes/cucumber_tags/query_builder.yml +2025 -0
  20. data/features/cassettes/cucumber_tags/service.yml +234 -0
  21. data/features/cassettes/cucumber_tags/service_manage.yml +937 -0
  22. data/features/cassettes/cucumber_tags/service_methods.yml +647 -0
  23. data/features/cassettes/cucumber_tags/ssl.yml +203 -0
  24. data/features/cassettes/cucumber_tags/type_conversion.yml +337 -0
  25. data/features/cassettes/service_manage_additions.yml +65 -0
  26. data/features/cassettes/service_manage_deletions.yml +58 -0
  27. data/features/cassettes/service_manage_deletions_2.yml +58 -0
  28. data/features/cassettes/unsecured_metadata.yml +89 -0
  29. data/features/complex_types.feature +4 -3
  30. data/features/error_handling.feature +14 -0
  31. data/features/query_builder.feature +30 -9
  32. data/features/service.feature +4 -3
  33. data/features/service_manage.feature +6 -5
  34. data/features/service_methods.feature +3 -2
  35. data/features/ssl.feature +8 -8
  36. data/features/step_definitions/service_steps.rb +38 -24
  37. data/features/support/env.rb +1 -3
  38. data/features/support/hooks.rb +3 -2
  39. data/features/support/pickle.rb +29 -18
  40. data/features/support/vcr.rb +24 -0
  41. data/features/type_conversion.feature +16 -17
  42. data/lib/ruby_odata/association.rb +7 -6
  43. data/lib/ruby_odata/class_builder.rb +6 -7
  44. data/lib/ruby_odata/exceptions.rb +4 -0
  45. data/lib/ruby_odata/helpers.rb +11 -0
  46. data/lib/ruby_odata/operation.rb +5 -6
  47. data/lib/ruby_odata/property_metadata.rb +4 -5
  48. data/lib/ruby_odata/query_builder.rb +98 -63
  49. data/lib/ruby_odata/service.rb +118 -103
  50. data/lib/ruby_odata/version.rb +3 -1
  51. data/lib/ruby_odata.rb +20 -18
  52. data/ruby_odata.gemspec +16 -12
  53. data/spec/query_builder_spec.rb +78 -14
  54. data/spec/service_spec.rb +83 -83
  55. data/spec/support/sample_service_matcher.rb +15 -0
  56. data/test/RubyODataService/RubyODataService/App_Data/.gitkeep +0 -0
  57. data/test/blueprints.rb +15 -9
  58. data/test/usage_samples/querying.rb +5 -1
  59. data/test/usage_samples/sample_data.rb +1 -3
  60. metadata +213 -39
  61. data/CHANGELOG.rdoc +0 -88
  62. data/README.rdoc +0 -259
@@ -1,17 +1,16 @@
1
1
  module OData
2
-
2
+ # The main service class, also known as a *Context*
3
3
  class Service
4
4
  attr_reader :classes, :class_metadata, :options, :collections, :edmx, :function_imports
5
5
  # Creates a new instance of the Service class
6
6
  #
7
- # ==== Required Attributes
8
- # - service_uri: The root URI of the OData service
9
- # ==== Options in options hash
10
- # - username: username for http basic auth
11
- # - password: password for http basic auth
12
- # - verify_ssl: false if no verification, otherwise mode (OpenSSL::SSL::VERIFY_PEER is default)
13
- # - additional_params: a hash of query string params that will be passed on all calls
14
- # - eager_partial: true (default) if queries should consume partial feeds until the feed is complete, false if explicit calls to next must be performed
7
+ # @param [String] service_uri the root URI of the OData service
8
+ # @param [Hash] options the options to pass to the service
9
+ # @option options [String] :username for http basic auth
10
+ # @option options [String] :password for http basic auth
11
+ # @option options [Object] :verify_ssl false if no verification, otherwise mode (OpenSSL::SSL::VERIFY_PEER is default)
12
+ # @option options [Hash] :additional_params a hash of query string params that will be passed on all calls
13
+ # @option options [Boolean, true] :eager_partial true if queries should consume partial feeds until the feed is complete, false if explicit calls to next must be performed
15
14
  def initialize(service_uri, options = {})
16
15
  @uri = service_uri.gsub!(/\/?$/, '')
17
16
  set_options! options
@@ -20,7 +19,7 @@ class Service
20
19
  build_collections_and_classes
21
20
  end
22
21
 
23
- # Handles the dynamic AddTo<EntityName> methods as well as the collections on the service
22
+ # Handles the dynamic `AddTo<EntityName>` methods as well as the collections on the service
24
23
  def method_missing(name, *args)
25
24
  # Queries
26
25
  if @collections.include?(name.to_s)
@@ -45,31 +44,29 @@ class Service
45
44
 
46
45
  # Queues an object for deletion. To actually remove it from the server, you must call save_changes as well.
47
46
  #
48
- # ==== Required Attributes
49
- # - obj: The object to mark for deletion
47
+ # @param [Object] obj the object to mark for deletion
50
48
  #
51
- # Note: This method will throw an exception if the +obj+ isn't a tracked entity
49
+ # @raise [NotSupportedError] if the `obj` isn't a tracked entity
52
50
  def delete_object(obj)
53
51
  type = obj.class.to_s
54
52
  if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
55
53
  @save_operations << Operation.new("Delete", type, obj)
56
54
  else
57
- raise "You cannot delete a non-tracked entity"
55
+ raise OData::NotSupportedError.new "You cannot delete a non-tracked entity"
58
56
  end
59
57
  end
60
58
 
61
59
  # Queues an object for update. To actually update it on the server, you must call save_changes as well.
62
60
  #
63
- # ==== Required Attributes
64
- # - obj: The object to queue for update
61
+ # @param [Object] obj the object to queue for update
65
62
  #
66
- # Note: This method will throw an exception if the +obj+ isn't a tracked entity
63
+ # @raise [NotSupportedError] if the `obj` isn't a tracked entity
67
64
  def update_object(obj)
68
65
  type = obj.class.to_s
69
66
  if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
70
67
  @save_operations << Operation.new("Update", type, obj)
71
68
  else
72
- raise "You cannot update a non-tracked entity"
69
+ raise OData::NotSupportedError.new "You cannot update a non-tracked entity"
73
70
  end
74
71
  end
75
72
 
@@ -79,22 +76,28 @@ class Service
79
76
 
80
77
  result = nil
81
78
 
82
- if @save_operations.length == 1
83
- result = single_save(@save_operations[0])
84
- else
85
- result = batch_save(@save_operations)
86
- end
79
+ begin
80
+ if @save_operations.length == 1
81
+ result = single_save(@save_operations[0])
82
+ else
83
+ result = batch_save(@save_operations)
84
+ end
87
85
 
88
- # TODO: We should probably perform a check here
89
- # to make sure everything worked before clearing it out
90
- @save_operations.clear
86
+ # TODO: We should probably perform a check here
87
+ # to make sure everything worked before clearing it out
88
+ @save_operations.clear
91
89
 
92
- return result
90
+ return result
91
+ rescue Exception => e
92
+ handle_exception(e)
93
+ end
93
94
  end
94
95
 
95
- # Performs query operations (Read) against the server, returns an array of record instances.
96
+ # Performs query operations (Read) against the server.
97
+ # Typically this returns an array of record instances, except in the case of count queries
96
98
  def execute
97
99
  result = RestClient::Resource.new(build_query_uri, @rest_options).get
100
+ return Integer(result) if result =~ /^\d+$/
98
101
  handle_collection_result(result)
99
102
  end
100
103
 
@@ -118,7 +121,7 @@ class Service
118
121
  end
119
122
  end
120
123
 
121
- # Retrieves the next resultset of a partial result (if any). Does not honor the :eager_partial option.
124
+ # Retrieves the next resultset of a partial result (if any). Does not honor the `:eager_partial` option.
122
125
  def next
123
126
  return if not partial?
124
127
  handle_partial
@@ -131,14 +134,13 @@ class Service
131
134
 
132
135
  # Lazy loads a navigation property on a model
133
136
  #
134
- # ==== Required Attributes
135
- # - obj: The object to fill
136
- # - nav_prop: The navigation property to fill
137
+ # @param [Object] obj the object to fill
138
+ # @param [String] nav_prop the navigation property to fill
137
139
  #
138
- # Note: This method will throw an exception if the +obj+ isn't a tracked entity
139
- # Note: This method will throw an exception if the +nav_prop+ isn't a valid navigation property
140
+ # @raise [NotSupportedError] if the `obj` isn't a tracked entity
141
+ # @raise [ArgumentError] if the `nav_prop` isn't a valid navigation property
140
142
  def load_property(obj, nav_prop)
141
- raise ArgumentError, "You cannot load a property on an entity that isn't tracked" if obj.send(:__metadata).nil?
143
+ raise NotSupportedError, "You cannot load a property on an entity that isn't tracked" if obj.send(:__metadata).nil?
142
144
  raise ArgumentError, "'#{nav_prop}' is not a valid navigation property" unless obj.respond_to?(nav_prop.to_sym)
143
145
  raise ArgumentError, "'#{nav_prop}' is not a valid navigation property" unless @class_metadata[obj.class.to_s][nav_prop].nav_prop
144
146
  results = RestClient::Resource.new(build_load_property_uri(obj, nav_prop), @rest_options).get
@@ -148,15 +150,17 @@ class Service
148
150
 
149
151
  # Adds a child object to a parent object's collection
150
152
  #
151
- # ==== Required Attributes
152
- # - parent: The parent object
153
- # - nav_prop: The name of the navigation property to add the child to
154
- # - child: The child object
153
+ # @param [Object] parent the parent object
154
+ # @param [String] nav_prop the name of the navigation property to add the child to
155
+ # @param [Object] child the child object
156
+ # @raise [NotSupportedError] if the `parent` isn't a tracked entity
157
+ # @raise [ArgumentError] if the `nav_prop` isn't a valid navigation property
158
+ # @raise [NotSupportedError] if the `child` isn't a tracked entity
155
159
  def add_link(parent, nav_prop, child)
156
- raise ArgumentError, "You cannot add a link on an entity that isn't tracked (#{parent.class})" if parent.send(:__metadata).nil?
160
+ raise NotSupportedError, "You cannot add a link on an entity that isn't tracked (#{parent.class})" if parent.send(:__metadata).nil?
157
161
  raise ArgumentError, "'#{nav_prop}' is not a valid navigation property for #{parent.class}" unless parent.respond_to?(nav_prop.to_sym)
158
162
  raise ArgumentError, "'#{nav_prop}' is not a valid navigation property for #{parent.class}" unless @class_metadata[parent.class.to_s][nav_prop].nav_prop
159
- raise ArgumentError, "You cannot add a link on a child entity that isn't tracked (#{child.class})" if child.send(:__metadata).nil?
163
+ raise NotSupportedError, "You cannot add a link on a child entity that isn't tracked (#{child.class})" if child.send(:__metadata).nil?
160
164
  @save_operations << Operation.new("AddLink", nav_prop, parent, child)
161
165
  end
162
166
 
@@ -179,16 +183,16 @@ class Service
179
183
  @has_partial = false
180
184
  @next_uri = nil
181
185
  end
182
-
186
+
183
187
  def set_namespaces
184
188
  @edmx = Nokogiri::XML(RestClient::Resource.new(build_metadata_uri, @rest_options).get)
185
- @ds_namespaces = {
189
+ @ds_namespaces = {
186
190
  "m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata",
187
191
  "edmx" => "http://schemas.microsoft.com/ado/2007/06/edmx",
188
192
  "ds" => "http://schemas.microsoft.com/ado/2007/08/dataservices",
189
193
  "atom" => "http://www.w3.org/2005/Atom"
190
194
  }
191
-
195
+
192
196
  # Get the edm namespace from the edmx
193
197
  edm_ns = @edmx.xpath("edmx:Edmx/edmx:DataServices/*", @namespaces).first.namespaces['xmlns'].to_s
194
198
  @ds_namespaces.merge! "edm" => edm_ns
@@ -232,7 +236,7 @@ class Service
232
236
  entity_type = c["EntityType"]
233
237
  @collections[c["Name"]] = { :edmx_type => entity_type, :type => convert_to_local_type(entity_type) }
234
238
  end
235
-
239
+
236
240
  build_function_imports
237
241
  end
238
242
 
@@ -258,9 +262,9 @@ class Service
258
262
  parameters[p["Name"]] = p["Type"]
259
263
  end
260
264
  end
261
- @function_imports[f["Name"]] = {
262
- :http_method => http_method,
263
- :return_type => return_type,
265
+ @function_imports[f["Name"]] = {
266
+ :http_method => http_method,
267
+ :return_type => return_type,
264
268
  :inner_return_type => inner_return_type,
265
269
  :parameters => parameters }
266
270
  end
@@ -294,7 +298,7 @@ class Service
294
298
  end
295
299
  metadata
296
300
  end
297
-
301
+
298
302
  # Handle parsing of OData Atom result and return an array of Entry classes
299
303
  def handle_collection_result(result)
300
304
  results = build_classes_from_result(result)
@@ -303,7 +307,18 @@ class Service
303
307
  end
304
308
  results
305
309
  end
306
-
310
+
311
+ # Handles errors from the OData service
312
+ def handle_exception(e)
313
+ raise e unless e.response
314
+
315
+ code = e.http_code
316
+ error = Nokogiri::XML(e.response)
317
+
318
+ message = error.xpath("m:error/m:message", @ds_namespaces).first.content
319
+ raise "HTTP Error #{code}: #{message}"
320
+ end
321
+
307
322
  # Loops through the standard properties (non-navigation) for a given class and returns the appropriate list of methods
308
323
  def collect_properties(klass_name, element, doc)
309
324
  props = element.xpath(".//edm:Property", @ds_namespaces)
@@ -318,7 +333,7 @@ class Service
318
333
  end
319
334
  methods
320
335
  end
321
-
336
+
322
337
  # Similar to +collect_properties+, but handles the navigation properties
323
338
  def collect_navigation_properties(klass_name, element, doc)
324
339
  nav_props = element.xpath(".//edm:NavigationProperty", @ds_namespaces)
@@ -329,14 +344,14 @@ class Service
329
344
  # Helper to loop through a result and create an instance for each entity in the results
330
345
  def build_classes_from_result(result)
331
346
  doc = Nokogiri::XML(result)
332
-
347
+
333
348
  is_links = doc.at_xpath("/ds:links", @ds_namespaces)
334
349
  return parse_link_results(doc) if is_links
335
-
350
+
336
351
  entries = doc.xpath("//atom:entry[not(ancestor::atom:entry)]", @ds_namespaces)
337
-
352
+
338
353
  extract_partial(doc)
339
-
354
+
340
355
  results = []
341
356
  entries.each do |entry|
342
357
  results << entry_to_class(entry)
@@ -348,24 +363,24 @@ class Service
348
363
  def entry_to_class(entry)
349
364
  # Retrieve the class name from the fully qualified name (the last string after the last dot)
350
365
  klass_name = entry.xpath("./atom:category/@term", @ds_namespaces).to_s.split('.')[-1]
351
-
366
+
352
367
  # Is the category missing? See if there is a title that we can use to build the class
353
368
  if klass_name.nil?
354
369
  title = entry.xpath("./atom:title", @ds_namespaces).first
355
370
  return nil if title.nil?
356
371
  klass_name = title.content.to_s
357
372
  end
358
-
373
+
359
374
  return nil if klass_name.nil?
360
375
 
361
- # If we are working against a child (inline) entry, we need to use the more generic xpath because a child entry WILL
376
+ # If we are working against a child (inline) entry, we need to use the more generic xpath because a child entry WILL
362
377
  # have properties that are ancestors of m:inline. Check if there is an m:inline child to determine the xpath query to use
363
378
  has_inline = entry.xpath(".//m:inline", @ds_namespaces).any?
364
379
  properties_xpath = has_inline ? ".//m:properties[not(ancestor::m:inline)]/*" : ".//m:properties/*"
365
380
  properties = entry.xpath(properties_xpath, @ds_namespaces)
366
381
 
367
382
  klass = @classes[qualify_class_name(klass_name)].new
368
-
383
+
369
384
  # Fill metadata
370
385
  meta_id = entry.xpath("./atom:id", @ds_namespaces)[0].content
371
386
  klass.send :__metadata=, { :uri => meta_id }
@@ -375,7 +390,7 @@ class Service
375
390
  prop_name = prop.name
376
391
  klass.send "#{prop_name}=", parse_value(prop)
377
392
  end
378
-
393
+
379
394
  # Fill properties represented outside of the properties collection
380
395
  @class_metadata[qualify_class_name(klass_name)].select { |k,v| v.fc_keep_in_content == false }.each do |k, meta|
381
396
  if meta.fc_target_path == "SyndicationTitle"
@@ -386,13 +401,13 @@ class Service
386
401
  klass.send "#{meta.name}=", summary.content
387
402
  end
388
403
  end
389
-
404
+
390
405
  inline_links = entry.xpath("./atom:link[m:inline]", @ds_namespaces)
391
-
406
+
392
407
  for link in inline_links
393
408
  inline_entries = link.xpath(".//atom:entry", @ds_namespaces)
394
409
 
395
- # TODO: Use the metadata's associations to determine the multiplicity instead of this "hack"
410
+ # TODO: Use the metadata's associations to determine the multiplicity instead of this "hack"
396
411
  property_name = link.attributes['title'].to_s
397
412
  if inline_entries.length == 1 && singular?(property_name)
398
413
  inline_klass = build_inline_class(klass, inline_entries[0], property_name)
@@ -412,17 +427,17 @@ class Service
412
427
  klass.send "#{property_name}=", inline_classes
413
428
  end
414
429
  end
415
-
430
+
416
431
  klass
417
432
  end
418
-
433
+
419
434
  # Tests for and extracts the next href of a partial
420
435
  def extract_partial(doc)
421
436
  next_links = doc.xpath('//atom:link[@rel="next"]', @ds_namespaces)
422
437
  @has_partial = next_links.any?
423
438
  @next_uri = next_links[0]['href'] if @has_partial
424
439
  end
425
-
440
+
426
441
  def handle_partial
427
442
  if @next_uri
428
443
  result = RestClient::Resource.new(@next_uri, @rest_options).get
@@ -430,7 +445,7 @@ class Service
430
445
  end
431
446
  results
432
447
  end
433
-
448
+
434
449
  # Handle link results
435
450
  def parse_link_results(doc)
436
451
  uris = doc.xpath("/ds:links/ds:uri", @ds_namespaces)
@@ -440,7 +455,7 @@ class Service
440
455
  results << URI.parse(link)
441
456
  end
442
457
  results
443
- end
458
+ end
444
459
 
445
460
  # Build URIs
446
461
  def build_metadata_uri
@@ -470,7 +485,7 @@ class Service
470
485
  def build_batch_uri
471
486
  uri = "#{@uri}/$batch"
472
487
  uri << "?#{@additional_params.to_query}" unless @additional_params.empty?
473
- uri
488
+ uri
474
489
  end
475
490
  def build_load_property_uri(obj, property)
476
491
  uri = obj.__metadata[:uri]
@@ -483,11 +498,11 @@ class Service
483
498
  uri << "?#{params.to_query}" unless params.empty?
484
499
  uri
485
500
  end
486
-
501
+
487
502
  def build_inline_class(klass, entry, property_name)
488
503
  # Build the class
489
504
  inline_klass = entry_to_class(entry)
490
-
505
+
491
506
  # Add the property
492
507
  klass.send "#{property_name}=", inline_klass
493
508
  end
@@ -543,20 +558,20 @@ class Service
543
558
  def generate_guid
544
559
  rand(36**12).to_s(36).insert(4, "-").insert(9, "-")
545
560
  end
546
- def batch_save(operations)
561
+ def batch_save(operations)
547
562
  batch_num = generate_guid
548
563
  changeset_num = generate_guid
549
564
  batch_uri = build_batch_uri
550
-
565
+
551
566
  body = build_batch_body(operations, batch_num, changeset_num)
552
567
  result = RestClient::Resource.new( batch_uri, @rest_options).post body, {:content_type => "multipart/mixed; boundary=batch_#{batch_num}"}
553
568
 
554
- # TODO: More result validation needs to be done.
569
+ # TODO: More result validation needs to be done.
555
570
  # The result returns HTTP 202 even if there is an error in the batch
556
571
  return (result.code == 202)
557
572
  end
558
573
  def build_batch_body(operations, batch_num, changeset_num)
559
- # Header
574
+ # Header
560
575
  body = "--batch_#{batch_num}\n"
561
576
  body << "Content-Type: multipart/mixed;boundary=changeset_#{changeset_num}\n\n"
562
577
 
@@ -565,51 +580,51 @@ class Service
565
580
  body << build_batch_operation(operation, changeset_num)
566
581
  body << "\n"
567
582
  end
568
-
569
- # Footer
583
+
584
+ # Footer
570
585
  body << "\n\n--changeset_#{changeset_num}--\n"
571
586
  body << "--batch_#{batch_num}--"
572
-
587
+
573
588
  return body
574
589
  end
575
590
  def build_batch_operation(operation, changeset_num)
576
591
  accept_headers = "Accept-Charset: utf-8\n"
577
592
  accept_headers << "Content-Type: application/json;charset=utf-8\n" unless operation.kind == "Delete"
578
593
  accept_headers << "\n"
579
-
594
+
580
595
  content = "--changeset_#{changeset_num}\n"
581
596
  content << "Content-Type: application/http\n"
582
597
  content << "Content-Transfer-Encoding: binary\n\n"
583
-
584
- if operation.kind == "Add"
598
+
599
+ if operation.kind == "Add"
585
600
  save_uri = "#{@uri}/#{operation.klass_name}"
586
601
  json_klass = operation.klass.to_json(:type => :add)
587
-
602
+
588
603
  content << "POST #{save_uri} HTTP/1.1\n"
589
604
  content << accept_headers
590
605
  content << json_klass
591
606
  elsif operation.kind == "Update"
592
607
  update_uri = operation.klass.send(:__metadata)[:uri]
593
608
  json_klass = operation.klass.to_json
594
-
609
+
595
610
  content << "PUT #{update_uri} HTTP/1.1\n"
596
611
  content << accept_headers
597
612
  content << json_klass
598
613
  elsif operation.kind == "Delete"
599
614
  delete_uri = operation.klass.send(:__metadata)[:uri]
600
-
615
+
601
616
  content << "DELETE #{delete_uri} HTTP/1.1\n"
602
617
  content << accept_headers
603
618
  elsif
604
619
  save_uri = build_add_link_uri(operation)
605
620
  json_klass = operation.child_klass.to_json(:type => :link)
606
-
621
+
607
622
  content << "POST #{save_uri} HTTP/1.1\n"
608
623
  content << accept_headers
609
624
  content << json_klass
610
625
  link_child_to_parent(operation)
611
626
  end
612
-
627
+
613
628
  return content
614
629
  end
615
630
 
@@ -628,24 +643,24 @@ class Service
628
643
  end
629
644
 
630
645
  # Field Converters
631
-
646
+
632
647
  # Handles parsing datetimes from a string
633
648
  def parse_date(sdate)
634
649
  # Assume this is UTC if no timezone is specified
635
650
  sdate = sdate + "Z" unless sdate.match(/Z|([+|-]\d{2}:\d{2})$/)
636
-
651
+
637
652
  # This is to handle older versions of Ruby (e.g. ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mingw32])
638
653
  # See http://makandra.com/notes/1017-maximum-representable-value-for-a-ruby-time-object
639
654
  # In recent versions of Ruby, Time has a much larger range
640
655
  begin
641
- result = Time.parse(sdate)
656
+ result = Time.parse(sdate)
642
657
  rescue ArgumentError
643
658
  result = DateTime.parse(sdate)
644
659
  end
645
-
660
+
646
661
  return result
647
662
  end
648
-
663
+
649
664
  # Parses a value into the proper type based on an xml property element
650
665
  def parse_value(property_xml)
651
666
  property_type = property_xml.attr('type')
@@ -673,7 +688,7 @@ class Service
673
688
  # If we can't parse the value, just return the element's content
674
689
  property_xml.content
675
690
  end
676
-
691
+
677
692
  # Parses a value into the proper type based on a specified return type
678
693
  def parse_primative_type(value, return_type)
679
694
  return value.to_i if return_type == Fixnum
@@ -681,7 +696,7 @@ class Service
681
696
  return parse_date(value.to_s) if return_type == Time
682
697
  return value.to_s
683
698
  end
684
-
699
+
685
700
  # Converts an edm type (string) to a ruby type
686
701
  def edm_to_ruby_type(edm_type)
687
702
  return String if edm_type =~ /Edm.String/
@@ -690,30 +705,30 @@ class Service
690
705
  return Time if edm_type =~ /Edm.DateTime/
691
706
  return String
692
707
  end
693
-
708
+
694
709
  # Method Missing Handlers
695
-
710
+
696
711
  # Executes an import function
697
712
  def execute_import_function(name, *args)
698
713
  func = @function_imports[name]
699
-
714
+
700
715
  # Check the args making sure that more weren't passed in than the function needs
701
716
  param_count = func[:parameters].nil? ? 0 : func[:parameters].count
702
717
  arg_count = args.nil? ? 0 : args[0].count
703
718
  if arg_count > param_count
704
719
  raise ArgumentError, "wrong number of arguments (#{arg_count} for #{param_count})"
705
720
  end
706
-
721
+
707
722
  # Convert the parameters to a hash
708
723
  params = {}
709
724
  func[:parameters].keys.each_with_index { |key, i| params[key] = args[0][i] } unless func[:parameters].nil?
710
-
725
+
711
726
  function_uri = build_function_import_uri(name, params)
712
727
  result = RestClient::Resource.new(function_uri, @rest_options).send(func[:http_method].downcase, {})
713
-
728
+
714
729
  # Is this a 204 (No content) result?
715
730
  return true if result.code == 204
716
-
731
+
717
732
  # No? Then we need to parse the results. There are 4 kinds...
718
733
  if func[:return_type] == Array
719
734
  # a collection of entites
@@ -726,19 +741,19 @@ class Service
726
741
  end
727
742
  return results
728
743
  end
729
-
744
+
730
745
  # a single entity
731
746
  if @classes.include?(func[:return_type].to_s)
732
747
  entry = Nokogiri::XML(result).xpath("atom:entry[not(ancestor::atom:entry)]", @ds_namespaces)
733
748
  return entry_to_class(entry)
734
749
  end
735
-
750
+
736
751
  # or a single native type
737
752
  unless func[:return_type].nil?
738
753
  e = Nokogiri::XML(result).xpath("/*").first
739
754
  return parse_primative_type(e.content, func[:return_type])
740
755
  end
741
-
756
+
742
757
  # Nothing could be parsed, so just return if we got a 200 or not
743
758
  return (result.code == 200)
744
759
  end
@@ -1,3 +1,5 @@
1
+ # The ruby_odata namespace
1
2
  module OData
2
- VERSION = "0.1.0"
3
+ # The current version of ruby_odata
4
+ VERSION = "0.1.1"
3
5
  end
data/lib/ruby_odata.rb CHANGED
@@ -1,21 +1,23 @@
1
1
  lib = File.dirname(__FILE__)
2
2
 
3
- $: << lib + '/ruby_odata/'
4
- require 'rubygems'
5
- require 'active_support' # Used for serializtion to JSON
6
- require 'active_support/inflector'
7
- require 'active_support/core_ext'
8
- require 'cgi'
9
- require 'rest_client'
10
- require 'nokogiri'
11
- require 'bigdecimal'
12
- require 'bigdecimal/util'
13
- require 'backports'
3
+ $: << lib + "/ruby_odata/"
4
+ require "rubygems"
5
+ require "i18n"
6
+ require "active_support" # Used for serializtion to JSON
7
+ require "active_support/inflector"
8
+ require "active_support/core_ext"
9
+ require "cgi"
10
+ require "rest_client"
11
+ require "nokogiri"
12
+ require "bigdecimal"
13
+ require "bigdecimal/util"
14
+ require "backports"
14
15
 
15
- require lib + '/ruby_odata/association'
16
- require lib + '/ruby_odata/property_metadata'
17
- require lib + '/ruby_odata/query_builder'
18
- require lib + '/ruby_odata/class_builder'
19
- require lib + '/ruby_odata/operation'
20
- require lib + '/ruby_odata/service'
21
- require lib + '/ruby_odata/helpers'
16
+ require lib + "/ruby_odata/exceptions"
17
+ require lib + "/ruby_odata/association"
18
+ require lib + "/ruby_odata/property_metadata"
19
+ require lib + "/ruby_odata/query_builder"
20
+ require lib + "/ruby_odata/class_builder"
21
+ require lib + "/ruby_odata/operation"
22
+ require lib + "/ruby_odata/service"
23
+ require lib + "/ruby_odata/helpers"
data/ruby_odata.gemspec CHANGED
@@ -14,18 +14,22 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "ruby-odata"
16
16
 
17
- s.add_dependency('activesupport', '>= 2.3.5')
18
- s.add_dependency('rest-client', '>= 1.5.1')
19
- s.add_dependency('nokogiri', '>= 1.4.2')
20
- s.add_dependency('backports', "~> 2.3.0")
21
-
22
- s.add_development_dependency('rake', '~> 0.8.7')
23
- s.add_development_dependency('rspec', '~> 2.5.0')
24
- s.add_development_dependency('cucumber', '~> 0.10.2')
25
- s.add_development_dependency('pickle', '~> 0.4.10')
26
- s.add_development_dependency('faker', '~> 0.9.5')
27
- s.add_development_dependency('machinist', '~> 1.0.6')
28
- s.add_development_dependency('webmock', '~> 1.6.2')
17
+ s.add_dependency("i18n", "~> 0.6.0")
18
+ s.add_dependency("activesupport", ">= 3.0.0")
19
+ s.add_dependency("rest-client", ">= 1.5.1")
20
+ s.add_dependency("nokogiri", ">= 1.4.2")
21
+ s.add_dependency("backports", "~> 2.3.0")
22
+
23
+ s.add_development_dependency("rake", "0.9.2")
24
+ s.add_development_dependency("rspec", "~> 2.11.0")
25
+ s.add_development_dependency("cucumber", "~> 1.2.1")
26
+ s.add_development_dependency("pickle", "~> 0.4.11")
27
+ s.add_development_dependency("machinist", "~> 2.0")
28
+ s.add_development_dependency("webmock", "~> 1.8.8")
29
+ s.add_development_dependency("guard", "~> 1.3.0")
30
+ s.add_development_dependency("guard-rspec", "~> 1.2.1")
31
+ s.add_development_dependency("guard-cucumber", "~> 1.2.0")
32
+ s.add_development_dependency("vcr", "~> 2.2.4")
29
33
 
30
34
  s.files = `git ls-files`.split("\n")
31
35
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")