rod-rest 0.0.1.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/Readme.md +110 -0
- data/changelog.txt +11 -0
- data/lib/rod/rest.rb +1 -0
- data/lib/rod/rest/api.rb +148 -50
- data/lib/rod/rest/client.rb +95 -23
- data/lib/rod/rest/collection_proxy.rb +27 -5
- data/lib/rod/rest/constants.rb +1 -1
- data/lib/rod/rest/exception.rb +1 -0
- data/lib/rod/rest/metadata.rb +10 -0
- data/lib/rod/rest/property_metadata.rb +12 -0
- data/lib/rod/rest/proxy.rb +46 -1
- data/lib/rod/rest/proxy_cache.rb +65 -0
- data/lib/rod/rest/proxy_factory.rb +12 -0
- data/lib/rod/rest/resource_metadata.rb +11 -0
- data/test/int/end_to_end.rb +37 -30
- data/test/spec/api.rb +132 -35
- data/test/spec/client.rb +137 -38
- data/test/spec/collection_proxy.rb +52 -3
- data/test/spec/metadata.rb +24 -0
- data/test/spec/property_metadata.rb +31 -6
- data/test/spec/proxy.rb +50 -2
- data/test/spec/proxy_cache.rb +80 -0
- data/test/spec/proxy_factory.rb +16 -1
- data/test/spec/resource_metadata.rb +12 -0
- metadata +6 -2
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -1,3 +1,113 @@
|
|
1
1
|
# rod-rest
|
2
2
|
|
3
3
|
REST API for [Ruby Object Database](https://github.com/apohllo/rod)
|
4
|
+
|
5
|
+
|
6
|
+
## Server
|
7
|
+
|
8
|
+
Starting the server is as simple as:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
SomeDatabase.instance.open_database("path/to/rod/database")
|
12
|
+
Rod::Rest::API.start_with_database(SomeDatabase.instance)
|
13
|
+
```
|
14
|
+
|
15
|
+
It starts Sinatra application listening by default on port 4567.
|
16
|
+
|
17
|
+
## Client
|
18
|
+
|
19
|
+
The client requires a `http_client` to be passed to the constructor. We
|
20
|
+
recommend Faraday, e.g.
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
faraday = Faraday.new(url: "http://localhost:4567")
|
24
|
+
client = Rod::Rest::Client.new(http_client: faraday)
|
25
|
+
```
|
26
|
+
|
27
|
+
The client automatically fetches metadata, so there is no need to set it up.
|
28
|
+
Assuming you have the following Rod classes defined on the server side:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class Person < Rod::Model
|
32
|
+
field :name, :string, index: :hash
|
33
|
+
field :surname, :string, index: :hash
|
34
|
+
end
|
35
|
+
|
36
|
+
class Car < Rod::Model
|
37
|
+
field :brand, :string, index: :hash
|
38
|
+
has_one :owner, class_name: "Person"
|
39
|
+
has_many :drivers, class_name: "Person"
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
|
44
|
+
The client provides the following calls:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# return people count
|
48
|
+
client.people_count()
|
49
|
+
|
50
|
+
# find person by ROD id
|
51
|
+
client.find_person(1)
|
52
|
+
|
53
|
+
# find several people by their ROD ids
|
54
|
+
client.find_people(1,2,3)
|
55
|
+
# or
|
56
|
+
client.find_people(1..3)
|
57
|
+
|
58
|
+
# find people by name
|
59
|
+
client.find_people_by_name("Albert")
|
60
|
+
|
61
|
+
# find people by surname
|
62
|
+
client.find_people_by_surname("Einstein")
|
63
|
+
|
64
|
+
# return cars count
|
65
|
+
client.cars_count()
|
66
|
+
|
67
|
+
# find cars by brand
|
68
|
+
car = client.find_cars_by_brand("Mercedes").first
|
69
|
+
car.owner # returns proxy to singular association
|
70
|
+
car.drivers # returns collection proxy
|
71
|
+
car.drivers.each do |driver|
|
72
|
+
puts driver.name
|
73
|
+
end
|
74
|
+
|
75
|
+
puts car.drivers.first.name
|
76
|
+
|
77
|
+
car.drivers[1..2].each do |driver| # negative indices are not yet supported
|
78
|
+
puts driver.surname
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
There are also some more low-level API calls supported, by usually when you get the
|
83
|
+
first object of some larger graph, there is no need to use them.
|
84
|
+
|
85
|
+
|
86
|
+
## License
|
87
|
+
|
88
|
+
(The MIT/X11 License)
|
89
|
+
|
90
|
+
Copyright (c) 2014 Aleksander Pohl
|
91
|
+
|
92
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
93
|
+
a copy of this software and associated documentation files (the
|
94
|
+
'Software'), to deal in the Software without restriction, including
|
95
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
96
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
97
|
+
permit persons to whom the Software is furnished to do so, subject to
|
98
|
+
the following conditions:
|
99
|
+
|
100
|
+
The above copyright notice and this permission notice shall be
|
101
|
+
included in all copies or substantial portions of the Software.
|
102
|
+
|
103
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
104
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
105
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
106
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
107
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
108
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
109
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
110
|
+
|
111
|
+
## Feedback
|
112
|
+
|
113
|
+
* mailto:apohllo@o2.pl
|
data/changelog.txt
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
0.5.0
|
2
|
+
#inspect & #to_s for major classes
|
3
|
+
ProxyCache
|
4
|
+
Improved readme
|
5
|
+
Caching in CollectionProxy
|
6
|
+
Support for index ranges and collections
|
7
|
+
Support for id ranges and collections
|
8
|
+
0.0.1.1
|
9
|
+
Fix Ruby restriction in gemspec
|
10
|
+
0.0.1
|
11
|
+
Working version of the API
|
data/lib/rod/rest.rb
CHANGED
data/lib/rod/rest/api.rb
CHANGED
@@ -15,59 +15,13 @@ module Rod
|
|
15
15
|
def build_api_for(resource,options={})
|
16
16
|
serializer = options[:serializer] || JsonSerializer.new
|
17
17
|
resource_name = options[:resource_name] || plural_resource_name(resource)
|
18
|
-
get "/#{resource_name}" do
|
19
|
-
if params.empty?
|
20
|
-
serializer.serialize({count: resource.count})
|
21
|
-
elsif params.size == 1
|
22
|
-
name, value = params.first
|
23
|
-
if resource.respond_to?("find_all_by_#{name}")
|
24
|
-
serializer.serialize(resource.send("find_all_by_#{name}",value))
|
25
|
-
else
|
26
|
-
status 404
|
27
|
-
serializer.serialize(nil)
|
28
|
-
end
|
29
|
-
else
|
30
|
-
status 404
|
31
|
-
serializer.serialize(nil)
|
32
|
-
end
|
33
|
-
end
|
34
18
|
|
35
|
-
|
36
|
-
|
37
|
-
if object
|
38
|
-
serializer.serialize(object)
|
39
|
-
else
|
40
|
-
status 404
|
41
|
-
serializer.serialize(nil)
|
42
|
-
end
|
43
|
-
end
|
19
|
+
define_index(resource,resource_name,serializer)
|
20
|
+
define_show(resource,resource_name,serializer)
|
44
21
|
|
45
22
|
resource.plural_associations.each do |property|
|
46
|
-
|
47
|
-
|
48
|
-
if object
|
49
|
-
serializer.serialize({count: object.send("#{property.name}_count") })
|
50
|
-
else
|
51
|
-
status 404
|
52
|
-
serializer.serialize(nil)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
get "/#{resource_name}/:id/#{property.name}/:index" do
|
57
|
-
object = resource.find_by_rod_id(params[:id].to_i)
|
58
|
-
if object
|
59
|
-
related_object = object.send(property.name)[params[:index].to_i]
|
60
|
-
if related_object
|
61
|
-
serializer.serialize(related_object)
|
62
|
-
else
|
63
|
-
status 404
|
64
|
-
serializer.serialize(nil)
|
65
|
-
end
|
66
|
-
else
|
67
|
-
status 404
|
68
|
-
serializer.serialize(nil)
|
69
|
-
end
|
70
|
-
end
|
23
|
+
define_association_index(resource,resource_name,property,serializer)
|
24
|
+
define_association_show(resource,resource_name,property,serializer)
|
71
25
|
end
|
72
26
|
end
|
73
27
|
|
@@ -94,6 +48,150 @@ module Rod
|
|
94
48
|
end
|
95
49
|
run!(web_options)
|
96
50
|
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
# GET /cars
|
54
|
+
# GET /cars?name=Mercedes
|
55
|
+
def define_index(resource,resource_name,serializer)
|
56
|
+
get index_path(resource_name) do
|
57
|
+
case params.size
|
58
|
+
when 0
|
59
|
+
respond_with_count(resource,serializer)
|
60
|
+
when 1
|
61
|
+
index_name, searched_value = params.first
|
62
|
+
respond_with_indexed_resource(resource,index_name,searched_value,serializer)
|
63
|
+
else
|
64
|
+
respond_with_nil(serializer)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# GET /cars/1
|
70
|
+
# GET /cars/1..3
|
71
|
+
# GET /cars/1,2,3
|
72
|
+
def define_show(resource,resource_name,serializer)
|
73
|
+
get show_path(resource_name) do
|
74
|
+
respond_with_resource(params[:id],resource,serializer)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# GET /cars/1/drivers
|
79
|
+
def define_association_index(resource,resource_name,property,serializer)
|
80
|
+
get association_index_path(resource_name,property.name) do
|
81
|
+
respond_with_related_count(resource,property.name,params[:id].to_i,serializer)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# GET /cars/1/drivers/0
|
86
|
+
# GET /cars/1/drivers/0..2
|
87
|
+
# GET /cars/1/drivers/0,1,2
|
88
|
+
def define_association_show(resource,resource_name,property,serializer)
|
89
|
+
get association_show_path(resource_name,property.name) do
|
90
|
+
respond_with_related_resource(params[:id].to_i,params[:index],resource,property,serializer)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def index_path(resource_name)
|
95
|
+
"/#{resource_name}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def show_path(resource_name)
|
99
|
+
"/#{resource_name}/:id"
|
100
|
+
end
|
101
|
+
|
102
|
+
def association_index_path(resource_name,property_name)
|
103
|
+
"/#{resource_name}/:id/#{property_name}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def association_show_path(resource_name,property_name)
|
107
|
+
"/#{resource_name}/:id/#{property_name}/:index"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
def respond_with_resource(id_param,resource,serializer)
|
113
|
+
id_or_range = extract_elements(id_param)
|
114
|
+
result =
|
115
|
+
if Integer === id_or_range
|
116
|
+
fetch_one(id_or_range,resource)
|
117
|
+
else
|
118
|
+
fetch_collection(id_or_range,resource)
|
119
|
+
end
|
120
|
+
serializer.serialize(result)
|
121
|
+
end
|
122
|
+
|
123
|
+
def respond_with_related_resource(id,index_param,resource,property,serializer)
|
124
|
+
object = resource.find_by_rod_id(id)
|
125
|
+
if object
|
126
|
+
index_or_range = extract_elements(index_param)
|
127
|
+
result =
|
128
|
+
if Integer === index_or_range
|
129
|
+
fetch_one_related(index_or_range,object,property)
|
130
|
+
else
|
131
|
+
fetch_related_collection(index_or_range,object,property)
|
132
|
+
end
|
133
|
+
serializer.serialize(result)
|
134
|
+
else
|
135
|
+
respond_with_nil(serializer)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def respond_with_count(resource,serializer)
|
140
|
+
serializer.serialize({count: resource.count})
|
141
|
+
end
|
142
|
+
|
143
|
+
def respond_with_related_count(resource,property_name,id,serializer)
|
144
|
+
object = resource.find_by_rod_id(id)
|
145
|
+
if object
|
146
|
+
serializer.serialize({count: object.send("#{property_name}_count") })
|
147
|
+
else
|
148
|
+
respond_with_nil(serializer)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def respond_with_indexed_resource(resource,index_name,searched_value,serializer)
|
153
|
+
if resource.respond_to?("find_all_by_#{index_name}")
|
154
|
+
serializer.serialize(resource.send("find_all_by_#{index_name}",searched_value))
|
155
|
+
else
|
156
|
+
respond_with_nil(serializer)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def fetch_collection(ids,resource)
|
161
|
+
ids.map{|id| resource.find_by_rod_id(id) }.compact
|
162
|
+
end
|
163
|
+
|
164
|
+
def fetch_one(id,resource)
|
165
|
+
resource.find_by_rod_id(id) || report_not_found
|
166
|
+
end
|
167
|
+
|
168
|
+
def fetch_related_collection(indices,object,property)
|
169
|
+
indices.map{|index| object.send(property.name)[index] }.compact
|
170
|
+
end
|
171
|
+
|
172
|
+
def fetch_one_related(index,object,property)
|
173
|
+
object.send(property.name)[index] || report_not_found
|
174
|
+
end
|
175
|
+
|
176
|
+
def extract_elements(id)
|
177
|
+
case id
|
178
|
+
when /^(\d+)\.\.(\d+)/
|
179
|
+
($~[1].to_i..$~[2].to_i)
|
180
|
+
when /,/
|
181
|
+
id.split(",").map(&:to_i)
|
182
|
+
else
|
183
|
+
id.to_i
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def report_not_found
|
188
|
+
status 404
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
|
192
|
+
def respond_with_nil(serializer)
|
193
|
+
report_not_found
|
194
|
+
serializer.serialize(nil)
|
97
195
|
end
|
98
196
|
end
|
99
197
|
end
|
data/lib/rod/rest/client.rb
CHANGED
@@ -18,11 +18,18 @@ module Rod
|
|
18
18
|
# be provided).
|
19
19
|
# * metadata_factory - factory used to build the metadata (used only if
|
20
20
|
# metadata was not provided).
|
21
|
+
# * proxy_cache - used to cache proxied objects. By default it is
|
22
|
+
# ProxyCache. Might be disabled by passing +nil+.
|
21
23
|
def initialize(options={})
|
22
24
|
@web_client = options.fetch(:http_client)
|
23
25
|
@parser = options[:parser] || JSON
|
24
26
|
@proxy_factory_class = options[:factory] || ProxyFactory
|
25
27
|
@url_encoder = options[:url_encoder] || CGI
|
28
|
+
if options.has_key?(:proxy_cache)
|
29
|
+
@proxy_cache = options[:proxy_cache]
|
30
|
+
else
|
31
|
+
@proxy_cache = ProxyCache.new
|
32
|
+
end
|
26
33
|
|
27
34
|
@metadata = options[:metadata]
|
28
35
|
if @metadata
|
@@ -46,7 +53,7 @@ module Rod
|
|
46
53
|
def fetch_object(object_stub)
|
47
54
|
check_stub(object_stub)
|
48
55
|
check_method(object_stub)
|
49
|
-
__send__(
|
56
|
+
__send__(primary_finder_method(object_stub[:type]),object_stub[:rod_id])
|
50
57
|
end
|
51
58
|
|
52
59
|
# Fetch object related via the association to the +subject+.
|
@@ -54,10 +61,19 @@ module Rod
|
|
54
61
|
# the +index+-th element in the collection.
|
55
62
|
def fetch_related_object(subject,association_name,index)
|
56
63
|
check_subject_and_association(subject,association_name)
|
57
|
-
__send__(
|
64
|
+
__send__(association_method(subject.type,association_name),subject.rod_id,index)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Fetch objects related via the association to the +subject+.
|
68
|
+
# The association name is +association_name+ and the objects are the
|
69
|
+
# objects indicated by the idices. This might be a range or a comma
|
70
|
+
# separated list of indices.
|
71
|
+
def fetch_related_objects(subject,association_name,*indices)
|
72
|
+
check_subject_and_association(subject,association_name)
|
73
|
+
__send__(plural_association_method(subject.type,association_name),subject.rod_id,*indices)
|
58
74
|
end
|
59
75
|
|
60
|
-
# Overrided in order to fetch the metadata
|
76
|
+
# Overrided in order to fetch the metadata if it was not provided in the
|
61
77
|
# constructor.
|
62
78
|
def method_missing(*args)
|
63
79
|
unless @metadata.nil?
|
@@ -68,6 +84,16 @@ module Rod
|
|
68
84
|
self.send(*args)
|
69
85
|
end
|
70
86
|
|
87
|
+
# Detailed description of the client.
|
88
|
+
def inspect
|
89
|
+
"Rod::Rest::Client<port: #{@web_client.port}, host: #{@web_client.host}>"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Short description of the client.
|
93
|
+
def to_s
|
94
|
+
"ROD REST API client"
|
95
|
+
end
|
96
|
+
|
71
97
|
private
|
72
98
|
def fetch_metadata
|
73
99
|
response = @web_client.get(metadata_path())
|
@@ -81,17 +107,17 @@ module Rod
|
|
81
107
|
define_counters(metadata)
|
82
108
|
define_finders(metadata)
|
83
109
|
define_relations(metadata)
|
84
|
-
@factory = @proxy_factory_class.new(metadata.resources,self)
|
110
|
+
@factory = @proxy_factory_class.new(metadata.resources,self,cache: @proxy_cache)
|
85
111
|
end
|
86
112
|
|
87
113
|
def define_counters(metadata)
|
88
114
|
metadata.resources.each do |resource|
|
89
|
-
self.define_singleton_method(
|
90
|
-
|
115
|
+
self.define_singleton_method(count_method(resource)) do
|
116
|
+
return_count(count_path(resource))
|
91
117
|
end
|
92
118
|
resource.plural_associations.each do |association|
|
93
|
-
self.define_singleton_method(
|
94
|
-
|
119
|
+
self.define_singleton_method(association_count_method(resource,association.name)) do |id|
|
120
|
+
return_count(association_count_path(resource,id,association.name))
|
95
121
|
end
|
96
122
|
end
|
97
123
|
end
|
@@ -99,12 +125,15 @@ module Rod
|
|
99
125
|
|
100
126
|
def define_finders(metadata)
|
101
127
|
metadata.resources.each do |resource|
|
102
|
-
self.define_singleton_method(
|
103
|
-
|
128
|
+
self.define_singleton_method(primary_finder_method(resource)) do |id|
|
129
|
+
return_single(primary_resource_finder_path(resource,id))
|
130
|
+
end
|
131
|
+
self.define_singleton_method(plural_finder_method(resource)) do |*id|
|
132
|
+
return_collection(plural_resource_finder_path(resource,id))
|
104
133
|
end
|
105
134
|
resource.indexed_properties.each do |property|
|
106
|
-
self.define_singleton_method(
|
107
|
-
|
135
|
+
self.define_singleton_method(finder_method(resource,property.name)) do |value|
|
136
|
+
return_collection(resource_finder_path(resource,property.name,value))
|
108
137
|
end
|
109
138
|
end
|
110
139
|
end
|
@@ -113,13 +142,28 @@ module Rod
|
|
113
142
|
def define_relations(metadata)
|
114
143
|
metadata.resources.each do |resource|
|
115
144
|
resource.plural_associations.each do |association|
|
116
|
-
self.define_singleton_method(
|
117
|
-
|
145
|
+
self.define_singleton_method(association_method(resource,association.name)) do |id,index|
|
146
|
+
return_single(association_path(resource,association.name,id,index))
|
147
|
+
end
|
148
|
+
self.define_singleton_method(plural_association_method(resource,association.name)) do |id,*indices|
|
149
|
+
return_collection(plural_association_path(resource,association.name,id,*indices))
|
118
150
|
end
|
119
151
|
end
|
120
152
|
end
|
121
153
|
end
|
122
154
|
|
155
|
+
def return_count(path)
|
156
|
+
get_parsed_response(path)[:count]
|
157
|
+
end
|
158
|
+
|
159
|
+
def return_single(path)
|
160
|
+
@factory.build(get_parsed_response(path))
|
161
|
+
end
|
162
|
+
|
163
|
+
def return_collection(path)
|
164
|
+
get_parsed_response(path).map{|hash| @factory.build(hash) }
|
165
|
+
end
|
166
|
+
|
123
167
|
def get_parsed_response(path)
|
124
168
|
result = @web_client.get(path)
|
125
169
|
check_status(result,path)
|
@@ -144,18 +188,18 @@ module Rod
|
|
144
188
|
end
|
145
189
|
|
146
190
|
def check_method(object_stub)
|
147
|
-
unless self.respond_to?(
|
148
|
-
raise APIError.new(invalid_method_error(
|
191
|
+
unless self.respond_to?(primary_finder_method(object_stub[:type]))
|
192
|
+
raise APIError.new(invalid_method_error(primary_finder_method(object_stub[:type])))
|
149
193
|
end
|
150
194
|
end
|
151
195
|
|
152
196
|
def check_subject_and_association(subject,association_name)
|
153
|
-
unless self.respond_to?(
|
154
|
-
raise APIError.new(invalid_method_error(
|
197
|
+
unless self.respond_to?(association_method(subject.type,association_name))
|
198
|
+
raise APIError.new(invalid_method_error(association_method(subject.type,association_name)))
|
155
199
|
end
|
156
200
|
end
|
157
201
|
|
158
|
-
def
|
202
|
+
def count_path(resource)
|
159
203
|
"/#{plural_resource_name(resource)}"
|
160
204
|
end
|
161
205
|
|
@@ -163,6 +207,10 @@ module Rod
|
|
163
207
|
"/#{plural_resource_name(resource)}/#{id}"
|
164
208
|
end
|
165
209
|
|
210
|
+
def plural_resource_finder_path(resource,*ids)
|
211
|
+
"/#{plural_resource_name(resource)}/#{convert_path_elements(*ids)}"
|
212
|
+
end
|
213
|
+
|
166
214
|
def resource_finder_path(resource,property_name,value)
|
167
215
|
"/#{plural_resource_name(resource)}#{finder_query(property_name,value)}"
|
168
216
|
end
|
@@ -175,26 +223,50 @@ module Rod
|
|
175
223
|
"/#{plural_resource_name(resource)}/#{id}/#{association_name}/#{index}"
|
176
224
|
end
|
177
225
|
|
226
|
+
def plural_association_path(resource,association_name,id,*indices)
|
227
|
+
"/#{plural_resource_name(resource)}/#{id}/#{association_name}/#{convert_path_elements(*indices)}"
|
228
|
+
end
|
229
|
+
|
178
230
|
def metadata_path
|
179
231
|
"/metadata"
|
180
232
|
end
|
181
233
|
|
182
|
-
def
|
234
|
+
def convert_path_elements(*elements)
|
235
|
+
if elements.size == 1 && Range === elements.first
|
236
|
+
elements.first.to_s
|
237
|
+
else
|
238
|
+
elements.join(",")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def count_method(resource)
|
243
|
+
"#{plural_resource_name(resource)}_count"
|
244
|
+
end
|
245
|
+
|
246
|
+
def primary_finder_method(resource)
|
183
247
|
"find_#{singular_resource_name(resource)}"
|
184
248
|
end
|
185
249
|
|
186
|
-
def
|
250
|
+
def plural_finder_method(resource)
|
251
|
+
"find_#{plural_resource_name(resource)}"
|
252
|
+
end
|
253
|
+
|
254
|
+
def finder_method(resource,property_name)
|
187
255
|
"find_#{plural_resource_name(resource)}_by_#{property_name}"
|
188
256
|
end
|
189
257
|
|
190
|
-
def
|
258
|
+
def association_count_method(resource,association_name)
|
191
259
|
"#{singular_resource_name(resource)}_#{association_name}_count"
|
192
260
|
end
|
193
261
|
|
194
|
-
def
|
262
|
+
def association_method(resource,association_name)
|
195
263
|
"#{singular_resource_name(resource)}_#{association_name.to_s.singularize}"
|
196
264
|
end
|
197
265
|
|
266
|
+
def plural_association_method(resource,association_name)
|
267
|
+
"#{singular_resource_name(resource)}_#{association_name.to_s}"
|
268
|
+
end
|
269
|
+
|
198
270
|
def finder_query(property_name,value)
|
199
271
|
"?#{@url_encoder.escape(property_name)}=#{@url_encoder.escape(value)}"
|
200
272
|
end
|