ruby_odata 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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")