blindgaenger-sinatra-rest 0.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,11 +1,10 @@
1
1
  require 'spec/rake/spectask'
2
2
 
3
- task :default => [:test]
4
- task :test => :spec
3
+ task :default => :test
5
4
 
6
- desc "Run specs"
7
- Spec::Rake::SpecTask.new :spec do |t|
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
 
@@ -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
+