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 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
+