ruby_odata 0.1.0 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +7 -2
  4. data/.simplecov +10 -0
  5. data/.travis.yml +10 -4
  6. data/.yardopts +6 -0
  7. data/CHANGELOG.md +157 -0
  8. data/Guardfile +14 -0
  9. data/LICENSE +20 -22
  10. data/README.md +289 -0
  11. data/Rakefile +19 -9
  12. data/features/basic_auth.feature +3 -2
  13. data/features/batch_request.feature +7 -6
  14. data/features/cassettes/basic_auth_protected_resource.yml +57 -0
  15. data/features/cassettes/batch_request_additions.yml +69 -0
  16. data/features/cassettes/batch_request_deletes.yml +69 -0
  17. data/features/cassettes/batch_request_updates.yml +69 -0
  18. data/features/cassettes/clean_database_for_testing.yml +46 -0
  19. data/features/cassettes/cucumber_tags/basic_auth.yml +297 -0
  20. data/features/cassettes/cucumber_tags/batch_request.yml +1459 -0
  21. data/features/cassettes/cucumber_tags/complex_types.yml +326 -0
  22. data/features/cassettes/cucumber_tags/error_handling.yml +64 -0
  23. data/features/cassettes/cucumber_tags/query_builder.yml +2025 -0
  24. data/features/cassettes/cucumber_tags/service.yml +234 -0
  25. data/features/cassettes/cucumber_tags/service_manage.yml +937 -0
  26. data/features/cassettes/cucumber_tags/service_methods.yml +647 -0
  27. data/features/cassettes/cucumber_tags/ssl.yml +203 -0
  28. data/features/cassettes/cucumber_tags/type_conversion.yml +337 -0
  29. data/features/cassettes/service_manage_additions.yml +65 -0
  30. data/features/cassettes/service_manage_deletions.yml +58 -0
  31. data/features/cassettes/service_manage_deletions_2.yml +58 -0
  32. data/features/cassettes/unsecured_metadata.yml +89 -0
  33. data/features/complex_types.feature +4 -3
  34. data/features/error_handling.feature +13 -0
  35. data/features/query_builder.feature +30 -9
  36. data/features/service.feature +4 -3
  37. data/features/service_manage.feature +6 -5
  38. data/features/service_methods.feature +3 -2
  39. data/features/ssl.feature +8 -8
  40. data/features/step_definitions/service_steps.rb +32 -74
  41. data/features/support/env.rb +6 -3
  42. data/features/support/hooks.rb +3 -2
  43. data/features/support/pickle.rb +29 -18
  44. data/features/support/vcr.rb +24 -0
  45. data/features/type_conversion.feature +16 -17
  46. data/gemfiles/Gemfile.ruby187 +6 -0
  47. data/lib/ruby_odata.rb +20 -18
  48. data/lib/ruby_odata/association.rb +7 -6
  49. data/lib/ruby_odata/class_builder.rb +31 -14
  50. data/lib/ruby_odata/exceptions.rb +11 -0
  51. data/lib/ruby_odata/helpers.rb +17 -0
  52. data/lib/ruby_odata/operation.rb +5 -6
  53. data/lib/ruby_odata/property_metadata.rb +9 -7
  54. data/lib/ruby_odata/query_builder.rb +127 -63
  55. data/lib/ruby_odata/service.rb +265 -147
  56. data/lib/ruby_odata/version.rb +3 -1
  57. data/ruby_odata.gemspec +22 -13
  58. data/spec/fixtures/error_without_message.xml +5 -0
  59. data/spec/fixtures/int64_ids/edmx_boat_service.xml +19 -0
  60. data/spec/fixtures/int64_ids/edmx_car_service.xml +21 -0
  61. data/spec/fixtures/int64_ids/result_boats.xml +26 -0
  62. data/spec/fixtures/int64_ids/result_cars.xml +28 -0
  63. data/spec/fixtures/ms_system_center/edmx_ms_system_center.xml +1645 -0
  64. data/spec/fixtures/ms_system_center/edmx_ms_system_center_v2.xml +2120 -0
  65. data/spec/fixtures/ms_system_center/hardware_profiles.xml +61 -0
  66. data/spec/fixtures/ms_system_center/virtual_machines.xml +175 -0
  67. data/spec/fixtures/ms_system_center/vm_templates.xml +1193 -0
  68. data/spec/fixtures/nested_expands/edmx_northwind.xml +557 -0
  69. data/spec/fixtures/nested_expands/northwind_products_category_expands.xml +774 -0
  70. data/spec/fixtures/sample_service/result_select_categories_expand.xml +268 -0
  71. data/spec/fixtures/sample_service/result_select_categories_no_property.xml +6 -0
  72. data/spec/fixtures/sample_service/result_select_categories_travsing_no_expand.xml +6 -0
  73. data/spec/fixtures/sample_service/result_select_products_name_price.xml +92 -0
  74. data/spec/property_metadata_spec.rb +10 -10
  75. data/spec/query_builder_spec.rb +153 -14
  76. data/spec/revised_service_spec.rb +111 -6
  77. data/spec/service_spec.rb +389 -85
  78. data/spec/spec_helper.rb +3 -0
  79. data/spec/support/sample_service_matcher.rb +15 -0
  80. data/test/RubyODataService/RubyODataService/App_Data/.gitkeep +0 -0
  81. data/test/blueprints.rb +15 -9
  82. data/test/usage_samples/querying.rb +5 -1
  83. data/test/usage_samples/sample_data.rb +1 -3
  84. metadata +276 -76
  85. data/CHANGELOG.rdoc +0 -88
  86. data/README.rdoc +0 -259
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec :path => '../'
4
+
5
+ gem 'nokogiri', '~> 1.5.10'
6
+ gem 'activesupport', '~> 3.0'
@@ -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 "addressable/uri"
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"
@@ -1,8 +1,9 @@
1
1
  module OData
