sinatra-rabbit 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +32 -24
- data/Rakefile +44 -0
- data/lib/sinatra/docs/api.haml +8 -0
- data/lib/sinatra/docs/collection.haml +24 -0
- data/lib/sinatra/docs/operation.haml +26 -0
- data/lib/sinatra/rabbit/base.rb +140 -64
- data/lib/sinatra/rabbit/base_collection.rb +8 -5
- data/lib/sinatra/rabbit/dsl.rb +16 -8
- data/lib/sinatra/rabbit.rb +9 -2
- data/sinatra-rabbit.gemspec +4 -1
- data/tests/app_test.rb +72 -0
- data/tests/docs_test.rb +75 -0
- data/tests/dsl_test.rb +262 -0
- data/tests/fixtures.rb +149 -0
- data/tests/params_test.rb +98 -0
- metadata +13 -4
data/README.md
CHANGED
@@ -5,12 +5,15 @@ Sinatra::Rabbit
|
|
5
5
|
|
6
6
|
Sinatra::Rabbit is a [Sinatra](http://www.sinatrarb.com) extensions that
|
7
7
|
makes designing a [REST API](http://en.wikipedia.org/wiki/Representational_State_Transfer)
|
8
|
-
much easier.
|
8
|
+
much easier and more fun.
|
9
9
|
|
10
|
-
Rabbit
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
Rabbit maps REST resources to 'collections'. Every collection then could define
|
11
|
+
CRUD and other operations to manipulate with resource. Rabbit will handle
|
12
|
+
parameter validation and capability checks for you, so you can focus on the
|
13
|
+
structure and design of your REST API.
|
14
|
+
|
15
|
+
Once your REST API is designed, Rabbit will make it available as modular Sinatra
|
16
|
+
application thus mountable to any Rack compatible container (Rails, Padrino...)
|
14
17
|
|
15
18
|
Original Rabbit idea has been taken from the [Deltacloud API](http://deltacloud.org)
|
16
19
|
project. However this is an complete rewrite of the original Deltacloud Rabbit.
|
@@ -25,54 +28,59 @@ An example application (modular Deltacloud API) can be found here:
|
|
25
28
|
Features
|
26
29
|
-------
|
27
30
|
|
28
|
-
* Automatically generate proper path and use right HTTP method for all CRUD operations:
|
31
|
+
* Automatically generate proper path and use the right HTTP method for all CRUD operations:
|
29
32
|
* `operation :create` is being mapped as 'POST /:collection'
|
30
33
|
* `operation :index` is being mapped as 'GET /:collection'
|
31
34
|
* `operation :show` is being mapped as 'GET /:collection/:id'
|
32
|
-
* `operation :update` is being mapped as '
|
35
|
+
* `operation :update` is being mapped as 'PATCH /:collection/:id'
|
33
36
|
* `operation :destroy` is being mapped as 'DELETE /:collection/:id'
|
34
|
-
*
|
37
|
+
* `action :reboot` is being mapped as 'POST /:collection/:id/reboot'
|
38
|
+
* Automatically set the :id parameter for routes like :show or :destroy
|
39
|
+
* Automatically validates the request parameter values
|
40
|
+
* Allow to change the HTTP method (`action :reboot, :http_method => :post`)
|
41
|
+
* Allow to check if operation is supported by the current resource
|
35
42
|
<pre>
|
36
43
|
<code>
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
check_capability :for => lambda { |m| driver.respond_to? m }
|
45
|
+
|
46
|
+
collection :vms do
|
47
|
+
action :stop, :with_capability => :stop do
|
48
|
+
control do
|
49
|
+
# ...
|
50
|
+
end
|
51
|
+
end
|
43
52
|
end
|
44
53
|
</code>
|
45
54
|
</pre>
|
46
|
-
*
|
47
|
-
|
55
|
+
* Allow to define groups of parameters as features and make them available if
|
56
|
+
the current resource support them
|
57
|
+
* Support the 'sub-collections' that might be defined on resouce
|
48
58
|
<pre>
|
49
59
|
<code>
|
50
60
|
collection :buckets do
|
51
61
|
collection :blobs, :with_id => :bucket_id do
|
52
|
-
|
53
|
-
|
62
|
+
|
54
63
|
# GET /buckets/:bucket_id/blobs/:id
|
55
64
|
operation :show do
|
56
|
-
description "Show content of blob"
|
57
|
-
param :id, :string, :required
|
58
65
|
control { #... }
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
62
69
|
# GET /buckets/:id
|
63
70
|
operation :show do
|
64
|
-
description "Show content of bucket"
|
65
|
-
param :id, :string, :required
|
66
71
|
control { #... }
|
67
72
|
end
|
68
73
|
end
|
69
74
|
</code>
|
70
75
|
</pre>
|
71
|
-
* Generate HEAD routes for all operations and collections
|
76
|
+
* Generate HEAD routes for all operations and collections to check if the route
|
77
|
+
is available
|
72
78
|
* Generate OPTIONS routes for all operations and collections
|
73
79
|
* `OPTIONS /:collection` will return list of all operations (using 'Allow' header)
|
74
80
|
* `OPTIONS /:collection/:operation` will return list of parameters defined for given operation (using 'Allow' header)
|
75
|
-
*
|
81
|
+
* Allow to manipulate with the REST structure programmatically, to generate
|
82
|
+
documentation or collect statistics
|
83
|
+
* Include bunch of tests to prove it all works correctly
|
76
84
|
|
77
85
|
Configuration
|
78
86
|
-------
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
namespace :test do
|
4
|
+
|
5
|
+
desc 'Run all tests suites'
|
6
|
+
task :all do
|
7
|
+
Rake::Task['test:app'].invoke
|
8
|
+
Rake::Task['test:dsl'].invoke
|
9
|
+
Rake::Task['test:docs'].invoke
|
10
|
+
Rake::Task['test:param'].invoke
|
11
|
+
end
|
12
|
+
|
13
|
+
Rake::TestTask.new(:app) do |t|
|
14
|
+
t.test_files = FileList['tests/app_test.rb']
|
15
|
+
t.verbose = false
|
16
|
+
t.options = "-v"
|
17
|
+
end
|
18
|
+
|
19
|
+
Rake::TestTask.new(:dsl) do |t|
|
20
|
+
t.test_files = FileList['tests/dsl_test.rb']
|
21
|
+
t.verbose = false
|
22
|
+
#t.options = "-v"
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::TestTask.new(:docs) do |t|
|
26
|
+
t.test_files = FileList['tests/docs_test.rb']
|
27
|
+
t.verbose = false
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Run RSpec with code coverage"
|
33
|
+
task :coverage do
|
34
|
+
ENV["COVERAGE"] = "true"
|
35
|
+
Rake::Task["test:all"].execute
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Reinstall gem"
|
39
|
+
task :reinstall do
|
40
|
+
puts %x{rm -rf sinatra-rabbit-*.gem}
|
41
|
+
puts %x{gem uninstall sinatra-rabbit --all -I -x}
|
42
|
+
puts %x{gem build sinatra-rabbit.gemspec}
|
43
|
+
puts %x{gem install sinatra-rabbit-*.gem --local}
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
%html
|
2
|
+
%head
|
3
|
+
%body
|
4
|
+
%h1=@collection.name.gsub('Sinatra::Rabbit::', '')
|
5
|
+
%blockquote=@collection.description
|
6
|
+
.url=@collection.full_path
|
7
|
+
.features
|
8
|
+
- @collection.features.each do |f|
|
9
|
+
%p.feature=f.name
|
10
|
+
%table.operations
|
11
|
+
%thead
|
12
|
+
%tr
|
13
|
+
%th Name
|
14
|
+
%th Type
|
15
|
+
%th Valid values
|
16
|
+
%th Description
|
17
|
+
%tbody
|
18
|
+
- @collection.operations.each do |o|
|
19
|
+
%tr
|
20
|
+
%td.name
|
21
|
+
%a{:href => o.docs_url }
|
22
|
+
%td.method=o.http_method.to_s.upcase
|
23
|
+
%td.url
|
24
|
+
%a{:href=>o.full_path}=o.full_path
|
@@ -0,0 +1,26 @@
|
|
1
|
+
%html
|
2
|
+
%head
|
3
|
+
%body
|
4
|
+
%h1=@operation.name.gsub('Sinatra::Rabbit::', '')
|
5
|
+
%blockquote=@operation.description
|
6
|
+
.url=@operation.full_path
|
7
|
+
- if @operation.required_capability
|
8
|
+
%p{:class => :requires}=@operation.required_capability
|
9
|
+
- @operation.features.each do |f|
|
10
|
+
.feature
|
11
|
+
.name=f.name
|
12
|
+
%blockquote=f.description
|
13
|
+
%table.parameters
|
14
|
+
%thead
|
15
|
+
%tr
|
16
|
+
%th Name
|
17
|
+
%th Type
|
18
|
+
%th Valid values
|
19
|
+
%th Description
|
20
|
+
%tbody
|
21
|
+
- (@operation.params + @operation.features_params).each do |p|
|
22
|
+
%tr
|
23
|
+
%td{ :class => p.required? && 'required'}=p.name
|
24
|
+
%td=p.klass
|
25
|
+
%td=p.values
|
26
|
+
%td=p.description
|
data/lib/sinatra/rabbit/base.rb
CHANGED
@@ -14,14 +14,6 @@
|
|
14
14
|
# License for the specific language governing permissions and limitations
|
15
15
|
# under the License.
|
16
16
|
|
17
|
-
unless Kernel.respond_to?(:require_relative)
|
18
|
-
module Kernel
|
19
|
-
def require_relative(path)
|
20
|
-
require File.join(File.dirname(caller[0]), path.to_str)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
17
|
require_relative './dsl'
|
26
18
|
require_relative './param'
|
27
19
|
require_relative './base_collection'
|
@@ -98,7 +90,10 @@ module Sinatra
|
|
98
90
|
#
|
99
91
|
def self.included(base)
|
100
92
|
configuration[:root_path] = '/'
|
101
|
-
base.register(DSL)
|
93
|
+
base.register(DSL) if base.respond_to? :register
|
94
|
+
base.get '/docs' do
|
95
|
+
haml File.read(File.join(File.dirname(__FILE__), '..', 'docs', 'api.haml'))
|
96
|
+
end
|
102
97
|
end
|
103
98
|
|
104
99
|
class Collection < BaseCollection
|
@@ -107,31 +102,57 @@ module Sinatra
|
|
107
102
|
|
108
103
|
def self.generate(name, parent_collection=nil, &block)
|
109
104
|
@collection_name = name.to_sym
|
110
|
-
@collections ||= []
|
111
105
|
@parent_collection = parent_collection
|
112
106
|
class_eval(&block)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end unless Rabbit.disabled? :options_routes
|
107
|
+
generate_head_route unless Rabbit.disabled? :head_routes
|
108
|
+
generate_option_route unless Rabbit.disabled? :options_routes
|
109
|
+
generate_docs_route unless Rabbit.disabled? :docs
|
117
110
|
self
|
118
111
|
end
|
119
112
|
|
113
|
+
def self.generate_option_route
|
114
|
+
base_class.options '/docs' + full_path do
|
115
|
+
header 'Allow' => operations.map { |o| o.operation_name }.join(',')
|
116
|
+
status 200
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.generate_head_route
|
121
|
+
base_class.head full_path do
|
122
|
+
status 200
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.docs_url
|
127
|
+
(root_path || '/') + 'docs/' + route_for(path)
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.generate_docs_route
|
131
|
+
collection = self
|
132
|
+
base_class.get docs_url do
|
133
|
+
@collection = collection
|
134
|
+
haml File.read(File.join(File.dirname(__FILE__), '..', 'docs', 'collection.haml'))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Define new collection using the name
|
139
|
+
#
|
140
|
+
# opts[:with_id] Define :id used if the collection is a subcollection
|
141
|
+
#
|
120
142
|
def self.collection(name, opts={}, &block)
|
121
|
-
|
122
|
-
|
143
|
+
return collections.find { |c| c.collection_name == name } if not block_given?
|
144
|
+
new_collection = BaseCollection.collection_class(name, self) do |c|
|
145
|
+
c.base_class = self.base_class
|
146
|
+
c.with_id!(opts.delete(:with_id)) if opts.has_key?(:with_id)
|
147
|
+
c.no_member! if opts.has_key?(:no_member)
|
148
|
+
c.generate(name, self, &block)
|
123
149
|
end
|
124
|
-
|
125
|
-
current_collection.set_base_class(self.base_class)
|
126
|
-
current_collection.with_id!(opts.delete(:with_id)) if opts.has_key? :with_id
|
127
|
-
current_collection.no_member! if opts.has_key? :no_member
|
128
|
-
current_collection.generate(name, self, &block)
|
129
|
-
@collections << current_collection
|
150
|
+
collections << new_collection
|
130
151
|
end
|
131
152
|
|
132
153
|
def self.parent_routes
|
133
154
|
return '' if @parent_collection.nil?
|
134
|
-
route = [
|
155
|
+
route = [ @parent_collection.collection_name.to_s ]
|
135
156
|
current_parent = @parent_collection
|
136
157
|
while current_parent = current_parent.parent_collection
|
137
158
|
route << current_parent.collection_name
|
@@ -139,7 +160,7 @@ module Sinatra
|
|
139
160
|
route.reverse.join('/')+'/'
|
140
161
|
end
|
141
162
|
|
142
|
-
def self.
|
163
|
+
def self.base_class=(klass)
|
143
164
|
@klass = klass
|
144
165
|
end
|
145
166
|
|
@@ -169,26 +190,37 @@ module Sinatra
|
|
169
190
|
parent_routes + with_id_param + ((@no_member) ? '' : collection_name.to_s)
|
170
191
|
end
|
171
192
|
|
172
|
-
def self.base_class
|
173
|
-
|
193
|
+
def self.base_class
|
194
|
+
@klass
|
195
|
+
end
|
196
|
+
|
174
197
|
def self.collection_name; @collection_name; end
|
175
198
|
def self.parent_collection; @parent_collection; end
|
176
|
-
|
199
|
+
|
200
|
+
def self.subcollection?
|
201
|
+
!parent_collection.nil?
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.collections
|
205
|
+
@collections ||= []
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.full_path
|
209
|
+
(root_path || '') + route_for(path)
|
210
|
+
end
|
177
211
|
|
178
212
|
def self.description(text=nil)
|
179
213
|
return @description if text.nil?
|
180
214
|
@description = text
|
181
215
|
end
|
182
216
|
|
183
|
-
def self.
|
184
|
-
|
217
|
+
def self.[](obj_id)
|
218
|
+
collections.find { |c| c.collection_name == obj_id } || operation(obj_id)
|
185
219
|
end
|
186
220
|
|
187
|
-
|
188
221
|
def self.operation(operation_name, opts={}, &block)
|
189
|
-
@operations ||= []
|
190
222
|
# Return operation when no block is given
|
191
|
-
return
|
223
|
+
return operations.find { |o| o.operation_name == operation_name } unless block_given?
|
192
224
|
|
193
225
|
# Check if current operation is not already registred
|
194
226
|
if operation_registred?(operation_name)
|
@@ -196,13 +228,8 @@ module Sinatra
|
|
196
228
|
end
|
197
229
|
|
198
230
|
# Create operation class
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
# Generate HEAD routes
|
203
|
-
unless Rabbit.disabled? :head_routes
|
204
|
-
send(:head, root_path + route_for(path, operation_name, {})) { status 200 }
|
205
|
-
end
|
231
|
+
new_operation = operation_class(self, operation_name).generate(self, operation_name, opts, &block)
|
232
|
+
operations << new_operation
|
206
233
|
|
207
234
|
# Add route conditions if defined
|
208
235
|
if opts.has_key? :if
|
@@ -215,24 +242,21 @@ module Sinatra
|
|
215
242
|
end
|
216
243
|
|
217
244
|
# Make the full_path method on operation return currect operation path
|
218
|
-
|
245
|
+
new_operation.route = root_path + route_for(path, operation_name, :id_name => @with_id || ':id')
|
219
246
|
|
220
247
|
# Change the HTTP method to POST automatically for 'action' operations
|
221
|
-
if opts[:http_method]
|
222
|
-
operation.http_method(opts.delete(:http_method))
|
223
|
-
end
|
248
|
+
new_operation.http_method = opts.delete(:http_method) if opts[:http_method]
|
224
249
|
|
225
|
-
#
|
250
|
+
# Remove :with_capability from Sinatra route options
|
226
251
|
route_options = opts.clone
|
227
252
|
route_options.delete :with_capability
|
228
|
-
base_class.send(operation.http_method || http_method_for(operation_name), operation.full_path, route_options, &operation.control)
|
229
253
|
|
230
|
-
#
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
254
|
+
# Define Sinatra route and generate OPTIONS route if enabled
|
255
|
+
base_class.send(new_operation.http_method || http_method_for(operation_name), new_operation.full_path, route_options, &new_operation.control)
|
256
|
+
|
257
|
+
new_operation.generate_option_route(root_path + route_for(path, operation_name, :no_id_and_member)) unless Rabbit.disabled?(:options_routes)
|
258
|
+
new_operation.generate_head_route(root_path + route_for(path, operation_name, :member)) unless Rabbit.disabled?(:head_routes)
|
259
|
+
new_operation.generate_docs_route(new_operation.docs_url) unless Rabbit.disabled?(:doc_routes)
|
236
260
|
self
|
237
261
|
end
|
238
262
|
|
@@ -241,23 +265,71 @@ module Sinatra
|
|
241
265
|
operation(action_name, opts, &block)
|
242
266
|
end
|
243
267
|
|
244
|
-
def self.operations
|
268
|
+
def self.operations
|
269
|
+
@operations ||= []
|
270
|
+
end
|
271
|
+
|
272
|
+
def self.features_for(operation_name)
|
273
|
+
features.select { |f| f.operations.map { |o| o.name}.include?(operation_name) }
|
274
|
+
end
|
245
275
|
|
246
|
-
class Operation
|
276
|
+
class Operation < BaseCollection
|
247
277
|
|
248
|
-
def self.
|
278
|
+
def self.docs_url
|
279
|
+
@collection.root_path + ['docs', @collection.collection_name, operation_name].join('/')
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.route=(path)
|
249
283
|
@operation_path = path
|
250
284
|
end
|
251
285
|
|
252
|
-
def self.http_method
|
253
|
-
@method ||=
|
286
|
+
def self.http_method
|
287
|
+
@method ||= BaseCollection.http_method_for(@name)
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.http_method=(method)
|
291
|
+
@method = method
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.generate_head_route(path)
|
295
|
+
@collection.base_class.head path do
|
296
|
+
status 200
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.generate_docs_route(path)
|
301
|
+
operation = self
|
302
|
+
@collection.base_class.get path do
|
303
|
+
@operation = operation
|
304
|
+
haml File.read(File.join(File.dirname(__FILE__), '..', 'docs', 'operation.haml'))
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.generate_option_route(path)
|
309
|
+
operation_params = params.map { |p| p.to_s }.join(',')
|
310
|
+
@collection.base_class.options path do
|
311
|
+
headers 'Allow' => operation_params
|
312
|
+
status 200
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.features
|
317
|
+
@collection.features_for(operation_name)
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.features_params
|
321
|
+
features.map { |f| f.operations.map { |o| o.params_array } }.flatten
|
254
322
|
end
|
255
323
|
|
256
324
|
def self.generate(collection, name, opts={}, &block)
|
257
325
|
@name, @params, @collection = name, [], collection
|
258
326
|
@options = opts
|
259
|
-
|
260
|
-
@
|
327
|
+
|
328
|
+
if @options.has_key?(:http_method)
|
329
|
+
@method = @options.delete(:http_method)
|
330
|
+
end
|
331
|
+
|
332
|
+
features.each do |feature|
|
261
333
|
if Sinatra::Rabbit.configuration[:check_features]
|
262
334
|
next unless Sinatra::Rabbit.configuration[:check_features].call(collection.collection_name, feature.name)
|
263
335
|
end
|
@@ -265,6 +337,7 @@ module Sinatra
|
|
265
337
|
instance_eval(&o.params)
|
266
338
|
end
|
267
339
|
end
|
340
|
+
|
268
341
|
if Sinatra::Rabbit::STANDARD_OPERATIONS.has_key? name
|
269
342
|
required_params = Sinatra::Rabbit::STANDARD_OPERATIONS[name][:required_params]
|
270
343
|
required_params.each do |p|
|
@@ -279,24 +352,27 @@ module Sinatra
|
|
279
352
|
def self.full_path; @operation_path; end
|
280
353
|
def self.operation_name; @name; end
|
281
354
|
|
355
|
+
def self.required_capability
|
356
|
+
@options[:with_capability]
|
357
|
+
end
|
358
|
+
|
282
359
|
def self.has_capability?
|
283
360
|
@capability ||= Sinatra::Rabbit.configuration[:check_capability]
|
284
|
-
if @capability and
|
285
|
-
@capability.call(
|
361
|
+
if @capability and required_capability
|
362
|
+
@capability.call(required_capability)
|
286
363
|
else
|
287
364
|
true
|
288
365
|
end
|
289
366
|
end
|
290
367
|
|
291
368
|
def self.description(text=nil)
|
292
|
-
|
293
|
-
@description = text
|
369
|
+
@description ||= text
|
294
370
|
end
|
295
371
|
|
296
372
|
def self.control(&block)
|
297
373
|
params_def = @params
|
298
374
|
if not has_capability?
|
299
|
-
@control = Proc.new { [412, {}, "The required capability to execute this operation is missing"] }
|
375
|
+
@control = Proc.new { [412, { 'Expect' => @options[:with_capability]}, "The required capability to execute this operation is missing"] }
|
300
376
|
else
|
301
377
|
@control ||= Proc.new do
|
302
378
|
begin
|
@@ -324,7 +400,7 @@ module Sinatra
|
|
324
400
|
private
|
325
401
|
|
326
402
|
def self.operation_registred?(name)
|
327
|
-
|
403
|
+
operations.any? { |o| o.name == name }
|
328
404
|
end
|
329
405
|
|
330
406
|
# Create an unique class name for all operations within Collection class
|
@@ -22,19 +22,21 @@ module Sinatra
|
|
22
22
|
enable :method_overide
|
23
23
|
|
24
24
|
def self.route_for(collection, operation_name=nil, member=:member)
|
25
|
-
|
25
|
+
unless operation_name.nil?
|
26
26
|
o = Sinatra::Rabbit::STANDARD_OPERATIONS[operation_name]
|
27
27
|
if o
|
28
|
+
o = o.clone
|
28
29
|
o[:member] = false if member == :no_member
|
29
30
|
o[:collection] = true if member == :no_id
|
30
|
-
if member == :no_id_and_member
|
31
|
+
if member == :no_id_and_member or member == :docs
|
31
32
|
o[:collection] = true
|
32
33
|
o[:member] = true
|
33
34
|
end
|
34
35
|
end
|
35
36
|
operation_path = (o && o[:member]) ? operation_name.to_s : nil
|
36
37
|
operation_path = operation_name.to_s unless o
|
37
|
-
id_param = (o && o[:collection]) ? nil : (member.kind_of?(Hash) ? member[:id_name] :
|
38
|
+
id_param = (o && o[:collection]) ? nil : (member.kind_of?(Hash) ? member[:id_name] : ":id")
|
39
|
+
id_param.gsub!(':', '') if id_param and member == :docs
|
38
40
|
[route_for(collection), id_param, operation_path].compact.join('/')
|
39
41
|
else
|
40
42
|
collection.to_s
|
@@ -49,10 +51,11 @@ module Sinatra
|
|
49
51
|
def self.collection_class(name, parent_class=nil)
|
50
52
|
klass = parent_class || Sinatra::Rabbit
|
51
53
|
begin
|
52
|
-
klass.const_get(name.to_s.camelize + 'Collection')
|
54
|
+
yield k = klass.const_get(name.to_s.camelize + 'Collection')
|
53
55
|
rescue NameError
|
54
|
-
klass.const_set(name.to_s.camelize + 'Collection', Class.new(Collection))
|
56
|
+
yield k = klass.const_set(name.to_s.camelize + 'Collection', Class.new(Collection))
|
55
57
|
end
|
58
|
+
return k
|
56
59
|
end
|
57
60
|
|
58
61
|
def self.root_path
|
data/lib/sinatra/rabbit/dsl.rb
CHANGED
@@ -30,20 +30,20 @@ module Sinatra
|
|
30
30
|
# end
|
31
31
|
# end
|
32
32
|
def collection(name, &block)
|
33
|
-
return
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
return self[name] unless block_given?
|
34
|
+
current_collection = BaseCollection.collection_class(name) do |c|
|
35
|
+
c.base_class = self
|
36
|
+
c.generate(name, &block)
|
37
|
+
end
|
38
|
+
collections << current_collection
|
39
|
+
DSL << current_collection
|
40
40
|
use current_collection
|
41
41
|
end
|
42
42
|
|
43
43
|
# Return all defined collections
|
44
44
|
#
|
45
45
|
def collections
|
46
|
-
@collections
|
46
|
+
@collections ||= []
|
47
47
|
end
|
48
48
|
|
49
49
|
def self.register_collection(c, &block)
|
@@ -51,6 +51,14 @@ module Sinatra
|
|
51
51
|
@collections << c
|
52
52
|
end
|
53
53
|
|
54
|
+
def self.<<(c)
|
55
|
+
register_collection(c)
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](collection)
|
59
|
+
collections.find { |c| c.collection_name == collection }
|
60
|
+
end
|
61
|
+
|
54
62
|
end
|
55
63
|
end
|
56
64
|
end
|
data/lib/sinatra/rabbit.rb
CHANGED
@@ -22,11 +22,18 @@ unless "".respond_to? :camelize
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
unless Kernel.respond_to?(:require_relative)
|
26
|
+
module Kernel
|
27
|
+
def require_relative(path)
|
28
|
+
require File.join(File.dirname(caller[0]), path.to_str)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
25
33
|
class << Sinatra::Base
|
26
34
|
def options(path, opts={}, &bk)
|
27
35
|
route 'OPTIONS', path, opts, &bk
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
31
|
-
|
32
|
-
require 'rabbit/base'
|
39
|
+
require_relative './rabbit/base'
|
data/sinatra-rabbit.gemspec
CHANGED
@@ -29,12 +29,15 @@ Gem::Specification.new do |s|
|
|
29
29
|
a simple REST API using easy to undestand DSL.
|
30
30
|
EOF
|
31
31
|
|
32
|
-
s.version = '1.0.
|
32
|
+
s.version = '1.0.6'
|
33
33
|
s.date = Time.now
|
34
34
|
s.summary = %q{Sinatra REST API DSL}
|
35
35
|
s.files = FileList[
|
36
36
|
'lib/sinatra/*.rb',
|
37
37
|
'lib/sinatra/rabbit/*.rb',
|
38
|
+
'lib/sinatra/docs/*.haml',
|
39
|
+
'tests/*.rb',
|
40
|
+
'Rakefile',
|
38
41
|
'LICENSE',
|
39
42
|
'README.md',
|
40
43
|
'sinatra-rabbit.gemspec',
|