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.
- data/.gitignore +3 -2
- data/.travis.yml +2 -1
- data/.yardopts +6 -0
- data/CHANGELOG.md +102 -0
- data/Guardfile +14 -0
- data/README.md +285 -0
- data/Rakefile +0 -7
- 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 +14 -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 +38 -24
- data/features/support/env.rb +1 -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/lib/ruby_odata/association.rb +7 -6
- data/lib/ruby_odata/class_builder.rb +6 -7
- data/lib/ruby_odata/exceptions.rb +4 -0
- data/lib/ruby_odata/helpers.rb +11 -0
- data/lib/ruby_odata/operation.rb +5 -6
- data/lib/ruby_odata/property_metadata.rb +4 -5
- data/lib/ruby_odata/query_builder.rb +98 -63
- data/lib/ruby_odata/service.rb +118 -103
- data/lib/ruby_odata/version.rb +3 -1
- data/lib/ruby_odata.rb +20 -18
- data/ruby_odata.gemspec +16 -12
- data/spec/query_builder_spec.rb +78 -14
- data/spec/service_spec.rb +83 -83
- 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 +213 -39
- data/CHANGELOG.rdoc +0 -88
- data/README.rdoc +0 -259
data/features/support/pickle.rb
CHANGED
@@ -1,23 +1,28 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "pickle/world"
|
2
|
+
require "vcr"
|
3
|
+
require File.expand_path("../../../lib/ruby_odata", __FILE__)
|
3
4
|
|
4
5
|
module OData
|
5
|
-
|
6
6
|
module PickleAdapter
|
7
7
|
include Pickle::Adapter::Base
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
@@service = nil
|
9
|
+
def self.get_service
|
10
|
+
return @@service if @@service
|
11
|
+
VCR.use_cassette("unsecured_metadata") do
|
12
|
+
return OData::Service.new "http://#{WEBSERVER}:#{HTTP_PORT_NUMBER}/SampleService/RubyOData.svc"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
11
16
|
# Do not consider these to be part of the class list
|
12
17
|
def self.except_classes
|
13
18
|
@@except_classes ||= []
|
14
19
|
end
|
15
|
-
|
20
|
+
|
16
21
|
# Gets a list of the available models for this adapter
|
17
22
|
def self.model_classes
|
18
|
-
|
23
|
+
self.get_service.classes.values
|
19
24
|
end
|
20
|
-
|
25
|
+
|
21
26
|
# get a list of column names for a given class
|
22
27
|
def self.column_names(klass)
|
23
28
|
klass.properties.keys
|
@@ -26,7 +31,9 @@ module OData
|
|
26
31
|
# Get an instance by id of the model
|
27
32
|
def self.get_model(klass, id, expand = false)
|
28
33
|
collection = klass.to_s.split('::').last.pluralize
|
29
|
-
|
34
|
+
service = self.get_service
|
35
|
+
|
36
|
+
query = service.send collection, id
|
30
37
|
|
31
38
|
if expand then
|
32
39
|
# Expand all navigation properties
|
@@ -38,24 +45,28 @@ module OData
|
|
38
45
|
end
|
39
46
|
end
|
40
47
|
|
41
|
-
|
48
|
+
service.execute.first
|
42
49
|
end
|
43
50
|
|
44
51
|
# Find the first instance matching conditions
|
45
52
|
def self.find_first_model(klass, conditions)
|
46
53
|
collection = klass.to_s.split('::').last.pluralize
|
47
|
-
|
54
|
+
service = self.get_service
|
55
|
+
|
56
|
+
q = service.send collection
|
48
57
|
q.filter(conditions)
|
49
58
|
q.take(1)
|
50
|
-
|
59
|
+
service.execute.first
|
51
60
|
end
|
52
61
|
|
53
62
|
# Find all models matching conditions
|
54
63
|
def self.find_all_models(klass, conditions)
|
55
64
|
collection = klass.to_s.split('::').last.pluralize
|
56
|
-
|
65
|
+
service = self.get_service
|
66
|
+
|
67
|
+
q = service.send collection
|
57
68
|
q.filter(conditions)
|
58
|
-
|
69
|
+
service.execute
|
59
70
|
end
|
60
71
|
|
61
72
|
# Create a model using attributes
|
@@ -63,10 +74,10 @@ module OData
|
|
63
74
|
instance = klass.send :make, attributes
|
64
75
|
|
65
76
|
collection = klass.to_s.split('::').last.pluralize
|
66
|
-
|
67
|
-
|
77
|
+
service = self.get_service
|
78
|
+
service.send "AddTo#{collection}", instance
|
79
|
+
service.save_changes.first
|
68
80
|
end
|
69
|
-
|
70
81
|
end
|
71
82
|
end
|
72
83
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "vcr"
|
2
|
+
require File.expand_path("../../../spec/support/sample_service_matcher", __FILE__)
|
3
|
+
|
4
|
+
VCR.configure do |c|
|
5
|
+
c.hook_into :webmock
|
6
|
+
c.cassette_library_dir = "features/cassettes"
|
7
|
+
c.default_cassette_options = {
|
8
|
+
:record => :none,
|
9
|
+
:match_requests_on => [:method, OData::Support::SampleServiceMatcher]
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
VCR.cucumber_tags do |t|
|
14
|
+
t.tags "@basic_auth",
|
15
|
+
"@batch_request",
|
16
|
+
"@complex_types",
|
17
|
+
"@error_handling",
|
18
|
+
"@query_builder",
|
19
|
+
"@service",
|
20
|
+
"@service_manage",
|
21
|
+
"@service_methods",
|
22
|
+
"@ssl",
|
23
|
+
"@type_conversion"
|
24
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
@type_conversion
|
1
2
|
Feature: Type conversion
|
2
3
|
In order to accurately perform operations
|
3
|
-
As a user of the API
|
4
|
+
As a user of the API
|
4
5
|
I want types returned to be accurately represented
|
5
6
|
|
6
7
|
Background:
|
@@ -9,29 +10,29 @@ Background:
|
|
9
10
|
|
10
11
|
Scenario: Integers should be Fixnums
|
11
12
|
Given I call "AddToProducts" on the service with a new "Product" object
|
12
|
-
|
13
|
+
And I save changes
|
13
14
|
When I call "Products" on the service
|
14
15
|
And I run the query
|
15
|
-
Then the "Id" method on the object should return a Fixnum
|
16
|
-
|
16
|
+
Then the "Id" method on the object should return a Fixnum
|
17
|
+
|
17
18
|
Scenario: Decimals should be BigDecimals
|
18
|
-
|
19
|
+
Given I call "AddToProducts" on the service with a new "Product" object
|
19
20
|
And I save changes
|
20
|
-
|
21
|
+
When I call "Products" on the service
|
21
22
|
And I run the query
|
22
23
|
Then the "Price" method on the object should return a BigDecimal
|
23
24
|
|
24
25
|
Scenario: DateTimes should be Times
|
25
|
-
|
26
|
+
Given I call "AddToProducts" on the service with a new "Product" object
|
26
27
|
And I save changes
|
27
|
-
|
28
|
+
When I call "Products" on the service
|
28
29
|
And I run the query
|
29
|
-
|
30
|
-
|
30
|
+
Then the "AuditFields.CreateDate" method on the object should return a Time
|
31
|
+
|
31
32
|
Scenario: Verify that DateTimes don't change if not modified on an update
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
Given I call "AddToProducts" on the service with a new "Product" object with Name: "Test Product"
|
34
|
+
When I save changes
|
35
|
+
And I call "Products" on the service with args: "1"
|
35
36
|
And I run the query
|
36
37
|
Then I store the first last query result for comparison
|
37
38
|
When I set "Name" on the first result to "Changed Test Product"
|
@@ -44,10 +45,8 @@ Scenario: Verify that DateTimes don't change if not modified on an update
|
|
44
45
|
Then the new query first result's time "AuditFields.CreateDate" should equal the saved query result
|
45
46
|
|
46
47
|
Scenario: DateTimes should be able to be null
|
47
|
-
|
48
|
+
Given I call "AddToProducts" on the service with a new "Product" object
|
48
49
|
And I save changes
|
49
|
-
|
50
|
+
When I call "Products" on the service
|
50
51
|
And I run the query
|
51
52
|
Then the "DiscontinuedDate" method on the object should return a NilClass
|
52
|
-
|
53
|
-
|
@@ -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
|
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,15 @@ 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
|
9
20
|
end
|
10
21
|
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,13 +15,12 @@ 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
|
+
|
19
19
|
# Creates a new instance of the Class Property class
|
20
20
|
#
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(property_element)
|
21
|
+
# @param [Nokogiri::XML::Node] property_element from the EDMX
|
22
|
+
|
23
|
+
def initialize(property_element)
|
25
24
|
@name = property_element['Name']
|
26
25
|
@type = property_element['Type']
|
27
26
|
@nullable = ((property_element['Nullable'] && property_element['Nullable'] == "true") || property_element.name == 'NavigationProperty') || false
|
@@ -1,117 +1,152 @@
|
|
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
9
|
# Creates a new instance of the QueryBuilder class
|
10
10
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# ==== Optional
|
14
|
-
# Hash of additional parameters to use for a query
|
11
|
+
# @param [String] root entity collection to query against
|
12
|
+
# @param [Hash, {}] additional_params hash of additional parameters to use for a query
|
15
13
|
def initialize(root, additional_params = {})
|
16
|
-
@root = root.to_s
|
14
|
+
@root = Helpers.uri_escape(root.to_s)
|
17
15
|
@expands = []
|
18
16
|
@filters = []
|
19
17
|
@order_bys = []
|
18
|
+
@navigation_paths = []
|
20
19
|
@skip = nil
|
21
20
|
@top = nil
|
22
|
-
@
|
21
|
+
@count = nil
|
22
|
+
@links_navigation_property = nil
|
23
23
|
@additional_params = additional_params
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
# 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
27
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
28
|
+
# @param [String] path of the entity to expand relative to the root
|
29
|
+
# @example
|
30
|
+
# # Without expanding the query (no Category will be filled in for the Product)
|
31
|
+
# svc.Products(1)
|
32
|
+
# prod1 = svc.execute
|
34
33
|
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
34
|
+
# # With expanding the query (the Category will be filled in)
|
35
|
+
# svc.Products(1).expand('Category')
|
36
|
+
# prod1 = svc.execute
|
38
37
|
def expand(path)
|
39
38
|
@expands << path
|
40
39
|
self
|
41
40
|
end
|
42
|
-
|
41
|
+
|
43
42
|
# Used to filter data being returned
|
44
|
-
# ==== Required Attributes
|
45
|
-
# - filter: The conditions to apply to the query
|
46
43
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
44
|
+
# @param [String] filter conditions to apply to the query
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# svc.Products.filter("Name eq 'Product 2'")
|
48
|
+
# products = svc.execute
|
50
49
|
def filter(filter)
|
51
50
|
@filters << CGI.escape(filter)
|
52
51
|
self
|
53
52
|
end
|
54
|
-
|
53
|
+
|
55
54
|
# 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
55
|
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
56
|
+
# @param [String] order_by the order by statement. Note to specify direction, use "desc" or "asc"; must be lowercase
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# svc.Products.order_by("Name")
|
60
|
+
# products = svc.execute
|
62
61
|
def order_by(order_by)
|
63
62
|
@order_bys << CGI.escape(order_by)
|
64
63
|
self
|
65
64
|
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
|
-
#
|
65
|
+
|
66
|
+
# Used to skip a number of records
|
67
|
+
# This is typically used for paging, where it would be used along with the `top` method.
|
68
|
+
#
|
69
|
+
# @param [Integer] num the number of items to skip
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# svc.Products.skip(5)
|
73
|
+
# products = svc.execute # => skips the first 5 items
|
75
74
|
def skip(num)
|
76
75
|
@skip = num
|
77
76
|
self
|
78
77
|
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
|
-
#
|
78
|
+
|
79
|
+
# Used to take only the top X records
|
80
|
+
# This is typically used for paging, where it would be used along with the `skip` method.
|
81
|
+
#
|
82
|
+
# @param [Integer] num the number of items to return
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# svc.Products.top(5)
|
86
|
+
# products = svc.execute # => returns only the first 5 items
|
88
87
|
def top(num)
|
89
88
|
@top = num
|
90
89
|
self
|
91
90
|
end
|
92
|
-
|
91
|
+
|
93
92
|
# Used to return links instead of actual objects
|
94
|
-
# ==== Required Attributes
|
95
|
-
# - navigation_property: The NavigationProperty name to retrieve the links for
|
96
93
|
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
94
|
+
# @param [String] navigation_property the NavigationProperty name to retrieve the links for
|
95
|
+
#
|
96
|
+
# @raise [NotSupportedError] if count has already been called on the query
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# svc.Categories(1).links("Products")
|
100
|
+
# product_links = svc.execute # => returns URIs for the products under the Category with an ID of 1
|
100
101
|
def links(navigation_property)
|
101
|
-
|
102
|
+
raise OData::NotSupportedError.new("You cannot call both the `links` method and the `count` method in the same query.") if @count
|
103
|
+
@links_navigation_property = navigation_property
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# Used to return a count of objects instead of the objects themselves
|
108
|
+
#
|
109
|
+
# @raise [NotSupportedError] if links has already been called on the query
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# svc.Products
|
113
|
+
# svc.count
|
114
|
+
# product_count = svc.execute
|
115
|
+
def count
|
116
|
+
raise OData::NotSupportedError.new("You cannot call both the `links` method and the `count` method in the same query.") if @links_navigation_property
|
117
|
+
@count = true
|
102
118
|
self
|
103
119
|
end
|
104
|
-
|
120
|
+
|
121
|
+
# Used to navigate to a child collection, typically used to filter or perform a similar function against the children
|
122
|
+
#
|
123
|
+
# @param [String] navigation_property the NavigationProperty to drill-down into
|
124
|
+
#
|
125
|
+
# @example
|
126
|
+
# svc.Genres('Horror Movies').navigate("Titles").filter("Name eq 'Halloween'")
|
127
|
+
def navigate(navigation_property)
|
128
|
+
@navigation_paths << Helpers.uri_escape(navigation_property)
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
105
132
|
# Builds the query URI (path, not including root) incorporating expands, filters, etc.
|
106
133
|
# This is used internally when the execute method is called on the service
|
107
134
|
def query
|
108
135
|
q = @root.clone
|
109
|
-
|
136
|
+
|
137
|
+
# Navigation paths come first in the query
|
138
|
+
q << "/" + @navigation_paths.join("/") unless @navigation_paths.empty?
|
139
|
+
|
110
140
|
# Handle links queries, this isn't just a standard query option
|
111
|
-
if @
|
112
|
-
q << "/$links/#{@
|
141
|
+
if @links_navigation_property
|
142
|
+
q << "/$links/#{@links_navigation_property}"
|
113
143
|
end
|
114
|
-
|
144
|
+
|
145
|
+
# Handle count queries, this isn't just a standard query option
|
146
|
+
if @count
|
147
|
+
q << "/$count"
|
148
|
+
end
|
149
|
+
|
115
150
|
query_options = []
|
116
151
|
query_options << "$expand=#{@expands.join(',')}" unless @expands.empty?
|
117
152
|
query_options << "$filter=#{@filters.join('+and+')}" unless @filters.empty?
|
@@ -121,7 +156,7 @@ class QueryBuilder
|
|
121
156
|
query_options << @additional_params.to_query unless @additional_params.empty?
|
122
157
|
if !query_options.empty?
|
123
158
|
q << "?"
|
124
|
-
q << query_options.join('&')
|
159
|
+
q << query_options.join('&')
|
125
160
|
end
|
126
161
|
return q
|
127
162
|
end
|