2
+ # Internal class used to represent object associations
2
3
  class Association
3
-
4
+
4
5
  attr_reader :name, :namespace, :relationship, :from_role, :to_role
5
-
6
+
6
7
  def initialize(nav_prop_element, edmx)
7
8
  @edmx = edmx
8
9
 
@@ -11,9 +12,9 @@ module OData
11
12
  @edmx_namespaces = { "edmx" => "http://schemas.microsoft.com/ado/2007/06/edmx", "edm" => edm_ns }
12
13
  parse_nav_prop(nav_prop_element)
13
14
  end
14
-
15
+
15
16
  private
16
-
17
+
17
18
  def parse_nav_prop(element)
18
19
  @relationship = element['Relationship']
19
20
  relationship_parts = @relationship.split('.')
@@ -22,12 +23,12 @@ module OData
22
23
  @from_role = role_hash(@name, element['FromRole'])
23
24
  @to_role = role_hash(@name, element['ToRole'])
24
25
  end
25
-
26
+
26
27
  def role_hash(association_name, role_name)
27
28
  # Find the end role based on the assocation name
28
29
  role_xpath = "/edmx:Edmx/edmx:DataServices/edm:Schema[@Namespace='#{@namespace}']/edm:Association[@Name='#{association_name}']/edm:End[@Role='#{role_name}']"
29
30
  role_element = @edmx.xpath(role_xpath, @edmx_namespaces).first
