ruby_odata 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +14 -5
- data/README.rdoc +35 -2
- data/VERSION +1 -1
- data/features/query_builder.feature +58 -1
- data/features/step_definitions/service_steps.rb +7 -0
- data/lib/ruby_odata/query_builder.rb +31 -1
- data/ruby_odata.gemspec +1 -1
- data/test/blueprints.rb +3 -3
- metadata +3 -3
data/CHANGELOG.rdoc
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
= ruby_odata Change Log
|
2
2
|
|
3
3
|
=== 0.0.1
|
4
|
-
*
|
5
|
-
*
|
6
|
-
* Query:
|
4
|
+
* New Features
|
5
|
+
* Basic CRUD Operations
|
6
|
+
* Query Enhancement: Filters
|
7
|
+
* Query Enhancement: Expands
|
7
8
|
|
8
9
|
=== 0.0.2
|
9
|
-
*
|
10
|
+
* New Features
|
11
|
+
* Query Enhancement: Order By (both desc and asc)
|
10
12
|
|
11
13
|
=== 0.0.3
|
12
|
-
*
|
14
|
+
* Bug Fixes
|
15
|
+
* Rearranged code to match the gem name. Things were mismatched between odata_ruby and ruby_odata.
|
13
16
|
|
17
|
+
=== 0.0.4
|
18
|
+
* New Features
|
19
|
+
* Query Enhancement: skip
|
20
|
+
* Query Enhancement: top
|
21
|
+
* Ability to perform paging using skip and top together
|
22
|
+
* Updated README with examples for order_by, skip, and top
|
data/README.rdoc
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
Error: The word "DATABASE SETUP - DO THIS FIRST*" is invalid. The character ' ' (U+20) may not appear in the middle of a word.
|
1
2
|
= ruby_odata
|
2
3
|
|
3
4
|
The <b>Open Data Protocol</b> (OData) is a fantastic way to query and update data over standard Web technologies. The ruby_odata library acts as a consumer of OData services.
|
@@ -60,9 +61,12 @@ Querying is easy, for example to pull all the categories from the SampleService,
|
|
60
61
|
categories = svc.execute
|
61
62
|
puts categories.to_json
|
62
63
|
|
63
|
-
You can also expand
|
64
|
+
You can also expand, add filters, order, skip records, and take only the top X records to the query before executing it. For example:
|
64
65
|
|
65
66
|
=== Expanding
|
67
|
+
Expanding allows you to eagerly load other objects that are children of the root.
|
68
|
+
You can use more than one expand on a query.
|
69
|
+
For expanding grandchild and lower entities, you must pass in the full path from the root, for example +Products.expand('Orders').expand('Orders/LineItems')+
|
66
70
|
|
67
71
|
# Without expanding the query
|
68
72
|
svc.Products(1)
|
@@ -78,6 +82,8 @@ You can also expand and add filters to the query before executing it. For examp
|
|
78
82
|
|
79
83
|
|
80
84
|
=== Filtering
|
85
|
+
The syntax for filtering can be found on the {OData Protocol URI Conventions}[http://www.odata.org/developers/protocols/uri-conventions#FilterSystemQueryOption] page.
|
86
|
+
You can use more than one filter, if you call the filter method multiple times it will before an AND.
|
81
87
|
|
82
88
|
# You can access by ID (but that isn't is a filter)
|
83
89
|
# The syntax is just svc.ENTITYNAME(ID) which is shown in the expanding examples above
|
@@ -88,17 +94,44 @@ You can also expand and add filters to the query before executing it. For examp
|
|
88
94
|
puts "#{prod.to_json}"
|
89
95
|
|
90
96
|
=== Combining Expanding and Filtering
|
97
|
+
The query operations follow a {fluent interface}[http://en.wikipedia.org/wiki/Fluent_interface], although they can be added by themselves as well as chained
|
91
98
|
|
92
99
|
svc.Products.filter("Name eq 'Product 2'").expand("Category")
|
93
100
|
prod = svc.execute
|
94
101
|
puts "Filtering on Name eq 'Product 2' and expanding"
|
95
102
|
puts "#{prod.to_json}"
|
96
103
|
|
104
|
+
=== Order By
|
105
|
+
You can order the results by properties of your choice, either ascending or descending.
|
106
|
+
Order by are similar to +expand+s in that you can use more than one of them on a query.
|
107
|
+
For expanding grandchild and lower entities, you must pass in the full path from the root like would do on an +expand+
|
97
108
|
|
109
|
+
svc.Products.order_by("Name")
|
110
|
+
products = svc.execute
|
111
|
+
|
112
|
+
# Specifically requesting descending
|
113
|
+
svc.Products.order_by("Name desc")
|
114
|
+
products = svc.execute
|
115
|
+
|
116
|
+
# Specifically requesting ascending
|
117
|
+
svc.Products.order_by("Name asc")
|
118
|
+
products = svc.execute
|
119
|
+
|
120
|
+
=== Skip
|
121
|
+
Skip allows you to skip a number of records when querying. This is often used for paging along with +top+.
|
122
|
+
|
123
|
+
svc.Products.skip(5)
|
124
|
+
products = svc.execute # => skips the first 5 items
|
125
|
+
|
126
|
+
=== Top
|
127
|
+
Top allows you only retrieve the top X number of records when querying. This is often used for paging along with +skip+.
|
128
|
+
|
129
|
+
svc.Products.top(5)
|
130
|
+
products = svc.execute # => returns only the first 5 items
|
98
131
|
|
99
132
|
== Tests
|
100
133
|
*DATABASE SETUP - DO THIS FIRST*
|
101
|
-
Within /test/SampleService/App_Data/ rename _TestDB*.* to TestDB*.*. This file is just the
|
134
|
+
Within /test/SampleService/App_Data/ rename _TestDB*.* to TestDB*.*. This file is just the initial database, and needs to be renamed so that unwanted changes to that DB aren't persisted in source control.
|
102
135
|
|
103
136
|
All of the tests are written using Cucumber going against a sample service (Found in /test/SampleService/*). The SampleService is an ASP.NET Web Site running a SQLEXPRESS 2008 R2 Database (TestDB), as well as the ADO.NET Entity Framework and a WCF Data Service. In order to run the tests, you need to spin up the SampleService and have it running on port 8888 (http://localhost:8888/SampleService).
|
104
137
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
@@ -21,8 +21,9 @@ Scenario: Navigation Properties should be able to be eager loaded
|
|
21
21
|
And the method "Id" on the result's method "Category" should equal: "1"
|
22
22
|
|
23
23
|
|
24
|
-
#
|
24
|
+
# Filters
|
25
25
|
Scenario: Filters should be allowed on the root level entity
|
26
|
+
# Filter
|
26
27
|
Given I call "AddToProducts" on the service with a new "Product" object with Name: "Test Product"
|
27
28
|
When I save changes
|
28
29
|
When I call "Products" on the service
|
@@ -90,6 +91,62 @@ Scenario: Order by should access sorting acsending
|
|
90
91
|
| Product 5 |
|
91
92
|
|
92
93
|
|
94
|
+
# Skip
|
95
|
+
Scenario: Skip should be allowed on the root level entity
|
96
|
+
Given the following Products exist:
|
97
|
+
| Name |
|
98
|
+
| Product 1 |
|
99
|
+
| Product 2 |
|
100
|
+
| Product 3 |
|
101
|
+
| Product 4 |
|
102
|
+
| Product 5 |
|
103
|
+
When I call "Products" on the service
|
104
|
+
And I skip 3
|
105
|
+
And I run the query
|
106
|
+
Then the result should be:
|
107
|
+
| Name |
|
108
|
+
| Product 4 |
|
109
|
+
| Product 5 |
|
110
|
+
|
111
|
+
|
112
|
+
# Top
|
113
|
+
Scenario: Top should be allowed on the root level entity
|
114
|
+
Given the following Products exist:
|
115
|
+
| Name |
|
116
|
+
| Product 1 |
|
117
|
+
| Product 2 |
|
118
|
+
| Product 3 |
|
119
|
+
| Product 4 |
|
120
|
+
| Product 5 |
|
121
|
+
When I call "Products" on the service
|
122
|
+
And I ask for the top 3
|
123
|
+
And I run the query
|
124
|
+
Then the result should be:
|
125
|
+
| Name |
|
126
|
+
| Product 1 |
|
127
|
+
| Product 2 |
|
128
|
+
| Product 3 |
|
129
|
+
|
130
|
+
Scenario: Top should be able to be used along with skip for paging
|
131
|
+
Given the following Products exist:
|
132
|
+
| Name |
|
133
|
+
| Product 1 |
|
134
|
+
| Product 2 |
|
135
|
+
| Product 3 |
|
136
|
+
| Product 4 |
|
137
|
+
| Product 5 |
|
138
|
+
| Product 6 |
|
139
|
+
When I call "Products" on the service
|
140
|
+
And I skip 2
|
141
|
+
And I ask for the top 2
|
142
|
+
And I run the query
|
143
|
+
Then the result should be:
|
144
|
+
| Name |
|
145
|
+
| Product 3 |
|
146
|
+
| Product 4 |
|
147
|
+
|
148
|
+
|
149
|
+
|
93
150
|
|
94
151
|
|
95
152
|
|
@@ -62,6 +62,13 @@ When /^I order by: "([^\"]*)"$/ do |order|
|
|
62
62
|
@service_query.order_by(order)
|
63
63
|
end
|
64
64
|
|
65
|
+
When /^I skip (\d+)$/ do |skip|
|
66
|
+
@service_query.skip(skip)
|
67
|
+
end
|
68
|
+
|
69
|
+
When /^I ask for the top (\d+)$/ do |top|
|
70
|
+
@service_query.top(top)
|
71
|
+
end
|
65
72
|
|
66
73
|
Then /^the method "([^\"]*)" on the result should be of type "([^\"]*)"$/ do |method, type|
|
67
74
|
result = @service_result.send(method.to_sym)
|
@@ -15,6 +15,8 @@ class QueryBuilder
|
|
15
15
|
@expands = []
|
16
16
|
@filters = []
|
17
17
|
@order_bys = []
|
18
|
+
@skip = nil
|
19
|
+
@top = nil
|
18
20
|
end
|
19
21
|
|
20
22
|
# Used to eagerly-load data for nested objects, for example, obtaining a Category for a Product within one call to the server
|
@@ -36,7 +38,7 @@ class QueryBuilder
|
|
36
38
|
|
37
39
|
# Used to filter data being returned
|
38
40
|
# ==== Required Attributes
|
39
|
-
# - filter: The
|
41
|
+
# - filter: The conditions to apply to the query
|
40
42
|
#
|
41
43
|
# ==== Example
|
42
44
|
# svc.Products.filter("Name eq 'Product 2'")
|
@@ -58,6 +60,32 @@ class QueryBuilder
|
|
58
60
|
self
|
59
61
|
end
|
60
62
|
|
63
|
+
# Used to skip a number of records
|
64
|
+
# This is typically used for paging, where it would be used along with the +top+ method.
|
65
|
+
# ==== Required Attributes
|
66
|
+
# - num: The number of items to skip
|
67
|
+
#
|
68
|
+
# ==== Example
|
69
|
+
# svc.Products.skip(5)
|
70
|
+
# products = svc.execute # => skips the first 5 items
|
71
|
+
def skip(num)
|
72
|
+
@skip = num
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Used to take only the top X records
|
77
|
+
# This is typically used for paging, where it would be used along with the +skip+ method.
|
78
|
+
# ==== Required Attributes
|
79
|
+
# - num: The number of items to return
|
80
|
+
#
|
81
|
+
# ==== Example
|
82
|
+
# svc.Products.top(5)
|
83
|
+
# products = svc.execute # => returns only the first 5 items
|
84
|
+
def top(num)
|
85
|
+
@top = num
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
61
89
|
# Builds the query URI (path, not including root) incorporating expands, filters, etc.
|
62
90
|
# This is used internally when the execute method is called on the service
|
63
91
|
def query
|
@@ -66,6 +94,8 @@ class QueryBuilder
|
|
66
94
|
query_options << "$expand=#{@expands.join(',')}" unless @expands.empty?
|
67
95
|
query_options << "$filter=#{@filters.join('+and+')}" unless @filters.empty?
|
68
96
|
query_options << "$orderby=#{@order_bys.join(',')}" unless @order_bys.empty?
|
97
|
+
query_options << "$skip=#{@skip}" unless @skip.nil?
|
98
|
+
query_options << "$top=#{@top}" unless @top.nil?
|
69
99
|
if !query_options.empty?
|
70
100
|
q << "?"
|
71
101
|
q << query_options.join('&')
|
data/ruby_odata.gemspec
CHANGED
data/test/blueprints.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Sham.define do
|
2
|
-
category_name
|
3
|
-
product_name
|
4
|
-
price
|
2
|
+
category_name { |i| "Category #{i}" }
|
3
|
+
product_name { |i| "Widget #{i}" }
|
4
|
+
price(:unique => false) { ['5.00', '10.00', '20.00', '15.00' , '25.00', '7.50'].rand }
|
5
5
|
end
|
6
6
|
|
7
7
|
Product.blueprint do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_odata
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Damien White
|