blindgaenger-sinatra-rest 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +84 -0
- data/Rakefile +11 -0
- data/lib/rest.rb +278 -0
- data/spec/rest_spec.rb +448 -0
- data/spec/views/people/edit.erb +4 -0
- data/spec/views/people/index.erb +10 -0
- data/spec/views/people/new.erb +4 -0
- data/spec/views/people/show.erb +4 -0
- metadata +79 -0
data/README.textile
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
h1. Sinatra-REST
|
2
|
+
|
3
|
+
Actually it's a set of templates to introduce RESTful routes in Sinatra. The
|
4
|
+
only thing for you to do is to provide the views. The routes and some
|
5
|
+
url helpers will be provided behind the scenes.
|
6
|
+
|
7
|
+
|
8
|
+
h2. Installation
|
9
|
+
|
10
|
+
Guess what!
|
11
|
+
|
12
|
+
sudo gem source --add http://gems.github.com
|
13
|
+
sudo gem install blindgaenger-sinatra-rest
|
14
|
+
|
15
|
+
|
16
|
+
h2. Usage
|
17
|
+
|
18
|
+
Of course you need to require the gem in your sinatra application:
|
19
|
+
|
20
|
+
require 'rubygems'
|
21
|
+
require 'sinatra'
|
22
|
+
require 'rest'
|
23
|
+
|
24
|
+
It's very similar to defining routes in Sinatra (@get@, @post@, ...). But this
|
25
|
+
time you don't define the routes by yourself, but use the model's name for
|
26
|
+
convention.
|
27
|
+
|
28
|
+
For example, if the model's class is called @Person@ you only need to add this
|
29
|
+
line:
|
30
|
+
|
31
|
+
rest Person
|
32
|
+
|
33
|
+
Which will add the following RESTful routes to your application. (Note the
|
34
|
+
pluralization of @Person@ to the @/people/*@ routes.)
|
35
|
+
|
36
|
+
* GET /people
|
37
|
+
* GET /people/new
|
38
|
+
* POST /people
|
39
|
+
* GET /people/:id
|
40
|
+
* GET /people/:id/edit
|
41
|
+
* PUT /people/:id
|
42
|
+
* DELETE /people/:id
|
43
|
+
|
44
|
+
But the real benefit is, that these *routes define a restful standard behaviour*
|
45
|
+
on your model, *appropriate routing and redirecting* and *named url helpers*.
|
46
|
+
|
47
|
+
For instance, you can imagine the following code to be added for the @/people@
|
48
|
+
and @/people/:id@ routes.
|
49
|
+
|
50
|
+
<pre>
|
51
|
+
<code>
|
52
|
+
# simply add this line
|
53
|
+
|
54
|
+
rest Person, :renderer => :erb
|
55
|
+
|
56
|
+
# and this is generated for you
|
57
|
+
|
58
|
+
get '/people' do
|
59
|
+
@people = Person.all
|
60
|
+
erb :"people/index", options
|
61
|
+
end
|
62
|
+
|
63
|
+
put '/people/:id' do
|
64
|
+
@person = Person.find_by_id(params[:id])
|
65
|
+
redirect url_for_people_show(@person), 'person updated'
|
66
|
+
end
|
67
|
+
|
68
|
+
# further restful routes for Person ...
|
69
|
+
</code>
|
70
|
+
</pre>
|
71
|
+
|
72
|
+
That's only half the truth! The routes are generated dynamically, so all
|
73
|
+
defaults can be overridden (the behaviour, after/before callbacks, used renderer,
|
74
|
+
which routes are added).
|
75
|
+
|
76
|
+
For more details and options, please have a look at the pages in the
|
77
|
+
"Sinatra-REST Wiki":http://github.com/blindgaenger/sinatra-rest/wikis on Github.
|
78
|
+
|
79
|
+
|
80
|
+
h2. Contact
|
81
|
+
|
82
|
+
You can contact me via mail at blindgaenger at gmail dot com, or leave me a
|
83
|
+
message on my "Github profile":http://github.com/blindgaenger.
|
84
|
+
|
data/Rakefile
ADDED
data/lib/rest.rb
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'english/inflect'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Stone
|
5
|
+
module Resource
|
6
|
+
def find_by_id(id)
|
7
|
+
get(id)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module DataMapper
|
13
|
+
module Model
|
14
|
+
def find_by_id(id)
|
15
|
+
get(id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ActiveRecord
|
21
|
+
module Base
|
22
|
+
def find_by_id(id)
|
23
|
+
first(id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module Sinatra
|
29
|
+
module REST
|
30
|
+
|
31
|
+
#
|
32
|
+
# adds restful routes and url helpers for the model
|
33
|
+
def rest(model_class, options={}, &block)
|
34
|
+
model, singular, plural = conjugate(model_class)
|
35
|
+
|
36
|
+
renderer = options.delete(:renderer)
|
37
|
+
renderer ||= :haml
|
38
|
+
|
39
|
+
# @false@ will remove the @POST@, @PUT@ and @DELETE@ routes
|
40
|
+
# by dfault this is enabled
|
41
|
+
# :editable => true
|
42
|
+
editable = options.delete(:editable)
|
43
|
+
editable = true if editable.nil?
|
44
|
+
|
45
|
+
# @true@ will add the @/models/new@ and @/models/edit@ routes
|
46
|
+
# by default this is disabled
|
47
|
+
# :inputable => false
|
48
|
+
inputable = options.delete(:inputable)
|
49
|
+
inputable = true if inputable.nil?
|
50
|
+
|
51
|
+
|
52
|
+
# add some url_for_* helpers
|
53
|
+
Sinatra::Default.class_eval <<-XXX
|
54
|
+
public
|
55
|
+
|
56
|
+
# index GET /models
|
57
|
+
def url_for_#{plural}_index
|
58
|
+
'/#{plural}'
|
59
|
+
end
|
60
|
+
|
61
|
+
# new GET /models/new
|
62
|
+
if editable && inputable
|
63
|
+
def url_for_#{plural}_new
|
64
|
+
'/#{plural}/new'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# create POST /models
|
69
|
+
if editable
|
70
|
+
def url_for_#{plural}_create
|
71
|
+
'/#{plural}'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# show GET /models/1
|
76
|
+
def url_for_#{plural}_show(model)
|
77
|
+
"/#{plural}/\#{escape_model_id(model)}"
|
78
|
+
end
|
79
|
+
|
80
|
+
# edit GET /models/1/edit
|
81
|
+
if editable && inputable
|
82
|
+
def url_for_#{plural}_edit(model)
|
83
|
+
"/#{plural}/\#{escape_model_id(model)}/edit"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# update PUT /models/1
|
88
|
+
if editable
|
89
|
+
def url_for_#{plural}_update(model)
|
90
|
+
"/#{plural}/\#{escape_model_id(model)}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# destroy DELETE /models/1
|
95
|
+
if editable
|
96
|
+
def url_for_#{plural}_destroy(model)
|
97
|
+
"/#{plural}/\#{escape_model_id(model)}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def escape_model_id(model)
|
104
|
+
if model.nil?
|
105
|
+
raise 'can not generate url for nil'
|
106
|
+
elsif model.kind_of?(String)
|
107
|
+
CGI.escape(model)
|
108
|
+
elsif model.kind_of?(Fixnum)
|
109
|
+
model
|
110
|
+
elsif model.id.kind_of? String
|
111
|
+
CGI.escape(model.id)
|
112
|
+
else
|
113
|
+
model.id
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
public
|
118
|
+
XXX
|
119
|
+
|
120
|
+
# create an own module and fill it with the template
|
121
|
+
controller_template = Module.new
|
122
|
+
controller_template.class_eval <<-XXX
|
123
|
+
def call(name)
|
124
|
+
before name
|
125
|
+
send name
|
126
|
+
after name
|
127
|
+
end
|
128
|
+
|
129
|
+
def after(name)
|
130
|
+
# should be overridden
|
131
|
+
end
|
132
|
+
|
133
|
+
def before(name)
|
134
|
+
# should be overridden
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
# index GET /models
|
139
|
+
def index
|
140
|
+
@#{plural} = #{model}.all
|
141
|
+
end
|
142
|
+
|
143
|
+
# new GET /models/new
|
144
|
+
if editable && inputable
|
145
|
+
def new
|
146
|
+
@#{singular} = #{model}.new
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# create POST /models
|
151
|
+
if editable
|
152
|
+
def create
|
153
|
+
@#{singular} = #{model}.new(params)
|
154
|
+
@#{singular}.save
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# show GET /models/1
|
159
|
+
def show
|
160
|
+
@#{singular} = #{model}.find_by_id(params[:id])
|
161
|
+
end
|
162
|
+
|
163
|
+
# edit GET /models/1/edit
|
164
|
+
if editable && inputable
|
165
|
+
def edit
|
166
|
+
@#{singular} = #{model}.find_by_id(params[:id])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# update PUT /models/1
|
171
|
+
if editable
|
172
|
+
def update
|
173
|
+
@#{singular} = #{model}.find_by_id(params[:id])
|
174
|
+
@#{singular}.update_attributes(params)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# destroy DELETE /models/1
|
179
|
+
if editable
|
180
|
+
def destroy
|
181
|
+
#{model}.delete(params[:id])
|
182
|
+
end
|
183
|
+
end
|
184
|
+
XXX
|
185
|
+
|
186
|
+
# create an own module, to override the template with custom methods
|
187
|
+
# this way, you can still use #super# in the overridden methods
|
188
|
+
if block_given?
|
189
|
+
controller_custom = Module.new &block
|
190
|
+
end
|
191
|
+
|
192
|
+
# create the restful routes
|
193
|
+
Sinatra::Application.instance_eval <<-XXX
|
194
|
+
# add the correct modules to the EventContext
|
195
|
+
# use a metaclass so it isn't included again next time
|
196
|
+
before do
|
197
|
+
if request.path_info =~ /^\\/#{plural}\\b/
|
198
|
+
metaclass = class << self; self; end
|
199
|
+
metaclass.send(:include, controller_template)
|
200
|
+
metaclass.send(:include, controller_custom) if controller_custom
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# index GET /models
|
205
|
+
get '/#{plural}' do
|
206
|
+
call :index
|
207
|
+
#{renderer.to_s} :"#{plural}/index", options
|
208
|
+
end
|
209
|
+
|
210
|
+
# new GET /models/new
|
211
|
+
if editable && inputable
|
212
|
+
get '/#{plural}/new' do
|
213
|
+
call :new
|
214
|
+
#{renderer.to_s} :"#{plural}/new", options
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# create POST /models
|
219
|
+
if editable
|
220
|
+
post '/#{plural}' do
|
221
|
+
call :create
|
222
|
+
redirect url_for_#{plural}_show(@#{singular}), '#{singular} created'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# show GET /models/1
|
227
|
+
get '/#{plural}/:id' do
|
228
|
+
call :show
|
229
|
+
if @#{singular}.nil?
|
230
|
+
throw :halt, [404, '#{singular} not found']
|
231
|
+
else
|
232
|
+
#{renderer.to_s} :"#{plural}/show", options
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# edit GET /models/1/edit
|
237
|
+
if editable && inputable
|
238
|
+
get '/#{plural}/:id/edit' do
|
239
|
+
call :edit
|
240
|
+
#{renderer.to_s} :"#{plural}/edit", options
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# update PUT /models/1
|
245
|
+
if editable
|
246
|
+
put '/#{plural}/:id' do
|
247
|
+
call :update
|
248
|
+
redirect url_for_#{plural}_show(@#{singular}), '#{singular} updated'
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# destroy DELETE /models/1
|
253
|
+
if editable
|
254
|
+
delete '/#{plural}/:id' do
|
255
|
+
call :destroy
|
256
|
+
redirect url_for_#{plural}_index, '#{singular} deleted'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
XXX
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
protected
|
264
|
+
#
|
265
|
+
# creates the necessary forms of the model name
|
266
|
+
# pretty much like ActiveSupport's inflections, but don't like to depend on
|
267
|
+
def conjugate(model_class)
|
268
|
+
model = model_class.to_s.match(/(\w+)$/)[0]
|
269
|
+
singular = model.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
270
|
+
return model, singular, singular.pluralize
|
271
|
+
end
|
272
|
+
|
273
|
+
end # REST
|
274
|
+
end # Sinatra
|
275
|
+
|
276
|
+
include Sinatra::REST
|
277
|
+
|
278
|
+
|
data/spec/rest_spec.rb
ADDED
@@ -0,0 +1,448 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'sinatra'
|
4
|
+
require 'sinatra/test/rspec'
|
5
|
+
require 'lib/rest'
|
6
|
+
require "rexml/document"
|
7
|
+
require 'ruby-debug'
|
8
|
+
|
9
|
+
#
|
10
|
+
# kind of a 'minimal model'
|
11
|
+
class Person
|
12
|
+
attr_accessor :id
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
#puts "new #{args.inspect}"
|
17
|
+
if args.size == 0
|
18
|
+
@id = nil
|
19
|
+
@name = nil
|
20
|
+
elsif args.size == 2
|
21
|
+
@id = args[0].to_i
|
22
|
+
@name = args[1]
|
23
|
+
else args.size == 1
|
24
|
+
update_attributes(args[0])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def save
|
29
|
+
#puts "save #{@id}"
|
30
|
+
@@people << self
|
31
|
+
self.id = @@people.size
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_attributes(hash)
|
35
|
+
#puts "update_attributes #{hash.inspect}"
|
36
|
+
unless hash.empty?
|
37
|
+
@id = hash['id'].to_i if hash.include?('id')
|
38
|
+
@name = hash['name'] if hash.include?('name')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.delete(id)
|
43
|
+
#puts "delete #{id}"
|
44
|
+
@@people.delete_if {|person| person.id == id.to_i}
|
45
|
+
end
|
46
|
+
|
47
|
+
@@people = nil
|
48
|
+
|
49
|
+
def self.all
|
50
|
+
#puts 'all'
|
51
|
+
return @@people
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.find_by_id(id)
|
55
|
+
#puts "find_by_id #{id}"
|
56
|
+
all.find {|f| f.id == id.to_i}
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.reset!
|
60
|
+
#puts 'reset!'
|
61
|
+
@@people = [
|
62
|
+
Person.new(1, 'one'),
|
63
|
+
Person.new(2, 'two'),
|
64
|
+
Person.new(3, 'three')
|
65
|
+
]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def doc(xml)
|
71
|
+
REXML::Document.new(xml.gsub(/>\s+</, '><').strip)
|
72
|
+
end
|
73
|
+
|
74
|
+
def response_should_be(status, body)
|
75
|
+
@response.status.should == status
|
76
|
+
doc(@response.body).to_s.should == body
|
77
|
+
end
|
78
|
+
|
79
|
+
def model_should_be(size)
|
80
|
+
Person.all.size.should == size
|
81
|
+
end
|
82
|
+
|
83
|
+
def call_order_should_be(order, &block)
|
84
|
+
Person.call_order = []
|
85
|
+
block.call
|
86
|
+
Person.call_order.map {|c| c[:method]}.should == order
|
87
|
+
end
|
88
|
+
|
89
|
+
def called_should_be(name, &block)
|
90
|
+
Person.call_order = []
|
91
|
+
block.call
|
92
|
+
called = Person.call_order.map {|c| c[:called]}
|
93
|
+
called.should_not be_nil
|
94
|
+
called.each {|c| c.should == name }
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
describe Sinatra::REST do
|
99
|
+
|
100
|
+
describe 'as inflection generator' do
|
101
|
+
it "should conjugate a simple model name" do
|
102
|
+
Sinatra::REST.conjugate(Person).should eql(%w(Person person people))
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should conjugate a String as model name" do
|
106
|
+
Sinatra::REST.conjugate('Person').should eql(%w(Person person people))
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should conjugate a model name in camel cases" do
|
110
|
+
Sinatra::REST.conjugate('SomePerson').should eql(%w(SomePerson some_person some_people))
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should conjugate a model name without module" do
|
114
|
+
Sinatra::REST.conjugate('MyModule::ModulePerson').should eql(%w(ModulePerson module_person module_people))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
describe 'as route generator' do
|
120
|
+
|
121
|
+
before(:each) do
|
122
|
+
Sinatra.application = nil
|
123
|
+
@app = Sinatra.application
|
124
|
+
|
125
|
+
response_mock = mock "Response"
|
126
|
+
response_mock.should_receive(:"body=").with(nil).and_return(nil)
|
127
|
+
@context = Sinatra::EventContext.new(nil, response_mock, nil)
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
it 'should not add editable url_for_* helpers' do
|
132
|
+
rest Person, :editable => false
|
133
|
+
|
134
|
+
methods = Sinatra::EventContext.instance_methods.grep /^url_for_people_/
|
135
|
+
methods.size.should == 2
|
136
|
+
|
137
|
+
@person = Person.new
|
138
|
+
@person.id = 99
|
139
|
+
|
140
|
+
@context.url_for_people_index.should == '/people'
|
141
|
+
@context.url_for_people_show(@person).should == '/people/99'
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should not add editable restful routes' do
|
145
|
+
@app.events.clear
|
146
|
+
rest Person, :editable => false
|
147
|
+
@app.events[:get].map {|r| r.path}.should == ["/people", "/people/:id"]
|
148
|
+
@app.events[:post].map {|r| r.path}.should == []
|
149
|
+
@app.events[:put].map {|r| r.path}.should == []
|
150
|
+
@app.events[:delete].map {|r| r.path}.should == []
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
it 'should not add inputable url_for_* helpers' do
|
155
|
+
rest Person, :inputable => false
|
156
|
+
|
157
|
+
methods = Sinatra::EventContext.instance_methods.grep /^url_for_people_/
|
158
|
+
methods.size.should == 5
|
159
|
+
|
160
|
+
@person = Person.new
|
161
|
+
@person.id = 99
|
162
|
+
|
163
|
+
@context.url_for_people_index.should == '/people'
|
164
|
+
@context.url_for_people_create.should == '/people'
|
165
|
+
@context.url_for_people_show(@person).should == '/people/99'
|
166
|
+
@context.url_for_people_update(@person).should == '/people/99'
|
167
|
+
@context.url_for_people_destroy(@person).should == '/people/99'
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should not add inputable restful routes' do
|
171
|
+
@app.events.clear
|
172
|
+
rest Person, :inputable => false
|
173
|
+
@app.events[:get].map {|r| r.path}.should == ["/people", "/people/:id"]
|
174
|
+
@app.events[:post].map {|r| r.path}.should == ["/people"]
|
175
|
+
@app.events[:put].map {|r| r.path}.should == ["/people/:id"]
|
176
|
+
@app.events[:delete].map {|r| r.path}.should == ["/people/:id"]
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
it 'should add all url_for_* helpers' do
|
181
|
+
rest Person
|
182
|
+
|
183
|
+
methods = Sinatra::EventContext.instance_methods.grep /^url_for_people_/
|
184
|
+
methods.size.should == 7
|
185
|
+
|
186
|
+
@person = Person.new
|
187
|
+
@person.id = 99
|
188
|
+
|
189
|
+
@context.url_for_people_index.should == '/people'
|
190
|
+
@context.url_for_people_new.should == '/people/new'
|
191
|
+
@context.url_for_people_create.should == '/people'
|
192
|
+
@context.url_for_people_show(@person).should == '/people/99'
|
193
|
+
@context.url_for_people_edit(@person).should == '/people/99/edit'
|
194
|
+
@context.url_for_people_update(@person).should == '/people/99'
|
195
|
+
@context.url_for_people_destroy(@person).should == '/people/99'
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should add all restful routes' do
|
199
|
+
@app.events.clear
|
200
|
+
rest Person
|
201
|
+
@app.events[:get].map {|r| r.path}.should == ["/people", "/people/new", "/people/:id", "/people/:id/edit"]
|
202
|
+
@app.events[:post].map {|r| r.path}.should == ["/people"]
|
203
|
+
@app.events[:put].map {|r| r.path}.should == ["/people/:id"]
|
204
|
+
@app.events[:delete].map {|r| r.path}.should == ["/people/:id"]
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should support models, strings and integers' do
|
208
|
+
@person = Person.new('id' => 99)
|
209
|
+
|
210
|
+
@context.url_for_people_show(@person).should == '/people/99'
|
211
|
+
@context.url_for_people_show(99).should == '/people/99'
|
212
|
+
@context.url_for_people_show('99').should == '/people/99'
|
213
|
+
lambda {@context.url_for_people_show(nil)}.should raise_error('can not generate url for nil')
|
214
|
+
|
215
|
+
@context.url_for_people_edit(@person).should == '/people/99/edit'
|
216
|
+
@context.url_for_people_edit(99).should == '/people/99/edit'
|
217
|
+
@context.url_for_people_edit('99').should == '/people/99/edit'
|
218
|
+
lambda {@context.url_for_people_edit(nil)}.should raise_error('can not generate url for nil')
|
219
|
+
|
220
|
+
@context.url_for_people_update(@person).should == '/people/99'
|
221
|
+
@context.url_for_people_update(99).should == '/people/99'
|
222
|
+
@context.url_for_people_update('99').should == '/people/99'
|
223
|
+
lambda {@context.url_for_people_update(nil)}.should raise_error('can not generate url for nil')
|
224
|
+
|
225
|
+
@context.url_for_people_destroy(@person).should == '/people/99'
|
226
|
+
@context.url_for_people_destroy(99).should == '/people/99'
|
227
|
+
@context.url_for_people_destroy('99').should == '/people/99'
|
228
|
+
lambda {@context.url_for_people_destroy(nil)}.should raise_error('can not generate url for nil')
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
describe 'as restful service' do
|
234
|
+
|
235
|
+
before(:each) do
|
236
|
+
Sinatra.application = nil
|
237
|
+
@app = Sinatra.application
|
238
|
+
@app.configure :test do
|
239
|
+
set :views, File.join(File.dirname(__FILE__), "views")
|
240
|
+
end
|
241
|
+
|
242
|
+
Person.reset!
|
243
|
+
rest Person, :renderer => 'erb'
|
244
|
+
end
|
245
|
+
|
246
|
+
describe 'each method' do
|
247
|
+
|
248
|
+
# index GET /models
|
249
|
+
it 'should list all people on index by their id' do
|
250
|
+
get '/people'
|
251
|
+
response_should_be 200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>'
|
252
|
+
end
|
253
|
+
|
254
|
+
# new GET /models/new
|
255
|
+
it 'should prepare an empty item on new' do
|
256
|
+
get '/people/new'
|
257
|
+
response_should_be 200, '<person><id/><name/></person>'
|
258
|
+
end
|
259
|
+
|
260
|
+
# create POST /models
|
261
|
+
it 'should create an item on post' do
|
262
|
+
post '/people', :name => 'new resource'
|
263
|
+
response_should_be 302, 'person created'
|
264
|
+
end
|
265
|
+
|
266
|
+
# show GET /models/1
|
267
|
+
it 'should show an item on get' do
|
268
|
+
get '/people/1'
|
269
|
+
response_should_be 200, '<person><id>1</id><name>one</name></person>'
|
270
|
+
end
|
271
|
+
|
272
|
+
# edit GET /models/1/edit
|
273
|
+
it 'should get the item for editing' do
|
274
|
+
get '/people/1/edit'
|
275
|
+
response_should_be 200, '<person><id>1</id><name>one</name></person>'
|
276
|
+
end
|
277
|
+
|
278
|
+
# update PUT /models/1
|
279
|
+
it 'should update an item on put' do
|
280
|
+
put '/people/1', :name => 'another name'
|
281
|
+
response_should_be 302, 'person updated'
|
282
|
+
end
|
283
|
+
|
284
|
+
# destroy DELETE /models/1
|
285
|
+
it 'should destroy an item on delete' do
|
286
|
+
delete '/people/1'
|
287
|
+
response_should_be 302, 'person deleted'
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
291
|
+
|
292
|
+
describe 'some use cases' do
|
293
|
+
|
294
|
+
it 'should list all persons' do
|
295
|
+
get '/people'
|
296
|
+
response_should_be 200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>'
|
297
|
+
model_should_be 3
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'should read all persons' do
|
301
|
+
get '/people'
|
302
|
+
|
303
|
+
el_people = doc(body).elements.to_a("*/person/id")
|
304
|
+
el_people.size.should == 3
|
305
|
+
model_should_be 3
|
306
|
+
|
307
|
+
get "/people/#{el_people[0].text}"
|
308
|
+
response_should_be 200, '<person><id>1</id><name>one</name></person>'
|
309
|
+
model_should_be 3
|
310
|
+
|
311
|
+
get "/people/#{el_people[1].text}"
|
312
|
+
response_should_be 200, '<person><id>2</id><name>two</name></person>'
|
313
|
+
model_should_be 3
|
314
|
+
|
315
|
+
get "/people/#{el_people[2].text}"
|
316
|
+
response_should_be 200, '<person><id>3</id><name>three</name></person>'
|
317
|
+
model_should_be 3
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'should create a new person' do
|
321
|
+
get '/people'
|
322
|
+
response_should_be 200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>'
|
323
|
+
model_should_be 3
|
324
|
+
|
325
|
+
get '/people/new'
|
326
|
+
response_should_be 200, '<person><id/><name/></person>'
|
327
|
+
model_should_be 3
|
328
|
+
|
329
|
+
post '/people', {:name => 'four'}
|
330
|
+
response_should_be 302, 'person created'
|
331
|
+
model_should_be 4
|
332
|
+
|
333
|
+
get '/people'
|
334
|
+
response_should_be 200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person><person><id>4</id></person></people>'
|
335
|
+
model_should_be 4
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'should update a person' do
|
339
|
+
get '/people/2'
|
340
|
+
response_should_be 200, '<person><id>2</id><name>two</name></person>'
|
341
|
+
model_should_be 3
|
342
|
+
|
343
|
+
put '/people/2', {:name => 'tomorrow'}
|
344
|
+
response_should_be 302, 'person updated'
|
345
|
+
model_should_be 3
|
346
|
+
|
347
|
+
get '/people/2'
|
348
|
+
response_should_be 200, '<person><id>2</id><name>tomorrow</name></person>'
|
349
|
+
model_should_be 3
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'should delete a person' do
|
353
|
+
get '/people'
|
354
|
+
response_should_be 200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>'
|
355
|
+
model_should_be 3
|
356
|
+
|
357
|
+
delete '/people/2'
|
358
|
+
response_should_be 302, 'person deleted'
|
359
|
+
model_should_be 2
|
360
|
+
|
361
|
+
get '/people'
|
362
|
+
response_should_be 200, '<people><person><id>1</id></person><person><id>3</id></person></people>'
|
363
|
+
model_should_be 2
|
364
|
+
|
365
|
+
get '/people/2'
|
366
|
+
response_should_be 404, 'person not found'
|
367
|
+
model_should_be 2
|
368
|
+
end
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
describe 'life-cycle' do
|
373
|
+
|
374
|
+
before(:each) do
|
375
|
+
Person.class_eval '@call_order = []; def self.call_order; @call_order; end; def self.call_order=(arr); @call_order = arr; end'
|
376
|
+
rest Person, :renderer => 'erb' do
|
377
|
+
def before(name)
|
378
|
+
Person.call_order << {:method => :before, :called => name}
|
379
|
+
end
|
380
|
+
|
381
|
+
def after(name)
|
382
|
+
Person.call_order << {:method => :after, :called => name}
|
383
|
+
end
|
384
|
+
|
385
|
+
def index
|
386
|
+
Person.call_order << {:method => :index, :called => :index}
|
387
|
+
super
|
388
|
+
end
|
389
|
+
|
390
|
+
def new
|
391
|
+
Person.call_order << {:method => :new, :called => :new}
|
392
|
+
super
|
393
|
+
end
|
394
|
+
|
395
|
+
def create
|
396
|
+
Person.call_order << {:method => :create, :called => :create}
|
397
|
+
super
|
398
|
+
end
|
399
|
+
|
400
|
+
def show
|
401
|
+
Person.call_order << {:method => :show, :called => :show}
|
402
|
+
super
|
403
|
+
end
|
404
|
+
|
405
|
+
def edit
|
406
|
+
Person.call_order << {:method => :edit, :called => :edit}
|
407
|
+
super
|
408
|
+
end
|
409
|
+
|
410
|
+
def update
|
411
|
+
Person.call_order << {:method => :update, :called => :update}
|
412
|
+
super
|
413
|
+
end
|
414
|
+
|
415
|
+
def destroy
|
416
|
+
Person.call_order << {:method => :destroy, :called => :destroy}
|
417
|
+
super
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'should call :before and :after in the right order' do
|
423
|
+
call_order_should_be [:before, :index, :after] do get '/people' end
|
424
|
+
call_order_should_be [:before, :new, :after] do get '/people/new' end
|
425
|
+
call_order_should_be [:before, :create, :after] do post '/people', :name => 'initial name' end
|
426
|
+
call_order_should_be [:before, :show, :after] do get '/people/1' end
|
427
|
+
call_order_should_be [:before, :edit, :after] do get '/people/1/edit' end
|
428
|
+
call_order_should_be [:before, :update, :after] do put '/people/1', :name => 'new name' end
|
429
|
+
call_order_should_be [:before, :destroy, :after] do delete '/people/1' end
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'should call :before and :after with the name of the called method' do
|
433
|
+
called_should_be :index do get '/people' end
|
434
|
+
called_should_be :new do get '/people/new' end
|
435
|
+
called_should_be :create do post '/people', :name => 'initial name' end
|
436
|
+
called_should_be :show do get '/people/1' end
|
437
|
+
called_should_be :edit do get '/people/1/edit' end
|
438
|
+
called_should_be :update do put '/people/1', :name => 'new name' end
|
439
|
+
called_should_be :destroy do delete '/people/1' end
|
440
|
+
end
|
441
|
+
|
442
|
+
end
|
443
|
+
|
444
|
+
end
|
445
|
+
|
446
|
+
end
|
447
|
+
|
448
|
+
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blindgaenger-sinatra-rest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.2"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- blindgaenger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-28 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sinatra
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.0.5
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: english
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.1
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: blindgaenger@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- Rakefile
|
45
|
+
- README.textile
|
46
|
+
- lib/rest.rb
|
47
|
+
- spec/rest_spec.rb
|
48
|
+
- spec/views/people/edit.erb
|
49
|
+
- spec/views/people/index.erb
|
50
|
+
- spec/views/people/new.erb
|
51
|
+
- spec/views/people/show.erb
|
52
|
+
has_rdoc: "false"
|
53
|
+
homepage: http://github.com/blindgaenger/sinatra-rest
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
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.2.0
|
75
|
+
signing_key:
|
76
|
+
specification_version: 2
|
77
|
+
summary: Generates RESTful routes for the models of a Sinatra application
|
78
|
+
test_files: []
|
79
|
+
|