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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +7 -2
- data/.simplecov +10 -0
- data/.travis.yml +10 -4
- data/.yardopts +6 -0
- data/CHANGELOG.md +157 -0
- data/Guardfile +14 -0
- data/LICENSE +20 -22
- data/README.md +289 -0
- data/Rakefile +19 -9
- data/features/basic_auth.feature +3 -2
- data/features/batch_request.feature +7 -6
- data/features/cassettes/basic_auth_protected_resource.yml +57 -0
- data/features/cassettes/batch_request_additions.yml +69 -0
- data/features/cassettes/batch_request_deletes.yml +69 -0
- data/features/cassettes/batch_request_updates.yml +69 -0
- data/features/cassettes/clean_database_for_testing.yml +46 -0
- data/features/cassettes/cucumber_tags/basic_auth.yml +297 -0
- data/features/cassettes/cucumber_tags/batch_request.yml +1459 -0
- data/features/cassettes/cucumber_tags/complex_types.yml +326 -0
- data/features/cassettes/cucumber_tags/error_handling.yml +64 -0
- data/features/cassettes/cucumber_tags/query_builder.yml +2025 -0
- data/features/cassettes/cucumber_tags/service.yml +234 -0
- data/features/cassettes/cucumber_tags/service_manage.yml +937 -0
- data/features/cassettes/cucumber_tags/service_methods.yml +647 -0
- data/features/cassettes/cucumber_tags/ssl.yml +203 -0
- data/features/cassettes/cucumber_tags/type_conversion.yml +337 -0
- data/features/cassettes/service_manage_additions.yml +65 -0
- data/features/cassettes/service_manage_deletions.yml +58 -0
- data/features/cassettes/service_manage_deletions_2.yml +58 -0
- data/features/cassettes/unsecured_metadata.yml +89 -0
- data/features/complex_types.feature +4 -3
- data/features/error_handling.feature +13 -0
- data/features/query_builder.feature +30 -9
- data/features/service.feature +4 -3
- data/features/service_manage.feature +6 -5
- data/features/service_methods.feature +3 -2
- data/features/ssl.feature +8 -8
- data/features/step_definitions/service_steps.rb +32 -74
- data/features/support/env.rb +6 -3
- data/features/support/hooks.rb +3 -2
- data/features/support/pickle.rb +29 -18
- data/features/support/vcr.rb +24 -0
- data/features/type_conversion.feature +16 -17
- data/gemfiles/Gemfile.ruby187 +6 -0
- data/lib/ruby_odata.rb +20 -18
- data/lib/ruby_odata/association.rb +7 -6
- data/lib/ruby_odata/class_builder.rb +31 -14
- data/lib/ruby_odata/exceptions.rb +11 -0
- data/lib/ruby_odata/helpers.rb +17 -0
- data/lib/ruby_odata/operation.rb +5 -6
- data/lib/ruby_odata/property_metadata.rb +9 -7
- data/lib/ruby_odata/query_builder.rb +127 -63
- data/lib/ruby_odata/service.rb +265 -147
- data/lib/ruby_odata/version.rb +3 -1
- data/ruby_odata.gemspec +22 -13
- data/spec/fixtures/error_without_message.xml +5 -0
- data/spec/fixtures/int64_ids/edmx_boat_service.xml +19 -0
- data/spec/fixtures/int64_ids/edmx_car_service.xml +21 -0
- data/spec/fixtures/int64_ids/result_boats.xml +26 -0
- data/spec/fixtures/int64_ids/result_cars.xml +28 -0
- data/spec/fixtures/ms_system_center/edmx_ms_system_center.xml +1645 -0
- data/spec/fixtures/ms_system_center/edmx_ms_system_center_v2.xml +2120 -0
- data/spec/fixtures/ms_system_center/hardware_profiles.xml +61 -0
- data/spec/fixtures/ms_system_center/virtual_machines.xml +175 -0
- data/spec/fixtures/ms_system_center/vm_templates.xml +1193 -0
- data/spec/fixtures/nested_expands/edmx_northwind.xml +557 -0
- data/spec/fixtures/nested_expands/northwind_products_category_expands.xml +774 -0
- data/spec/fixtures/sample_service/result_select_categories_expand.xml +268 -0
- data/spec/fixtures/sample_service/result_select_categories_no_property.xml +6 -0
- data/spec/fixtures/sample_service/result_select_categories_travsing_no_expand.xml +6 -0
- data/spec/fixtures/sample_service/result_select_products_name_price.xml +92 -0
- data/spec/property_metadata_spec.rb +10 -10
- data/spec/query_builder_spec.rb +153 -14
- data/spec/revised_service_spec.rb +111 -6
- data/spec/service_spec.rb +389 -85
- data/spec/spec_helper.rb +3 -0
- data/spec/support/sample_service_matcher.rb +15 -0
- data/test/RubyODataService/RubyODataService/App_Data/.gitkeep +0 -0
- data/test/blueprints.rb +15 -9
- data/test/usage_samples/querying.rb +5 -1
- data/test/usage_samples/sample_data.rb +1 -3
- metadata +276 -76
- data/CHANGELOG.rdoc +0 -88
- data/README.rdoc +0 -259
data/lib/ruby_odata.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
lib = File.dirname(__FILE__)
|
2
2
|
|
3
|
-
$: << lib +
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
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 +
|
16
|
-
require lib +
|
17
|
-
require lib +
|
18
|
-
require lib +
|
19
|
-
require lib +
|
20
|
-
require lib +
|
21
|
-
require lib +
|
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
|
-
#
|
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
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
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
|
-
|
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
|
data/lib/ruby_odata/helpers.rb
CHANGED
@@ -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
|
data/lib/ruby_odata/operation.rb
CHANGED
@@ -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
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
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
|
-
#
|
22
|
-
|
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
|
29
|
-
|
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
|
-
#
|
6
|
-
#
|
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
|
-
#
|
12
|
-
#
|
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
|
-
@
|
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
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
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
|
-
#
|
36
|
-
#
|
37
|
-
#
|
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
|
-
#
|
48
|
-
#
|
49
|
-
#
|
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
|
-
#
|
60
|
-
#
|
61
|
-
#
|
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
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
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
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
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
|
-
#
|
98
|
-
#
|
99
|
-
#
|
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
|
-
|
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 @
|
112
|
-
q << "/$links/#{@
|
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
|