blindgaenger-sinatra-rest 0.2 → 0.3.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/Rakefile +4 -5
- data/lib/sinatra/rest.rb +153 -0
- data/lib/sinatra/rest/adapters.rb +24 -0
- data/lib/sinatra/rest/controller.tpl.rb +65 -0
- data/lib/sinatra/rest/helpers.tpl.rb +49 -0
- data/lib/sinatra/rest/routes.tpl.rb +78 -0
- data/test/call_order_spec.rb +111 -0
- data/test/crud_spec.rb +99 -0
- data/test/helper.rb +151 -0
- data/test/helpers_spec.rb +76 -0
- data/test/inflection_spec.rb +29 -0
- data/test/routes_spec.rb +112 -0
- data/test/test_spec.rb +19 -0
- data/test/views/people/edit.haml +4 -0
- data/test/views/people/index.haml +7 -0
- data/test/views/people/new.haml +4 -0
- data/test/views/people/show.haml +4 -0
- metadata +19 -9
- data/lib/rest.rb +0 -278
- data/spec/rest_spec.rb +0 -448
- data/spec/views/people/edit.erb +0 -4
- data/spec/views/people/index.erb +0 -10
- data/spec/views/people/new.erb +0 -4
- data/spec/views/people/show.erb +0 -4
data/Rakefile
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'spec/rake/spectask'
|
2
2
|
|
3
|
-
task :default =>
|
4
|
-
task :test => :spec
|
3
|
+
task :default => :test
|
5
4
|
|
6
|
-
desc "Run
|
7
|
-
Spec::Rake::SpecTask.new :
|
8
|
-
t.spec_opts = %w(--format specdoc --color)
|
5
|
+
desc "Run tests"
|
6
|
+
Spec::Rake::SpecTask.new :test do |t|
|
7
|
+
t.spec_opts = %w(--format specdoc --color --backtrace)
|
9
8
|
t.spec_files = FileList['test/*_spec.rb']
|
10
9
|
end
|
11
10
|
|
data/lib/sinatra/rest.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'english/inflect'
|
3
|
+
|
4
|
+
libdir = File.dirname(__FILE__) + "/rest"
|
5
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
6
|
+
require 'adapters'
|
7
|
+
|
8
|
+
module Sinatra
|
9
|
+
|
10
|
+
module REST
|
11
|
+
|
12
|
+
#
|
13
|
+
# adds restful routes and url helpers for the model
|
14
|
+
def rest(model_class, options={}, &block)
|
15
|
+
parse_args(model_class, options)
|
16
|
+
|
17
|
+
# register model specific helpers
|
18
|
+
helpers read_module_template('rest/helpers.tpl.rb')
|
19
|
+
|
20
|
+
# create an own module, to override the template with custom methods
|
21
|
+
# this way, you can still use #super# in the overridden methods
|
22
|
+
controller = read_module_template('rest/controller.tpl.rb')
|
23
|
+
if block_given?
|
24
|
+
custom = CustomController.new(@plural)
|
25
|
+
custom.instance_eval &block
|
26
|
+
custom.module.send :include, controller
|
27
|
+
controller = custom.module
|
28
|
+
end
|
29
|
+
helpers controller
|
30
|
+
|
31
|
+
# register routes as DSL extension
|
32
|
+
instance_eval read_template('rest/routes.tpl.rb')
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
ROUTES = {
|
38
|
+
:all => [:index, :new, :create, :show, :edit, :update, :destroy],
|
39
|
+
:readable => [:index, :show],
|
40
|
+
:writeable => [:index, :show, :create, :update, :destroy],
|
41
|
+
:editable => [:index, :show, :create, :update, :destroy, :new, :edit],
|
42
|
+
}
|
43
|
+
|
44
|
+
def parse_args(model_class, options)
|
45
|
+
@model, @singular, @plural = conjugate(model_class)
|
46
|
+
@renderer = (options.delete(:renderer) || :haml).to_s
|
47
|
+
@route_flags = parse_routes(options.delete(:routes) || :all)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_routes(routes)
|
51
|
+
[*routes].map {|route| ROUTES[route] || route}.flatten.uniq
|
52
|
+
end
|
53
|
+
|
54
|
+
def gsub_routes(routes, template)
|
55
|
+
routes.each {|route| template.gsub!(/#{route.to_s.upcase}/, true.to_s) }
|
56
|
+
(ROUTES[:all] - routes).each {|route| template.gsub!(/#{route.to_s.upcase}/, false.to_s) }
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# creates the necessary forms of the model name
|
61
|
+
# pretty much like ActiveSupport's inflections, but don't like to depend on
|
62
|
+
def conjugate(model_class)
|
63
|
+
model = model_class.to_s.match(/(\w+)$/)[0]
|
64
|
+
singular = model.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
65
|
+
return model, singular, singular.pluralize
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# read the file and do some substitutions
|
70
|
+
def read_template(filename)
|
71
|
+
t = File.read(File.join(File.dirname(__FILE__), filename))
|
72
|
+
t.gsub!(/PLURAL/, @plural)
|
73
|
+
t.gsub!(/SINGULAR/, @singular)
|
74
|
+
t.gsub!(/MODEL/, @model)
|
75
|
+
t.gsub!(/RENDERER/, @renderer)
|
76
|
+
gsub_routes(@route_flags, t)
|
77
|
+
t
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# read the template and put it into an anonymous module
|
82
|
+
def read_module_template(filename)
|
83
|
+
t = read_template(filename)
|
84
|
+
m = Module.new
|
85
|
+
m.module_eval(t, filename)
|
86
|
+
m
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# model unspecific helpers, will be included once
|
91
|
+
module Helpers
|
92
|
+
# for example _method will be removed
|
93
|
+
def filter_model_params(params)
|
94
|
+
params.reject {|k, v| k =~ /^_/}
|
95
|
+
end
|
96
|
+
|
97
|
+
def escape_model_id(model)
|
98
|
+
if model.nil?
|
99
|
+
raise 'can not generate url for nil'
|
100
|
+
elsif model.kind_of?(String)
|
101
|
+
Rack::Utils.escape(model)
|
102
|
+
elsif model.kind_of?(Fixnum)
|
103
|
+
model
|
104
|
+
elsif model.id.kind_of? String
|
105
|
+
Rack::Utils.escape(model.id)
|
106
|
+
else
|
107
|
+
model.id
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def call_model_method(model_class, name, options={})
|
112
|
+
method = model_class.method(name)
|
113
|
+
if method.arity == 0
|
114
|
+
Kernel.warn "warning: calling #{model_class.to_s}##{name} with args, although it doesn't take args" if options
|
115
|
+
method.call
|
116
|
+
else
|
117
|
+
method.call(options)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# used as context to evaluate the controller's module
|
124
|
+
class CustomController
|
125
|
+
attr_reader :module
|
126
|
+
|
127
|
+
def initialize(prefix)
|
128
|
+
@prefix = prefix
|
129
|
+
@module = Module.new
|
130
|
+
end
|
131
|
+
|
132
|
+
def before(&block) prefix :before, █ end
|
133
|
+
def after(&block) prefix :after, █ end
|
134
|
+
def index(&block) prefix :index, █ end
|
135
|
+
def new(&block) prefix :new, █ end
|
136
|
+
def create(&block) prefix :create, █ end
|
137
|
+
def show(&block) prefix :show, █ end
|
138
|
+
def edit(&block) prefix :edit, █ end
|
139
|
+
def update(&block) prefix :update, █ end
|
140
|
+
def destroy(&block) prefix :destroy, █ end
|
141
|
+
|
142
|
+
private
|
143
|
+
def prefix(name, &block)
|
144
|
+
@module.send :define_method, "#{@prefix}_#{name}", &block if block_given?
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end # REST
|
149
|
+
|
150
|
+
helpers REST::Helpers
|
151
|
+
register REST
|
152
|
+
|
153
|
+
end # Sinatra
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Stone
|
2
|
+
module Resource
|
3
|
+
def find_by_id(id)
|
4
|
+
get(id)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module DataMapper
|
10
|
+
module Model
|
11
|
+
def find_by_id(id)
|
12
|
+
get(id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ActiveRecord
|
18
|
+
module Base
|
19
|
+
def find_by_id(id)
|
20
|
+
first(id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
def PLURAL_before(name)
|
2
|
+
# override
|
3
|
+
end
|
4
|
+
|
5
|
+
def PLURAL_after(name)
|
6
|
+
# override
|
7
|
+
end
|
8
|
+
|
9
|
+
# index GET /models
|
10
|
+
if INDEX
|
11
|
+
def PLURAL_index(options=nil)
|
12
|
+
@PLURAL = call_model_method(MODEL, :all, options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# new GET /models/new
|
17
|
+
if NEW
|
18
|
+
def PLURAL_new(options=nil)
|
19
|
+
@SINGULAR = call_model_method(MODEL, :new, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# create POST /models
|
24
|
+
if CREATE
|
25
|
+
def PLURAL_create(options=params)
|
26
|
+
mp = filter_model_params(options)
|
27
|
+
@SINGULAR = call_model_method(MODEL, :new, mp)
|
28
|
+
@SINGULAR.save
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# show GET /models/1
|
33
|
+
if SHOW
|
34
|
+
def PLURAL_show(options=params)
|
35
|
+
mp = filter_model_params(options)
|
36
|
+
@SINGULAR = call_model_method(MODEL, :find_by_id, mp[:id])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# edit GET /models/1/edit
|
41
|
+
if EDIT
|
42
|
+
def PLURAL_edit(options=params)
|
43
|
+
mp = filter_model_params(options)
|
44
|
+
@SINGULAR = call_model_method(MODEL, :find_by_id, mp[:id])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# update PUT /models/1
|
49
|
+
if UPDATE
|
50
|
+
def PLURAL_update(options=params)
|
51
|
+
mp = filter_model_params(options)
|
52
|
+
@SINGULAR = call_model_method(MODEL, :find_by_id, mp[:id])
|
53
|
+
@SINGULAR.update_attributes(mp) unless @SINGULAR.nil?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# destroy DELETE /models/1
|
58
|
+
if DESTROY
|
59
|
+
def PLURAL_destroy(options=params)
|
60
|
+
mp = filter_model_params(options)
|
61
|
+
call_model_method(MODEL, :delete, mp[:id])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# index GET /models
|
2
|
+
if INDEX
|
3
|
+
def url_for_PLURAL_index
|
4
|
+
'/PLURAL'
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# new GET /models/new
|
9
|
+
if NEW
|
10
|
+
def url_for_PLURAL_new
|
11
|
+
'/PLURAL/new'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# create POST /models
|
16
|
+
if CREATE
|
17
|
+
def url_for_PLURAL_create
|
18
|
+
'/PLURAL'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# show GET /models/1
|
23
|
+
if SHOW
|
24
|
+
def url_for_PLURAL_show(model)
|
25
|
+
"/PLURAL/#{escape_model_id(model)}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# edit GET /models/1/edit
|
30
|
+
if EDIT
|
31
|
+
def url_for_PLURAL_edit(model)
|
32
|
+
"/PLURAL/#{escape_model_id(model)}/edit"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# update PUT /models/1
|
37
|
+
if UPDATE
|
38
|
+
def url_for_PLURAL_update(model)
|
39
|
+
"/PLURAL/#{escape_model_id(model)}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# destroy DELETE /models/1
|
44
|
+
if DESTROY
|
45
|
+
def url_for_PLURAL_destroy(model)
|
46
|
+
"/PLURAL/#{escape_model_id(model)}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# index GET /models
|
2
|
+
if INDEX
|
3
|
+
get '/PLURAL' do
|
4
|
+
PLURAL_before :index
|
5
|
+
PLURAL_index
|
6
|
+
PLURAL_after :index
|
7
|
+
RENDERER :"PLURAL/index", options
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# new GET /models/new
|
12
|
+
if NEW
|
13
|
+
get '/PLURAL/new' do
|
14
|
+
PLURAL_before :new
|
15
|
+
PLURAL_new
|
16
|
+
PLURAL_after :new
|
17
|
+
RENDERER :"PLURAL/new", options
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# create POST /models
|
22
|
+
if CREATE
|
23
|
+
post '/PLURAL' do
|
24
|
+
PLURAL_before :create
|
25
|
+
PLURAL_create
|
26
|
+
PLURAL_after :create
|
27
|
+
redirect url_for_PLURAL_show(@SINGULAR), 'SINGULAR created'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# show GET /models/1
|
32
|
+
if SHOW
|
33
|
+
get '/PLURAL/:id' do
|
34
|
+
PLURAL_before :show
|
35
|
+
PLURAL_show
|
36
|
+
PLURAL_after :show
|
37
|
+
if @SINGULAR.nil?
|
38
|
+
throw :halt, [404, 'SINGULAR not found']
|
39
|
+
else
|
40
|
+
RENDERER :"PLURAL/show", options
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# edit GET /models/1/edit
|
46
|
+
if EDIT
|
47
|
+
get '/PLURAL/:id/edit' do
|
48
|
+
PLURAL_before :edit
|
49
|
+
PLURAL_edit
|
50
|
+
PLURAL_after :edit
|
51
|
+
RENDERER :"PLURAL/edit", options
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# update PUT /models/1
|
56
|
+
if UPDATE
|
57
|
+
put '/PLURAL/:id' do
|
58
|
+
PLURAL_before :update
|
59
|
+
PLURAL_update
|
60
|
+
PLURAL_after :update
|
61
|
+
if @SINGULAR.nil?
|
62
|
+
throw :halt, [404, 'SINGULAR not found']
|
63
|
+
else
|
64
|
+
redirect url_for_PLURAL_show(@SINGULAR), 'SINGULAR updated'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# destroy DELETE /models/1
|
70
|
+
if DESTROY
|
71
|
+
delete '/PLURAL/:id' do
|
72
|
+
PLURAL_before :destroy
|
73
|
+
PLURAL_destroy
|
74
|
+
PLURAL_after :destroy
|
75
|
+
redirect url_for_PLURAL_index, 'SINGULAR destroyed'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
describe 'call order' do
|
4
|
+
|
5
|
+
def called_routes
|
6
|
+
@app.call_order.map {|r, m| r}.uniq
|
7
|
+
end
|
8
|
+
|
9
|
+
def called_methods
|
10
|
+
@app.call_order.map {|r, m| m}
|
11
|
+
end
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
mock_app {
|
15
|
+
configure do
|
16
|
+
set :call_order, []
|
17
|
+
end
|
18
|
+
|
19
|
+
rest Person do
|
20
|
+
before do |route|
|
21
|
+
options.call_order << [route, :before]
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
after do |route|
|
26
|
+
options.call_order << [route, :after]
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
index do
|
31
|
+
options.call_order << [:index, :index]
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
new do
|
36
|
+
options.call_order << [:new, :new]
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
create do
|
41
|
+
options.call_order << [:create, :create]
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
show do
|
46
|
+
options.call_order << [:show, :show]
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
edit do
|
51
|
+
options.call_order << [:edit, :edit]
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
update do
|
56
|
+
options.call_order << [:update, :update]
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
destroy do
|
61
|
+
options.call_order << [:destroy, :destroy]
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should call :index in the right order' do
|
69
|
+
index '/people'
|
70
|
+
called_methods.should == [:before, :index, :after]
|
71
|
+
called_routes.should == [:index]
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should call :new in the right order' do
|
75
|
+
new '/people/new'
|
76
|
+
called_methods.should == [:before, :new, :after]
|
77
|
+
called_routes.should == [:new]
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should call :create in the right order' do
|
81
|
+
create('/people', :name => 'initial name')
|
82
|
+
called_methods.should == [:before, :create, :after]
|
83
|
+
called_routes.should == [:create]
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should call :show in the right order' do
|
87
|
+
show '/people/1'
|
88
|
+
called_methods.should == [:before, :show, :after]
|
89
|
+
called_routes.should == [:show]
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should call :edit in the right order' do
|
93
|
+
edit '/people/1/edit'
|
94
|
+
called_methods.should == [:before, :edit, :after]
|
95
|
+
called_routes.should == [:edit]
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should call :update in the right order' do
|
99
|
+
update '/people/1', :name => 'new name'
|
100
|
+
called_methods.should == [:before, :update, :after]
|
101
|
+
called_routes.should == [:update]
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should call :destroy in the right order' do
|
105
|
+
destroy '/people/1'
|
106
|
+
called_methods.should == [:before, :destroy, :after]
|
107
|
+
called_routes.should == [:destroy]
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|