rest-sinatra 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +10 -0
- data/README.md +23 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/rest-sinatra.rb +287 -0
- data/rest-sinatra.gemspec +63 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/unit/helpers/comments.rb +18 -0
- data/spec/unit/helpers/posts.rb +22 -0
- data/spec/unit/helpers/sinatra_stubs.rb +39 -0
- data/spec/unit/helpers/sources.rb +12 -0
- data/spec/unit/posts_and_comments_spec.rb +148 -0
- data/spec/unit/sources_spec.rb +94 -0
- metadata +85 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Copyright (c) 2009, Sunlight Foundation
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
8
|
+
* Neither the name of Sunlight Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
9
|
+
|
10
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
## About
|
2
|
+
|
3
|
+
With rest-sinatra, success is all but guaranteed in writing RESTful Web Services. (Provided that you are using a [Sinatra](http://sinatrarb.com) + [MongoMapper](http://github.com/jnunemaker/mongomapper) stack.)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
It might not be a bad idea to make sure you are running the latest RubyGems:
|
8
|
+
|
9
|
+
sudo gem update --system
|
10
|
+
|
11
|
+
I recommend a user-level install (no sudo needed):
|
12
|
+
|
13
|
+
gem install djsun-rest-sinatra
|
14
|
+
|
15
|
+
Note: in general, beware of `sudo gem install <project_name>` -- it gives elevated privileges. Do you trust `<project name>`? Better to be safe and use a local install to `~/.gem`.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
For a basic example of what this looks like when integrated into a real-world Sinatra app, see [sources.rb](http://github.com/sunlightlabs/datacatalog-api/blob/master/controllers/sources.rb). For an example of nested resources, see [users.rb](http://github.com/sunlightlabs/datacatalog-api/blob/master/controllers/users.rb) and [users_keys.rb](http://github.com/sunlightlabs/datacatalog-api/blob/master/controllers/users_keys.rb).
|
20
|
+
|
21
|
+
## History
|
22
|
+
|
23
|
+
This code was extracted from the [National Data Catalog](http://groups.google.com/group/datacatalog), a project of the [Sunlight Labs](http://sunlightlabs.com).
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rest-sinatra"
|
8
|
+
gem.summary = %Q{Easily write RESTful actions with Sinatra and MongoMapper}
|
9
|
+
gem.description = %Q{Provides a DSL for making RESTful Sinatra actions with MongoMapper models.}
|
10
|
+
gem.email = "djames@sunlightfoundation.com"
|
11
|
+
gem.homepage = "http://github.com/djsun/rest-sinatra"
|
12
|
+
gem.authors = ["David James"]
|
13
|
+
gem.add_development_dependency "rspec"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
task :spec => :check_dependencies
|
33
|
+
|
34
|
+
task :default => :spec
|
35
|
+
|
36
|
+
require 'rake/rdoctask'
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
if File.exist?('VERSION')
|
39
|
+
version = File.read('VERSION')
|
40
|
+
else
|
41
|
+
version = ""
|
42
|
+
end
|
43
|
+
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = "rest-sinatra #{version}"
|
46
|
+
rdoc.rdoc_files.include('README*')
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.1
|
data/lib/rest-sinatra.rb
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
module RestSinatra
|
2
|
+
|
3
|
+
def self.included(includer)
|
4
|
+
includer.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def resource(name, &block)
|
12
|
+
_resource(name, :regular, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def nestable_resource(name, &block)
|
16
|
+
_resource(name, :nestable, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def _resource(name, resource_type, &block)
|
22
|
+
config = evaluate_block(name, resource_type, &block)
|
23
|
+
validate(config)
|
24
|
+
build_resource(config)
|
25
|
+
config
|
26
|
+
end
|
27
|
+
|
28
|
+
def evaluate_block(name, resource_type, &block)
|
29
|
+
scope = Object.new
|
30
|
+
scope.extend(ResourceMethods)
|
31
|
+
scope.instance_eval do
|
32
|
+
@c = {
|
33
|
+
:name => name,
|
34
|
+
:resource_type => resource_type,
|
35
|
+
:model => nil,
|
36
|
+
:read_only => [],
|
37
|
+
:permission => nil,
|
38
|
+
:callbacks => {},
|
39
|
+
:nested_resources => []
|
40
|
+
}
|
41
|
+
end
|
42
|
+
scope.instance_eval(&block)
|
43
|
+
scope.instance_variable_get("@c")
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate(c)
|
47
|
+
raise "name required" unless c[:name]
|
48
|
+
raise "model required" unless c[:model]
|
49
|
+
c[:nested_resources].each do |resource|
|
50
|
+
unless resource[:association]
|
51
|
+
raise "association required for #{resource[:class]}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_resource(config)
|
57
|
+
case config[:resource_type]
|
58
|
+
when :regular
|
59
|
+
build_parent_resource(config)
|
60
|
+
build_nested_resources(config)
|
61
|
+
when :nestable
|
62
|
+
save_nestable_config(config)
|
63
|
+
else
|
64
|
+
raise "Unexpected resource_type"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_parent_resource(config)
|
69
|
+
callbacks = config[:callbacks]
|
70
|
+
model = config[:model]
|
71
|
+
name = config[:name]
|
72
|
+
read_only = config[:read_only]
|
73
|
+
|
74
|
+
get '/?' do
|
75
|
+
require_at_least(:basic)
|
76
|
+
validate_before_find_all(params, model)
|
77
|
+
@documents = find_with_filters(params, model)
|
78
|
+
@documents.to_json
|
79
|
+
end
|
80
|
+
|
81
|
+
get '/:id/?' do |id|
|
82
|
+
require_at_least(:basic)
|
83
|
+
id = params.delete("id")
|
84
|
+
validate_before_find_one(params, model)
|
85
|
+
@document = find_document!(model, id)
|
86
|
+
@document.to_json
|
87
|
+
end
|
88
|
+
|
89
|
+
post '/?' do
|
90
|
+
require_at_least(:curator)
|
91
|
+
validate_before_create(params, model, read_only)
|
92
|
+
callback(callbacks[:before_save])
|
93
|
+
callback(callbacks[:before_create])
|
94
|
+
@document = model.new(params)
|
95
|
+
unless @document.valid?
|
96
|
+
error 400, { "errors" => @document.errors.errors }.to_json
|
97
|
+
end
|
98
|
+
@document.save
|
99
|
+
callback(callbacks[:after_create])
|
100
|
+
callback(callbacks[:after_save])
|
101
|
+
response.status = 201
|
102
|
+
response.headers['Location'] = full_uri "/#{name}/#{@document.id}"
|
103
|
+
@document.to_json
|
104
|
+
end
|
105
|
+
|
106
|
+
put '/:id/?' do
|
107
|
+
require_at_least(:curator)
|
108
|
+
id = params.delete("id")
|
109
|
+
@document = find_document!(model, id)
|
110
|
+
validate_before_update(params, model, read_only)
|
111
|
+
callback(callbacks[:before_save])
|
112
|
+
callback(callbacks[:before_update])
|
113
|
+
@document = model.update(id, params)
|
114
|
+
unless @document.valid?
|
115
|
+
error 400, { "errors" => @document.errors.errors }.to_json
|
116
|
+
end
|
117
|
+
callback(callbacks[:after_update])
|
118
|
+
callback(callbacks[:after_save])
|
119
|
+
@document.to_json
|
120
|
+
end
|
121
|
+
|
122
|
+
delete '/:id/?' do
|
123
|
+
require_at_least(:curator)
|
124
|
+
id = params.delete("id")
|
125
|
+
@document = find_document!(model, id)
|
126
|
+
callback(callbacks[:before_destroy])
|
127
|
+
@document.destroy
|
128
|
+
callback(callbacks[:after_destroy])
|
129
|
+
{ "id" => id }.to_json
|
130
|
+
end
|
131
|
+
|
132
|
+
helpers do
|
133
|
+
def find_document!(model, id)
|
134
|
+
document = model.find_by_id(id)
|
135
|
+
error 404, [].to_json unless document
|
136
|
+
document
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_nested_resources(parent_config)
|
143
|
+
parent_config[:nested_resources].each do |resource|
|
144
|
+
nested_res_class = resource[:class]
|
145
|
+
assoc = resource[:association]
|
146
|
+
nested_config = restore_nestable_config(nested_res_class)
|
147
|
+
build_nested_resource(nested_res_class, assoc, parent_config, nested_config)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# klass : nested resource class
|
152
|
+
# association : a method on the parent model that will return child models
|
153
|
+
def build_nested_resource(klass, association, parent_config, child_config)
|
154
|
+
callbacks = child_config[:callbacks]
|
155
|
+
child_model = child_config[:model]
|
156
|
+
child_name = child_config[:name]
|
157
|
+
permission = child_config[:permission]
|
158
|
+
read_only = child_config[:read_only]
|
159
|
+
|
160
|
+
parent_model = parent_config[:model]
|
161
|
+
parent_name = parent_config[:name]
|
162
|
+
|
163
|
+
get "/:parent_id/#{child_name}/?" do
|
164
|
+
parent_id = params.delete("parent_id")
|
165
|
+
permission_check(:basic, permission, parent_id)
|
166
|
+
@parent_document = find_parent!(parent_model, parent_id)
|
167
|
+
all_child_documents = @parent_document.send(association)
|
168
|
+
validate_before_find_all(params, child_model) # ?
|
169
|
+
@child_documents = nested_find_with_filters(all_child_documents, params, parent_model)
|
170
|
+
@child_documents.to_json
|
171
|
+
end
|
172
|
+
|
173
|
+
get "/:parent_id/#{child_name}/:child_id/?" do
|
174
|
+
parent_id = params.delete("parent_id")
|
175
|
+
permission_check(:basic, permission, parent_id)
|
176
|
+
child_id = params.delete("child_id")
|
177
|
+
validate_before_find_one(params, child_model) # ?
|
178
|
+
@parent_document, @child_document = find_documents!(parent_model, parent_id, association, child_id)
|
179
|
+
@child_document.to_json
|
180
|
+
end
|
181
|
+
|
182
|
+
post "/:parent_id/#{child_name}/?" do
|
183
|
+
parent_id = params.delete("parent_id")
|
184
|
+
permission_check(:curator, permission, parent_id)
|
185
|
+
@parent_document = find_parent!(parent_model, parent_id)
|
186
|
+
validate_before_create(params, child_model, read_only)
|
187
|
+
callback(callbacks[:before_save])
|
188
|
+
callback(callbacks[:before_create])
|
189
|
+
@child_document = child_model.new(params)
|
190
|
+
@parent_document.send(association) << @child_document
|
191
|
+
error 500, [].to_json unless @parent_document.save
|
192
|
+
callback(callbacks[:after_create])
|
193
|
+
callback(callbacks[:after_save])
|
194
|
+
response.status = 201
|
195
|
+
response.headers['Location'] = full_uri(
|
196
|
+
"/#{parent_name}/#{parent_id}/#{child_name}/#{@child_document.id}"
|
197
|
+
)
|
198
|
+
@child_document.to_json
|
199
|
+
end
|
200
|
+
|
201
|
+
put "/:parent_id/#{child_name}/:child_id/?" do
|
202
|
+
parent_id = params.delete("parent_id")
|
203
|
+
permission_check(:curator, permission, parent_id)
|
204
|
+
child_id = params.delete("child_id")
|
205
|
+
@parent_document, @child_document = find_documents!(parent_model, parent_id, association, child_id)
|
206
|
+
validate_before_update(params, child_model, read_only)
|
207
|
+
callback(callbacks[:before_save])
|
208
|
+
callback(callbacks[:before_update])
|
209
|
+
@child_document.attributes = params
|
210
|
+
child_index = @parent_document.send(association).index(@child_document)
|
211
|
+
@parent_document.send(association)[child_index] = @child_document
|
212
|
+
error 500, [].to_json unless @parent_document.save
|
213
|
+
callback(callbacks[:after_update])
|
214
|
+
callback(callbacks[:after_save])
|
215
|
+
@child_document.to_json
|
216
|
+
end
|
217
|
+
|
218
|
+
delete "/:parent_id/#{child_name}/:child_id/?" do
|
219
|
+
parent_id = params.delete("parent_id")
|
220
|
+
permission_check(:curator, permission, parent_id)
|
221
|
+
child_id = params.delete("child_id")
|
222
|
+
@parent_document, @child_document = find_documents!(parent_model, parent_id, association, child_id)
|
223
|
+
callback(callbacks[:before_destroy])
|
224
|
+
@parent_document.send(association).delete(@child_document)
|
225
|
+
callback(callbacks[:after_destroy])
|
226
|
+
error 500, [].to_json unless @parent_document.save
|
227
|
+
{ "id" => child_id }.to_json
|
228
|
+
end
|
229
|
+
|
230
|
+
helpers do
|
231
|
+
def find_parent!(parent_model, parent_id)
|
232
|
+
parent_document = parent_model.find_by_id(parent_id)
|
233
|
+
error 404, [].to_json unless parent_document
|
234
|
+
parent_document
|
235
|
+
end
|
236
|
+
|
237
|
+
def find_child!(parent_document, association, child_id)
|
238
|
+
child_document = parent_document.send(association).find { |x| x.id == child_id }
|
239
|
+
error 404, [].to_json unless child_document
|
240
|
+
child_document
|
241
|
+
end
|
242
|
+
|
243
|
+
def find_documents!(parent_model, parent_id, association, child_id)
|
244
|
+
parent_document = find_parent!(parent_model, parent_id)
|
245
|
+
child_document = find_child!(parent_document, association, child_id)
|
246
|
+
[parent_document, child_document]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def save_nestable_config(config)
|
252
|
+
@nestable_resource_config = config
|
253
|
+
end
|
254
|
+
|
255
|
+
def restore_nestable_config(klass)
|
256
|
+
klass.instance_variable_get("@nestable_resource_config")
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
module ResourceMethods
|
262
|
+
|
263
|
+
def model(model)
|
264
|
+
raise "model already declared" if @c[:model]
|
265
|
+
@c[:model] = model
|
266
|
+
end
|
267
|
+
|
268
|
+
def read_only(attribute)
|
269
|
+
@c[:read_only] << attribute
|
270
|
+
end
|
271
|
+
|
272
|
+
def permission(level)
|
273
|
+
raise "permission already declared" if @c[:permission]
|
274
|
+
@c[:permission] = level
|
275
|
+
end
|
276
|
+
|
277
|
+
def callback(name, &block)
|
278
|
+
@c[:callbacks][name] = block
|
279
|
+
end
|
280
|
+
|
281
|
+
def nested_resource(klass, options)
|
282
|
+
@c[:nested_resources] << options.merge({:class => klass})
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rest-sinatra}
|
8
|
+
s.version = "0.3.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["David James"]
|
12
|
+
s.date = %q{2009-10-02}
|
13
|
+
s.description = %q{Provides a DSL for making RESTful Sinatra actions with MongoMapper models.}
|
14
|
+
s.email = %q{djames@sunlightfoundation.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.md",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/rest-sinatra.rb",
|
27
|
+
"rest-sinatra.gemspec",
|
28
|
+
"spec/spec_helper.rb",
|
29
|
+
"spec/unit/helpers/comments.rb",
|
30
|
+
"spec/unit/helpers/posts.rb",
|
31
|
+
"spec/unit/helpers/sinatra_stubs.rb",
|
32
|
+
"spec/unit/helpers/sources.rb",
|
33
|
+
"spec/unit/posts_and_comments_spec.rb",
|
34
|
+
"spec/unit/sources_spec.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/djsun/rest-sinatra}
|
37
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.3.5}
|
40
|
+
s.summary = %q{Easily write RESTful actions with Sinatra and MongoMapper}
|
41
|
+
s.test_files = [
|
42
|
+
"spec/spec_helper.rb",
|
43
|
+
"spec/unit/helpers/comments.rb",
|
44
|
+
"spec/unit/helpers/posts.rb",
|
45
|
+
"spec/unit/helpers/sinatra_stubs.rb",
|
46
|
+
"spec/unit/helpers/sources.rb",
|
47
|
+
"spec/unit/posts_and_comments_spec.rb",
|
48
|
+
"spec/unit/sources_spec.rb"
|
49
|
+
]
|
50
|
+
|
51
|
+
if s.respond_to? :specification_version then
|
52
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
53
|
+
s.specification_version = 3
|
54
|
+
|
55
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
56
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
59
|
+
end
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
62
|
+
end
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/sinatra_stubs')
|
2
|
+
|
3
|
+
class Comment ; end
|
4
|
+
|
5
|
+
class Comments
|
6
|
+
include RestSinatra
|
7
|
+
include SinatraStubs
|
8
|
+
|
9
|
+
@r = nestable_resource "comments" do
|
10
|
+
model Comment
|
11
|
+
|
12
|
+
read_only :created_at
|
13
|
+
|
14
|
+
callback :before_save do
|
15
|
+
"before saving comments"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/sinatra_stubs')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/comments')
|
3
|
+
|
4
|
+
class Post ; end
|
5
|
+
|
6
|
+
class Posts
|
7
|
+
include RestSinatra
|
8
|
+
include SinatraStubs
|
9
|
+
|
10
|
+
@r = resource "posts" do
|
11
|
+
model Post
|
12
|
+
|
13
|
+
read_only :created_at
|
14
|
+
read_only :updated_at
|
15
|
+
|
16
|
+
nested_resource Comments, :association => :comments
|
17
|
+
|
18
|
+
callback :before_save do
|
19
|
+
"before saving posts"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module SinatraStubs
|
2
|
+
|
3
|
+
def self.included(includer)
|
4
|
+
includer.class_eval do
|
5
|
+
@actions = {
|
6
|
+
:get => [],
|
7
|
+
:post => [],
|
8
|
+
:put => [],
|
9
|
+
:delete => []
|
10
|
+
}
|
11
|
+
end
|
12
|
+
includer.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
attr_reader :actions
|
17
|
+
|
18
|
+
def get(route)
|
19
|
+
@actions[:get] << route
|
20
|
+
end
|
21
|
+
|
22
|
+
def post(route)
|
23
|
+
@actions[:post] << route
|
24
|
+
end
|
25
|
+
|
26
|
+
def put(route)
|
27
|
+
@actions[:put] << route
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(route)
|
31
|
+
@actions[:delete] << route
|
32
|
+
end
|
33
|
+
|
34
|
+
def helpers()
|
35
|
+
# ...
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/helpers/posts.rb')
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/helpers/comments.rb')
|
4
|
+
|
5
|
+
describe "Posts" do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@config = Posts.instance_variable_get("@r")
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "DSL methods" do
|
12
|
+
it "resource should be 'posts'" do
|
13
|
+
@config[:name].should == "posts"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "model should be Post" do
|
17
|
+
@config[:model].should == Post
|
18
|
+
end
|
19
|
+
|
20
|
+
it "read_only should be correct" do
|
21
|
+
@config[:read_only].should == [:created_at, :updated_at]
|
22
|
+
end
|
23
|
+
|
24
|
+
it "callback :before_create should be correct" do
|
25
|
+
@config[:callbacks][:before_save].call.should == "before saving posts"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "callback :before_update should return nil" do
|
29
|
+
@config[:callbacks][:before_update].should == nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "actions" do
|
34
|
+
before do
|
35
|
+
@actions = Posts.actions
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "get" do
|
39
|
+
before do
|
40
|
+
@acts = @actions[:get]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "get /?" do
|
44
|
+
@acts.should include("/?")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "get /:id/?" do
|
48
|
+
@acts.should include("/:id/?")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "get /:parent_id/comments/?" do
|
52
|
+
@acts.should include("/:parent_id/comments/?")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "get /:parent_id/comments/:child_id/?" do
|
56
|
+
@acts.should include("/:parent_id/comments/:child_id/?")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "exactly 4 actions" do
|
60
|
+
@acts.length.should == 4
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "post" do
|
65
|
+
before do
|
66
|
+
@acts = @actions[:post]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "post /?" do
|
70
|
+
@acts.should include("/?")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "post /:parent_id/comments/?" do
|
74
|
+
@acts.should include("/:parent_id/comments/?")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "exactly 2 actions" do
|
78
|
+
@acts.length.should == 2
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "put" do
|
83
|
+
before do
|
84
|
+
@acts = @actions[:put]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "put /:id/?" do
|
88
|
+
@acts.should include("/:id/?")
|
89
|
+
end
|
90
|
+
|
91
|
+
it "put /:parent_id/comments/:child_id/?" do
|
92
|
+
@acts.should include("/:parent_id/comments/:child_id/?")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "exactly 2 actions" do
|
96
|
+
@acts.length.should == 2
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "delete" do
|
101
|
+
before do
|
102
|
+
@acts = @actions[:delete]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "delete /:id/?" do
|
106
|
+
@acts.should include("/:id/?")
|
107
|
+
end
|
108
|
+
|
109
|
+
it "delete /:parent_id/comments/:child_id/?" do
|
110
|
+
@acts.should include("/:parent_id/comments/:child_id/?")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "exactly 2 actions" do
|
114
|
+
@acts.length.should == 2
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "Comments" do
|
122
|
+
|
123
|
+
before do
|
124
|
+
@config = Comments.instance_variable_get("@r")
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "DSL methods" do
|
128
|
+
it "resource should be 'comments'" do
|
129
|
+
@config[:name].should == "comments"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "model should be Comment" do
|
133
|
+
@config[:model].should == Comment
|
134
|
+
end
|
135
|
+
|
136
|
+
it "read_only should be correct" do
|
137
|
+
@config[:read_only].should == [:created_at]
|
138
|
+
end
|
139
|
+
|
140
|
+
it "callback :before_create should be correct" do
|
141
|
+
@config[:callbacks][:before_save].call.should == "before saving comments"
|
142
|
+
end
|
143
|
+
|
144
|
+
it "callback :before_update should return nil" do
|
145
|
+
@config[:callbacks][:before_update].should == nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/helpers/sources')
|
3
|
+
|
4
|
+
describe "Sources" do
|
5
|
+
|
6
|
+
describe "DSL methods" do
|
7
|
+
before do
|
8
|
+
@config = Sources.instance_variable_get("@r")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "name should be 'sources'" do
|
12
|
+
@config[:name].should == "sources"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "model should be Source" do
|
16
|
+
@config[:model].should == Source
|
17
|
+
end
|
18
|
+
|
19
|
+
it "read_only should be empty" do
|
20
|
+
@config[:read_only].should == []
|
21
|
+
end
|
22
|
+
|
23
|
+
it "callbacks should be empty" do
|
24
|
+
@config[:callbacks].should == {}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "actions" do
|
29
|
+
before do
|
30
|
+
@actions = Sources.actions
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "get" do
|
34
|
+
before do
|
35
|
+
@acts = @actions[:get]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "get /?" do
|
39
|
+
@acts.should include("/?")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "get /:id/?" do
|
43
|
+
@acts.should include("/:id/?")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "exactly 2 actions" do
|
47
|
+
@acts.length.should == 2
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "post" do
|
52
|
+
before do
|
53
|
+
@acts = @actions[:post]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "post /?" do
|
57
|
+
@acts.should include("/?")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "exactly 1 action" do
|
61
|
+
@acts.length.should == 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "put" do
|
66
|
+
before do
|
67
|
+
@acts = @actions[:put]
|
68
|
+
end
|
69
|
+
|
70
|
+
it "put /:id/?" do
|
71
|
+
@acts.should include("/:id/?")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "exactly 1 action" do
|
75
|
+
@acts.length.should == 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "delete" do
|
80
|
+
before do
|
81
|
+
@acts = @actions[:delete]
|
82
|
+
end
|
83
|
+
|
84
|
+
it "delete /:id/?" do
|
85
|
+
@acts.should include("/:id/?")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "exactly 1 action" do
|
89
|
+
@acts.length.should == 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rest-sinatra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David James
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-02 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Provides a DSL for making RESTful Sinatra actions with MongoMapper models.
|
26
|
+
email: djames@sunlightfoundation.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- lib/rest-sinatra.rb
|
42
|
+
- rest-sinatra.gemspec
|
43
|
+
- spec/spec_helper.rb
|
44
|
+
- spec/unit/helpers/comments.rb
|
45
|
+
- spec/unit/helpers/posts.rb
|
46
|
+
- spec/unit/helpers/sinatra_stubs.rb
|
47
|
+
- spec/unit/helpers/sources.rb
|
48
|
+
- spec/unit/posts_and_comments_spec.rb
|
49
|
+
- spec/unit/sources_spec.rb
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://github.com/djsun/rest-sinatra
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options:
|
56
|
+
- --charset=UTF-8
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.5
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Easily write RESTful actions with Sinatra and MongoMapper
|
78
|
+
test_files:
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
- spec/unit/helpers/comments.rb
|
81
|
+
- spec/unit/helpers/posts.rb
|
82
|
+
- spec/unit/helpers/sinatra_stubs.rb
|
83
|
+
- spec/unit/helpers/sources.rb
|
84
|
+
- spec/unit/posts_and_comments_spec.rb
|
85
|
+
- spec/unit/sources_spec.rb
|