30
- { role_name => {
31
+ { role_name => {
31
32
  :edmx_type => "#{role_element['Type']}",
32
33
  :multiplicity => "#{role_element['Multiplicity']}"
33
34
  }}
@@ -1,14 +1,13 @@
1
1
  module OData
2
- # Internally used helper class for building a dynamic class. This class shouldn't be called directly.
2
+ # Internal helper class for building a dynamic class. This class shouldn't be called directly.
3
3
  class ClassBuilder
4
4
  # Creates a new instance of the ClassBuilder class
5
5
  #
6
- # ==== Required Attributes
7
- # - klass_name: The name/type of the class to create
8
- # - methods: The accessor methods to add to the class
9
- # - nav_props: The accessor methods to add for navigation properties
10
- # - context: The service context that this entity belongs to
11
- # - namespaces: Optional namespace to create the classes in
6
+ # @param [String] klass_name the name/type of the class to create
7
+ # @param [Array] methods the accessor methods to add to the class
8
+ # @param [Array] nav_props the accessor methods to add for navigation properties
9
+ # @param [Service] context the service context that this entity belongs to
10
+ # @param [String, nil] namespace optional namespace to create the classes in
12
11
  def initialize(klass_name, methods, nav_props, context, namespace = nil)
13
12
  @klass_name = klass_name.camelcase
14
13
  @methods = methods
@@ -77,7 +76,7 @@ module OData
77
76
  end
78
77
  end
79
78
  end
80
-
79
+
81
80
  def add_methods(klass)
82
81
  # Add metadata methods
83
82
  klass.send :define_method, :__metadata do
@@ -111,6 +110,20 @@ module OData
111
110
  end
112
111
  end
113
112
 
113
+ props = self.class.properties
114
+
115
+ # Convert a Int64 to a string for serialization (to match Edm.Int64)
116
+ bigints = vars.find_all { |o| props[o[0]] && props[o[0]].type == "Edm.Int64" } || []
117
+ bigints.each do |i|
118
+ vars[i[0]] = i[1].to_s
119
+ end
120
+
121
+ # Convert Arrays into proper Collections
122
+ collections = vars.find_all { |o| o[1].class == Array } || []
123
+ collections.each do |c|
124
+ vars[c[0]] = { '__metadata' => { 'type' => props[c[0]].type }, 'results' => c[1] }
125
+ end
126
+
114
127
  # Convert a BigDecimal to a string for serialization (to match Edm.Decimal)
115
128
  decimals = vars.find_all { |o| o[1].class == BigDecimal } || []
116
129
  decimals.each do |d|
@@ -145,20 +158,20 @@ module OData
145
158
  instance_variable_set("@#{method_name}", value)
146
159
  end
147
160
  end
148
-
161
+
149
162
  # Add an id method pulling out the id from the uri (mainly for Pickle support)
150
163
  klass.send :define_method, :id do
151
164
  metadata = self.__metadata
152
165
  id = nil
153
- if metadata && metadata[:uri] =~ /\((\d+)\)$/
166
+ if metadata && metadata[:uri] =~ /\((\d+)L?\)$/
154
167
  id = $~[1]
155
168
  end
156
169
  return (true if Integer(id) rescue false) ? id.to_i : id
157
170
  end
158
-
171
+
159
172
  # Override equals
160
173
  klass.send :define_method, :== do |other|
161
- self.class == other.class &&
174
+ self.class == other.class &&
162
175
  self.id == other.id &&
163
176
  self.__metadata == other.__metadata
164
177
  end
@@ -182,14 +195,18 @@ module OData
182
195
  klass.send :define_singleton_method, 'properties' do
183
196
  context.class_metadata[klass.to_s] || {}
184
197
  end
185
-
198
+
186
199
  # Finds a single model by ID
187
200
  klass.send :define_singleton_method, 'first' do |id|
188
201
  return nil if id.nil?
189
202
  # TODO: Instead of just pluralizing the klass name, use the actual collection name
190
203
  collection = klass.to_s.pluralize
191
204
  context.send "#{collection}", id
192
- results = context.execute
205
+ begin
206
+ results = context.execute
207
+ rescue OData::ServiceError => e
208
+ return nil
209
+ end
193
210
  results.count == 0 ? nil : results.first
194
211
  end
195
212
  end
@@ -0,0 +1,11 @@
1
+ module OData
2
+ # Raised when a user attempts to do something that is not supported
3
+ class NotSupportedError < StandardError; end
4
+ # Raised when the service returns an error
5
+ class ServiceError < StandardError
6
+ attr_reader :http_code
7
+ def initialize(code)
8
+ @http_code = code
9
+ end
10
+ end
11
+ end
@@ -1,4 +1,5 @@
1
1
  module OData
2
+ # Helper methods
2
3
  class Helpers
3
4
  # Helper to normalize the results of a select result; Ruby 1.9 Hash.select returns a Hash, 1.8 returns an Array
4
5
  # This is for Ruby 1.8 support, but should be removed in the future
@@ -6,5 +7,21 @@ module OData
6
7
  return nil if val.nil?
7
8
  (val.is_a? Hash) ? val : Hash[*val.flatten]
8
9
  end
10
+
11
+ # Wrapper for URI escaping that switches between URI::Parser#escape and
12
+ # URI.escape for 1.9-compatibility (thanks FakeWeb https://github.com/chrisk/fakeweb/blob/master/lib/fake_web/utility.rb#L40)
13
+ def self.uri_escape(*args)
14
+ if URI.const_defined?(:Parser)
15
+ URI::Parser.new.escape(*args)
16
+ else
17
+ URI.escape(*args)
18
+ end
19
+ end
20
+
21
+ # Nokogiri changed how it handles namespaced attributes with v1.5.1, this is for backwards compatibility to >= 1.4.2
22
+ # Nokogiri now >=1.5.1 requires the namespace prefix is used
23
+ def self.get_namespaced_attribute(node, attr_name, prefix)
24
+ return node["#{prefix}:#{attr_name}"] || node[attr_name]
25
+ end
9
26
  end
10
27
  end
@@ -2,14 +2,13 @@ module OData
2
2
  # Internally used helper class for storing operations called against the service. This class shouldn't be used directly.
3
3
  class Operation
4
4
  attr_accessor :kind, :klass_name, :klass, :child_klass
5
-
5
+
6
6
  # Creates a new instance of the Operation class
7
7
  #
8
- # ==== Required Attributes
9
- # - kind: The operation type (Standard: Add, Update, or Delete | Links: AddLink)
10
- # - klass_name: The name/type of the class to operate against
11
- # - klass: The actual class
12
- # - child_klass: (Optional) Only used for link operations
8
+ # @param [String] kind the operation type (Standard: Add, Update, or Delete | Links: AddLink)
9
+ # @param [String] klass_name the name/type of the class to operate against
10
+ # @param [Object] klass the actual class
11
+ # @param [Object, nil] child_klass used for link operations only
13
12
  def initialize(kind, klass_name, klass, child_klass = nil)
14
13
  @kind = kind
15
14
  @klass_name = klass_name
@@ -15,18 +15,20 @@ module OData
15
15
  attr_reader :nav_prop
16
16
  # Applies only to navigation properties; the association corresponding to the property
17
17
  attr_accessor :association
18
-
18
+ # Applies to the primary key(s)
19
+ attr_accessor :is_key
20
+
19
21
  # Creates a new instance of the Class Property class
20
22
  #
21
- # ==== Required Attributes
22
- # property_element: The property element from the EDMX
23
-
24
- def initialize(property_element)
23
+ # @param [Nokogiri::XML::Node] property_element from the EDMX
24
+
25
+ def initialize(property_element)
25
26
  @name = property_element['Name']
26
27
  @type = property_element['Type']
27
28
  @nullable = ((property_element['Nullable'] && property_element['Nullable'] == "true") || property_element.name == 'NavigationProperty') || false
28
- @fc_target_path = property_element['FC_TargetPath']
29
- @fc_keep_in_content = (property_element['FC_KeepInContent']) ? (property_element['FC_KeepInContent'] == "true") : nil
29
+ @fc_target_path = Helpers.get_namespaced_attribute(property_element, 'FC_TargetPath', 'm')
30
+ keep_in_content = Helpers.get_namespaced_attribute(property_element, 'FC_KeepInContent', 'm')
31
+ @fc_keep_in_content = (keep_in_content) ? (keep_in_content == "true") : nil
30
32
  @nav_prop = property_element.name == 'NavigationProperty'
31
33
  end
32
34
  end
@@ -1,118 +1,182 @@
1
1
  module OData
2
2
  # The query builder is used to call query operations against the service. This shouldn't be called directly, but rather it is returned from the dynamic methods created for the specific service that you are calling.
3
3
  #
4
- # For example, given the following code snippet:
5
- # svc = OData::Service.new "http://127.0.0.1:8989/SampleService/RubyOData.svc"
6
- # svc.Categories
7
- # The *Categories* method would return a QueryBuilder
4
+ # @example For example, given the following code snippet:
5
+ # svc = OData::Service.new "http://127.0.0.1:8989/SampleService/RubyOData.svc"
6
+ # svc.Categories
7
+ # The *Categories* method would return a QueryBuilder
8
8
  class QueryBuilder
9
+ attr_accessor :additional_params
10
+
9
11
  # Creates a new instance of the QueryBuilder class
10
12
  #
11
- # ==== Required Attributes
12
- # - root: The root entity collection to query against
13
- # ==== Optional
14
- # Hash of additional parameters to use for a query
13
+ # @param [String] root entity collection to query against
14
+ # @param [Hash, {}] additional_params hash of additional parameters to use for a query
15
15
  def initialize(root, additional_params = {})
16
- @root = root.to_s
16
+ @root = Helpers.uri_escape(root.to_s)
17
17
  @expands = []
18
18
  @filters = []
19
19
  @order_bys = []
20
+ @navigation_paths = []
21
+ @select = []
20
22
  @skip = nil
21
23
  @top = nil
22
- @links = nil
24
+ @count = nil
25
+ @links_navigation_property = nil
23
26
  @additional_params = additional_params
24
27
  end
25
-
28
+
26
29
  # Used to eagerly-load data for nested objects, for example, obtaining a Category for a Product within one call to the server
27
- # ==== Required Attributes
28
- # - path: The path of the entity to expand relative to the root
29
30
  #
30
- # ==== Example
31
- # # Without expanding the query (no Category will be filled in for the Product)
32
- # svc.Products(1)
33
- # prod1 = svc.execute
31
+ # @param [String] path of the entity to expand relative to the root
32
+ # @example
33
+ # # Without expanding the query (no Category will be filled in for the Product)
34
+ # svc.Products(1)
35
+ # prod1 = svc.execute
34
36
  #
35
- # # With expanding the query (the Category will be filled in)
36
- # svc.Products(1).expand('Category')
37
- # prod1 = svc.execute
37
+ # # With expanding the query (the Category will be filled in)
38
+ # svc.Products(1).expand('Category')
39
+ # prod1 = svc.execute
38
40
  def expand(path)
39
41
  @expands << path
40
42
  self
41
43
  end
42
-
44
+
43
45
  # Used to filter data being returned
44
- # ==== Required Attributes
45
- # - filter: The conditions to apply to the query
46
46
  #
47
- # ==== Example
48
- # svc.Products.filter("Name eq 'Product 2'")
49
- # products = svc.execute
47
+ # @param [String] filter conditions to apply to the query
48
+ #
49
+ # @example
50
+ # svc.Products.filter("Name eq 'Product 2'")
51
+ # products = svc.execute
50
52
  def filter(filter)
51
53
  @filters << CGI.escape(filter)
52
54
  self
53
55
  end
54
-
56
+
55
57
  # Used to order the data being returned
56
- # ==== Required Attributes
57
- # - order_by: The order by statement. Note to specify direction, use "desc" or "asc"; must be lowercase
58
58
  #
59
- # ==== Example
60
- # svc.Products.order_by("Name")
61
- # products = svc.execute
59
+ # @param [String] order_by the order by statement. Note to specify direction, use "desc" or "asc"; must be lowercase
60
+ #
61
+ # @example
62
+ # svc.Products.order_by("Name")
63
+ # products = svc.execute
62
64
  def order_by(order_by)
63
65
  @order_bys << CGI.escape(order_by)
64
66
  self
65
67
  end
66
-
67
- # Used to skip a number of records
68
- # This is typically used for paging, where it would be used along with the +top+ method.
69
- # ==== Required Attributes
70
- # - num: The number of items to skip
71
- #
72
- # ==== Example
73
- # svc.Products.skip(5)
74
- # products = svc.execute # => skips the first 5 items
68
+
69
+ # Used to skip a number of records
70
+ # This is typically used for paging, where it would be used along with the `top` method.
71
+ #
72
+ # @param [Integer] num the number of items to skip
73
+ #
74
+ # @example
75
+ # svc.Products.skip(5)
76
+ # products = svc.execute # => skips the first 5 items
75
77
  def skip(num)
76
78
  @skip = num
77
79
  self
78
80
  end
79
-
80
- # Used to take only the top X records
81
- # This is typically used for paging, where it would be used along with the +skip+ method.
82
- # ==== Required Attributes
83
- # - num: The number of items to return
84
- #
85
- # ==== Example
86
- # svc.Products.top(5)
87
- # products = svc.execute # => returns only the first 5 items
81
+
82
+ # Used to take only the top X records
83
+ # This is typically used for paging, where it would be used along with the `skip` method.
84
+ #
85
+ # @param [Integer] num the number of items to return
86
+ #
87
+ # @example
88
+ # svc.Products.top(5)
89
+ # products = svc.execute # => returns only the first 5 items
88
90
  def top(num)
89
91
  @top = num
90
92
  self
91
93
  end
92
-
94
+
93
95
  # Used to return links instead of actual objects
94
- # ==== Required Attributes
95
- # - navigation_property: The NavigationProperty name to retrieve the links for
96
96
  #
97
- # ==== Example
98
- # svc.Categories(1).links("Products")
99
- # product_links = svc.execute # => returns URIs for the products under the Category with an ID of 1
97
+ # @param [String] navigation_property the NavigationProperty name to retrieve the links for
98
+ #
99
+ # @raise [NotSupportedError] if count has already been called on the query
100
+ #
101
+ # @example
102
+ # svc.Categories(1).links("Products")
103
+ # product_links = svc.execute # => returns URIs for the products under the Category with an ID of 1
100
104
  def links(navigation_property)
101
- @navigation_property = navigation_property
105
+ raise OData::NotSupportedError.new("You cannot call both the `links` method and the `count` method in the same query.") if @count
106
+ raise OData::NotSupportedError.new("You cannot call both the `links` method and the `select` method in the same query.") unless @select.empty?
107
+ @links_navigation_property = navigation_property
108
+ self
109
+ end
110
+
111
+ # Used to return a count of objects instead of the objects themselves
112
+ #
113
+ # @raise [NotSupportedError] if links has already been called on the query
114
+ #
115
+ # @example
116
+ # svc.Products
117
+ # svc.count
118
+ # product_count = svc.execute
119
+ def count
120
+ raise OData::NotSupportedError.new("You cannot call both the `links` method and the `count` method in the same query.") if @links_navigation_property
121
+ raise OData::NotSupportedError.new("You cannot call both the `select` method and the `count` method in the same query.") unless @select.empty?
122
+
123
+ @count = true
102
124
  self
103
125
  end
104
-
126
+
127
+ # Used to navigate to a child collection, typically used to filter or perform a similar function against the children
128
+ #
129
+ # @param [String] navigation_property the NavigationProperty to drill-down into
130
+ #
131
+ # @example
132
+ # svc.Genres('Horror Movies').navigate("Titles").filter("Name eq 'Halloween'")
133
+ def navigate(navigation_property)
134
+ @navigation_paths << Helpers.uri_escape(navigation_property)
135
+ self
136
+ end
137
+
138
+ # Used to customize the properties that are returned for "ad-hoc" queries
139
+ #
140
+ # @param [Array<String>] properties to return
141
+ #
142
+ # @example
143
+ # svc.Products.select('Price', 'Rating')
144
+ def select(*fields)
145
+ raise OData::NotSupportedError.new("You cannot call both the `links` method and the `select` method in the same query.") if @links_navigation_property
146
+ raise OData::NotSupportedError.new("You cannot call both the `count` method and the `select` method in the same query.") if @count
147
+
148
+ @select |= fields
149
+
150
+ expands = fields.find_all { |f| /\// =~ f }
151
+ expands.each do |e|
152
+ parts = e.split '/'
153
+ @expands |= [parts[0...-1].join('/')]
154
+ end
155
+
156
+
157
+ self
158
+ end
159
+
105
160
  # Builds the query URI (path, not including root) incorporating expands, filters, etc.
106
161
  # This is used internally when the execute method is called on the service
107
162
  def query
108
163
  q = @root.clone
109
-
164
+
165
+ # Navigation paths come first in the query
166
+ q << "/" + @navigation_paths.join("/") unless @navigation_paths.empty?
167
+
110
168
  # Handle links queries, this isn't just a standard query option
111
- if @navigation_property
112
- q << "/$links/#{@navigation_property}"
169
+ if @links_navigation_property
170
+ q << "/$links/#{@links_navigation_property}"
113
171
  end
114
-
172
+
173
+ # Handle count queries, this isn't just a standard query option
174
+ if @count
175
+ q << "/$count"
176
+ end
177
+
115
178
  query_options = []
179
+ query_options << "$select=#{@select.join(',')}" unless @select.empty?
116
180
  query_options << "$expand=#{@expands.join(',')}" unless @expands.empty?
117
181
  query_options << "$filter=#{@filters.join('+and+')}" unless @filters.empty?
118
182
  query_options << "$orderby=#{@order_bys.join(',')}" unless @order_bys.empty?
@@ -121,7 +185,7 @@ class QueryBuilder
121
185
  query_options << @additional_params.to_query unless @additional_params.empty?
122
186
  if !query_options.empty?
123
187
  q << "?"
124
- q << query_options.join('&')
188
+ q << query_options.join('&')
125
189
  end
126
190
  return q
127
191
  end