sinatra-rest 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
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 'sinatra/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><code>
51
+ # simply add this line
52
+
53
+ rest Person, :renderer => :erb
54
+
55
+ # and this is generated for you
56
+
57
+ get '/people' do
58
+ @people = Person.all
59
+ erb :"people/index", options
60
+ end
61
+
62
+ put '/people/:id' do
63
+ @person = Person.find_by_id(params[:id])
64
+ redirect url_for_people_show(@person), 'person updated'
65
+ end
66
+
67
+ # further restful routes for Person ...
68
+ </code></pre>
69
+
70
+ That's only half the truth! The routes are generated dynamically, so all
71
+ defaults can be overridden (the behaviour, after/before callbacks, used renderer,
72
+ which routes are added).
73
+
74
+ For more details and options, please have a look at the pages in the
75
+ "Sinatra-REST Wiki":http://github.com/blindgaenger/sinatra-rest/wikis on Github.
76
+
77
+ h2. Links
78
+
79
+ * "Documentation @ rdoc.info":http://rdoc.info/projects/blindgaenger/sinatra-rest
80
+ * "Continuous Integration @ RunCodeRun":http://runcoderun.com/blindgaenger/sinatra-rest
81
+
82
+ h2. Contact
83
+
84
+ You can contact me via mail at blindgaenger at gmail dot com, or leave me a
85
+ message on my "Github profile":http://github.com/blindgaenger.
86
+
@@ -0,0 +1,10 @@
1
+ require 'spec/rake/spectask'
2
+
3
+ task :default => :test
4
+
5
+ desc "Run tests"
6
+ Spec::Rake::SpecTask.new :test do |t|
7
+ t.spec_opts = %w(--format specdoc --color) #--backtrace
8
+ t.spec_files = FileList['test/*_spec.rb']
9
+ end
10
+
@@ -0,0 +1,210 @@
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
+ require 'yaml'
8
+
9
+ module Sinatra
10
+
11
+ module REST
12
+
13
+ #
14
+ # adds restful routes and url helpers for the model
15
+ def rest(model_class, options={}, &block)
16
+ parse_args(model_class, options)
17
+ read_config('rest/rest.yaml')
18
+
19
+ # register model specific helpers
20
+ helpers generate_helpers
21
+
22
+ # create an own module, to override the template with custom methods
23
+ # this way, you can still use #super# in the overridden methods
24
+ controller = generate_controller
25
+ if block_given?
26
+ custom = CustomController.new(@plural)
27
+ custom.instance_eval &block
28
+ custom.module.send :include, controller
29
+ controller = custom.module
30
+ end
31
+ helpers controller
32
+
33
+ # register routes as DSL extension
34
+ instance_eval generate_routes
35
+ end
36
+
37
+ protected
38
+
39
+ ROUTES = {
40
+ :all => [:index, :new, :create, :show, :edit, :update, :destroy],
41
+ :readable => [:index, :show],
42
+ :writeable => [:index, :show, :create, :update, :destroy],
43
+ :editable => [:index, :show, :create, :update, :destroy, :new, :edit],
44
+ }
45
+
46
+ def parse_args(model_class, options)
47
+ @model, @singular, @plural = conjugate(model_class)
48
+ @renderer = (options.delete(:renderer) || :haml).to_s
49
+ @route_flags = parse_routes(options.delete(:routes) || :all)
50
+ end
51
+
52
+ def parse_routes(routes)
53
+ routes = [*routes].map {|route| ROUTES[route] || route}.flatten.uniq
54
+ # keep the order of :all routes
55
+ ROUTES[:all].select{|route| routes.include? route}
56
+ end
57
+
58
+ def read_config(filename)
59
+ file = File.read(File.join(File.dirname(__FILE__), filename))
60
+ @config = YAML.load file
61
+ end
62
+
63
+ #
64
+ # creates the necessary forms of the model name
65
+ # pretty much like ActiveSupport's inflections, but don't like to depend on
66
+ def conjugate(model_class)
67
+ model = model_class.to_s.match(/(\w+)$/)[0]
68
+ singular = model.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
69
+ return model, singular, singular.pluralize
70
+ end
71
+
72
+ def replace_variables(t, route=nil)
73
+ if route
74
+ t.gsub!('NAME', route.to_s)
75
+ t.gsub!('VERB', @config[route][:verb].downcase)
76
+ t.gsub!('URL', @config[route][:url])
77
+ t.gsub!('CONTROL', @config[route][:control])
78
+ t.gsub!('RENDER', @config[route][:render])
79
+ end
80
+ t.gsub!(/PLURAL/, @plural)
81
+ t.gsub!(/SINGULAR/, @singular)
82
+ t.gsub!(/MODEL/, @model)
83
+ t.gsub!(/RENDERER/, @renderer)
84
+ t
85
+ end
86
+
87
+ def generate_routes
88
+ @route_flags.map{|r| route_template(r)}.join("\n\n")
89
+ end
90
+
91
+ def route_template(route)
92
+ t = <<-RUBY
93
+ VERB 'URL' do
94
+ PLURAL_before :NAME
95
+ PLURAL_NAME
96
+ PLURAL_after :NAME
97
+ RENDER
98
+ end
99
+ RUBY
100
+ replace_variables(t, route)
101
+ end
102
+
103
+ def generate_helpers
104
+ m = Module.new
105
+ @route_flags.each {|r|
106
+ m.module_eval helpers_template(r)
107
+ }
108
+ m
109
+ end
110
+
111
+ def helpers_template(route)
112
+ t = <<-RUBY
113
+ def url_for_PLURAL_NAME(model=nil)
114
+ "URL"
115
+ end
116
+ RUBY
117
+ helper_route = @config[route][:url].gsub(':id', '#{escape_model_id(model)}')
118
+ t.gsub!('URL', helper_route)
119
+ replace_variables(t, route)
120
+ end
121
+
122
+ def generate_controller
123
+ m = Module.new
124
+ t = <<-RUBY
125
+ def PLURAL_before(name); end
126
+ def PLURAL_after(name); end
127
+ RUBY
128
+ m.module_eval replace_variables(t)
129
+
130
+ @route_flags.each {|route|
131
+ m.module_eval controller_template(route)
132
+ }
133
+ m
134
+ end
135
+
136
+ def controller_template(route)
137
+ t = <<-RUBY
138
+ def PLURAL_NAME(options=params)
139
+ mp = filter_model_params(options)
140
+ CONTROL
141
+ end
142
+ RUBY
143
+ replace_variables(t, route)
144
+ end
145
+
146
+ #
147
+ # model unspecific helpers, will be included once
148
+ module Helpers
149
+ # for example _method will be removed
150
+ def filter_model_params(params)
151
+ params.reject {|k, v| k =~ /^_/}
152
+ end
153
+
154
+ def escape_model_id(model)
155
+ if model.nil?
156
+ raise 'can not generate url for nil'
157
+ elsif model.kind_of?(String)
158
+ Rack::Utils.escape(model)
159
+ elsif model.kind_of?(Fixnum)
160
+ model
161
+ elsif model.id.kind_of? String
162
+ Rack::Utils.escape(model.id)
163
+ else
164
+ model.id
165
+ end
166
+ end
167
+
168
+ def call_model_method(model_class, name, options={})
169
+ method = model_class.method(name)
170
+ if options.nil? || method.arity == 0
171
+ Kernel.warn "warning: calling #{model_class.to_s}##{name} with args, although it doesn't take args" if options
172
+ method.call
173
+ else
174
+ method.call(options)
175
+ end
176
+ end
177
+ end
178
+
179
+ #
180
+ # used as context to evaluate the controller's module
181
+ class CustomController
182
+ attr_reader :module
183
+
184
+ def initialize(prefix)
185
+ @prefix = prefix
186
+ @module = Module.new
187
+ end
188
+
189
+ def before(options={}, &block) prefix :before, &block; end
190
+ def after(options={}, &block) prefix :after, &block; end
191
+ def index(options={}, &block) prefix :index, &block; end
192
+ def new(options={}, &block) prefix :new, &block; end
193
+ def create(options={}, &block) prefix :create, &block; end
194
+ def show(options={}, &block) prefix :show, &block; end
195
+ def edit(options={}, &block) prefix :edit, &block; end
196
+ def update(options={}, &block) prefix :update, &block; end
197
+ def destroy(options={}, &block) prefix :destroy, &block; end
198
+
199
+ private
200
+ def prefix(name, &block)
201
+ @module.send :define_method, "#{@prefix}_#{name}", &block if block_given?
202
+ end
203
+ end
204
+
205
+ end # REST
206
+
207
+ helpers REST::Helpers
208
+ register REST
209
+
210
+ end # Sinatra
@@ -0,0 +1,37 @@
1
+ module Stone
2
+ module Resource
3
+ def find_by_id(id)
4
+ get(id)
5
+ end
6
+
7
+ def delete(id)
8
+ model = self.find_by_id(id)
9
+ model.destroy if model
10
+ end
11
+ end
12
+ end
13
+
14
+ module DataMapper
15
+ module Model
16
+ def find_by_id(id)
17
+ get(id)
18
+ end
19
+
20
+ def delete(id)
21
+ model = self.find_by_id(id)
22
+ model.destroy if model
23
+ end
24
+ end
25
+ end
26
+
27
+ module ActiveRecord
28
+ class Base
29
+ class << self
30
+ def find_by_id(id)
31
+ find(id)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+
@@ -0,0 +1,67 @@
1
+ ---
2
+ :index:
3
+ :verb: GET
4
+ :url: /PLURAL
5
+ :control: |-
6
+ @PLURAL = call_model_method(MODEL, :all, mp)
7
+ :render: |-
8
+ RENDERER :'PLURAL/index', options
9
+
10
+ :new:
11
+ :verb: GET
12
+ :url: /PLURAL/new
13
+ :control: |-
14
+ @SINGULAR = call_model_method(MODEL, :new, mp)
15
+ :render: |-
16
+ RENDERER :'PLURAL/new', options
17
+
18
+ :create:
19
+ :verb: POST
20
+ :url: /PLURAL
21
+ :control: |-
22
+ @SINGULAR = call_model_method(MODEL, :new, mp)
23
+ @SINGULAR.save
24
+ :render: |-
25
+ redirect url_for_PLURAL_show(@SINGULAR), 'SINGULAR created'
26
+
27
+ :show:
28
+ :verb: GET
29
+ :url: /PLURAL/:id
30
+ :control: |-
31
+ @SINGULAR = call_model_method(MODEL, :find_by_id, mp[:id])
32
+ :render: |-
33
+ if @SINGULAR.nil?
34
+ throw :halt, [404, 'SINGULAR not found']
35
+ else
36
+ RENDERER :'PLURAL/show', options
37
+ end
38
+
39
+ :edit:
40
+ :verb: GET
41
+ :url: /PLURAL/:id/edit
42
+ :control: |-
43
+ @SINGULAR = call_model_method(MODEL, :find_by_id, mp[:id])
44
+ :render: |-
45
+ RENDERER :'PLURAL/edit', options
46
+
47
+ :update:
48
+ :verb: PUT
49
+ :url: /PLURAL/:id
50
+ :control: |-
51
+ @SINGULAR = call_model_method(MODEL, :find_by_id, mp[:id])
52
+ @SINGULAR.update_attributes(mp) unless @SINGULAR.nil?
53
+ :render: |-
54
+ if @SINGULAR.nil?
55
+ throw :halt, [404, 'SINGULAR not found']
56
+ else
57
+ redirect url_for_PLURAL_show(@SINGULAR), 'SINGULAR updated'
58
+ end
59
+
60
+ :destroy:
61
+ :verb: DELETE
62
+ :url: /PLURAL/:id
63
+ :control: |-
64
+ call_model_method(MODEL, :delete, mp[:id])
65
+ :render: |-
66
+ redirect url_for_PLURAL_index, 'SINGULAR destroyed'
67
+
@@ -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
+
@@ -0,0 +1,99 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'some use cases' do
4
+
5
+ def total_models
6
+ Person.all.size
7
+ end
8
+
9
+ require "rexml/document"
10
+ def doc(xml)
11
+ REXML::Document.new(xml.gsub(/>\s+</, '><').strip)
12
+ end
13
+
14
+ before(:each) do
15
+ Person.reset!
16
+ mock_rest Person
17
+ end
18
+
19
+ it 'should list all persons' do
20
+ get '/people'
21
+ normalized_response.should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
22
+ total_models.should == 3
23
+ end
24
+
25
+ it 'should create a new person' do
26
+ get '/people'
27
+ normalized_response.should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
28
+ total_models.should == 3
29
+
30
+ get '/people/new'
31
+ normalized_response.should == [200, '<person><id></id><name></name></person>']
32
+ total_models.should == 3
33
+
34
+ post '/people', {:name => 'four'}
35
+ normalized_response.should == [302, 'person created']
36
+ total_models.should == 4
37
+
38
+ get '/people'
39
+ normalized_response.should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person><person><id>4</id></person></people>']
40
+ total_models.should == 4
41
+ end
42
+
43
+ it 'should read all persons' do
44
+ get '/people'
45
+
46
+ el_people = doc(body).elements.to_a("*/person/id")
47
+ el_people.size.should == 3
48
+ total_models.should == 3
49
+
50
+ get "/people/#{el_people[0].text}"
51
+ normalized_response.should == [200, '<person><id>1</id><name>one</name></person>']
52
+ total_models.should == 3
53
+
54
+ get "/people/#{el_people[1].text}"
55
+ normalized_response.should == [200, '<person><id>2</id><name>two</name></person>']
56
+ total_models.should == 3
57
+
58
+ get "/people/#{el_people[2].text}"
59
+ normalized_response.should == [200, '<person><id>3</id><name>three</name></person>']
60
+ total_models.should == 3
61
+
62
+ get "/people/99"
63
+ normalized_response.should == [404, 'route not found']
64
+ total_models.should == 3
65
+ end
66
+
67
+ it 'should update a person' do
68
+ get '/people/2'
69
+ normalized_response.should == [200, '<person><id>2</id><name>two</name></person>']
70
+ total_models.should == 3
71
+
72
+ put '/people/2', {:name => 'tomorrow'}
73
+ normalized_response.should == [302, 'person updated']
74
+ total_models.should == 3
75
+
76
+ get '/people/2'
77
+ normalized_response.should == [200, '<person><id>2</id><name>tomorrow</name></person>']
78
+ total_models.should == 3
79
+ end
80
+
81
+ it 'should destroy a person' do
82
+ get '/people'
83
+ normalized_response.should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
84
+ total_models.should == 3
85
+
86
+ delete '/people/2'
87
+ normalized_response.should == [302, 'person destroyed']
88
+ total_models.should == 2
89
+
90
+ get '/people'
91
+ normalized_response.should == [200, '<people><person><id>1</id></person><person><id>3</id></person></people>']
92
+ total_models.should == 2
93
+
94
+ get '/people/2'
95
+ normalized_response.should == [404, 'route not found']
96
+ total_models.should == 2
97
+ end
98
+
99
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec'
2
+ require 'spec/interop/test'
3
+ require 'sinatra/test'
4
+
5
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
6
+ require 'sinatra/base'
7
+ require 'sinatra/rest'
8
+
9
+ Sinatra::Default.set(:environment, :test)
10
+ Test::Unit::TestCase.send :include, Sinatra::Test
11
+
12
+ #
13
+ # Sets up a Sinatra::Base subclass defined with the block
14
+ # given. Used in setup or individual spec methods to establish
15
+ # the application.
16
+ def mock_app(&block)
17
+ base = Sinatra::Application
18
+ @app = Sinatra.new(base) do
19
+ set :views, File.dirname(__FILE__) + '/views'
20
+
21
+ not_found do
22
+ 'route not found'
23
+ end
24
+ end
25
+ @app.instance_eval(&block) if block_given?
26
+ end
27
+
28
+ #
29
+ # sets rest in a sinatra instance
30
+ # and returns the block's result, if a block is given
31
+ def mock_rest(model, options={}, &block)
32
+ mock_app do
33
+ rest model, options
34
+
35
+ self.new.instance_eval do
36
+ @app.instance_eval(&block) if block_given?
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ #
43
+ # normalize for easier testing
44
+ def normalized_response
45
+ return status, body.gsub(/>\s+</, '><').strip
46
+ end
47
+
48
+ # index GET /models
49
+ def index(url)
50
+ get url
51
+ normalized_response
52
+ end
53
+
54
+ # new GET /models/new
55
+ def new(url)
56
+ get url
57
+ normalized_response
58
+ end
59
+
60
+ # create POST /models
61
+ def create(url, params={})
62
+ post url, params
63
+ normalized_response
64
+ end
65
+
66
+ # show GET /models/1
67
+ def show(url)
68
+ get url
69
+ normalized_response
70
+ end
71
+
72
+ # edit GET /models/1/edit
73
+ def edit(url)
74
+ get url
75
+ normalized_response
76
+ end
77
+
78
+ # update PUT /models/1
79
+ def update(url, params={})
80
+ put url, params
81
+ normalized_response
82
+ end
83
+
84
+ # destroy DELETE /models/1
85
+ def destroy(url)
86
+ delete url
87
+ normalized_response
88
+ end
89
+
90
+
91
+ ##
92
+ ## kind of a 'minimal model'
93
+ class Person
94
+ attr_accessor :id
95
+ attr_accessor :name
96
+
97
+ def initialize(*args)
98
+ #puts "new #{args.inspect}"
99
+ if args.size == 0
100
+ @id = nil
101
+ @name = nil
102
+ elsif args.size == 2
103
+ @id = args[0].to_i
104
+ @name = args[1]
105
+ else args.size == 1
106
+ update_attributes(args[0])
107
+ end
108
+ end
109
+
110
+ def save
111
+ #puts "save #{@id}"
112
+ @@people << self
113
+ self.id = @@people.size
114
+ end
115
+
116
+ def update_attributes(hash)
117
+ #puts "update_attributes #{hash.inspect}"
118
+ unless hash.empty?
119
+ @id = hash['id'].to_i if hash.include?('id')
120
+ @name = hash['name'] if hash.include?('name')
121
+ end
122
+ end
123
+
124
+ def self.delete(id)
125
+ #puts "delete #{id}"
126
+ @@people.delete_if {|person| person.id == id.to_i}
127
+ end
128
+
129
+ @@people = []
130
+
131
+ def self.all(criteria={})
132
+ #puts 'all'
133
+ return @@people
134
+ end
135
+
136
+ def self.find_by_id(id)
137
+ #puts "find_by_id #{id}"
138
+ all.find {|f| f.id == id.to_i}
139
+ end
140
+
141
+ def self.clear!
142
+ @@people = []
143
+ end
144
+
145
+ def self.reset!
146
+ clear!
147
+ Person.new(1, 'one').save
148
+ Person.new(2, 'two').save
149
+ Person.new(3, 'three').save
150
+ end
151
+ end
152
+
153
+
@@ -0,0 +1,76 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'url helpers' do
4
+
5
+ it 'should generate the correct urls for the model' do
6
+ mock_rest Person do
7
+ person = Person.new(99, 'foo')
8
+ url_for_people_create.should == '/people'
9
+ url_for_people_destroy(person).should == '/people/99'
10
+ url_for_people_edit(person).should == '/people/99/edit'
11
+ url_for_people_index.should == '/people'
12
+ url_for_people_new.should == '/people/new'
13
+ url_for_people_show(person).should == '/people/99'
14
+ url_for_people_update(person).should == '/people/99'
15
+ end
16
+ end
17
+
18
+ it 'should add :all helpers' do
19
+ mock_rest(Person) { methods.grep(/^url_for_people_/).sort }.should == [
20
+ "url_for_people_create",
21
+ "url_for_people_destroy",
22
+ "url_for_people_edit",
23
+ "url_for_people_index",
24
+ "url_for_people_new",
25
+ "url_for_people_show",
26
+ "url_for_people_update",
27
+ ]
28
+ end
29
+
30
+ it 'should add :readable helpers' do
31
+ mock_rest(Person, :routes => :readable) { methods.grep(/^url_for_people_/).sort }.should == [
32
+ "url_for_people_index",
33
+ "url_for_people_show",
34
+ ]
35
+ end
36
+
37
+ it 'should add :writeable helpers' do
38
+ mock_rest(Person, :routes => :writeable) { methods.grep(/^url_for_people_/).sort }.should == [
39
+ "url_for_people_create",
40
+ "url_for_people_destroy",
41
+ "url_for_people_index",
42
+ "url_for_people_show",
43
+ "url_for_people_update",
44
+ ]
45
+ end
46
+
47
+ it 'should add :editable helpers' do
48
+ mock_rest(Person, :routes => :editable) { methods.grep(/^url_for_people_/).sort }.should == [
49
+ "url_for_people_create",
50
+ "url_for_people_destroy",
51
+ "url_for_people_edit",
52
+ "url_for_people_index",
53
+ "url_for_people_new",
54
+ "url_for_people_show",
55
+ "url_for_people_update",
56
+ ]
57
+ end
58
+
59
+ it 'should add helpers by name' do
60
+ mock_rest(Person, :routes => [:new, :create, :destroy]) { methods.grep(/^url_for_people_/).sort }.should == [
61
+ "url_for_people_create",
62
+ "url_for_people_destroy",
63
+ "url_for_people_new",
64
+ ]
65
+ end
66
+
67
+ it 'should add helpers by mixing aliases and names' do
68
+ mock_rest(Person, :routes => [:readable, :create, :destroy]) { methods.grep(/^url_for_people_/).sort }.should == [
69
+ "url_for_people_create",
70
+ "url_for_people_destroy",
71
+ "url_for_people_index",
72
+ "url_for_people_show",
73
+ ]
74
+ end
75
+
76
+ end
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'model inflection' do
4
+
5
+ def conjugate(model)
6
+ mock_app {
7
+ include Sinatra::REST
8
+ conjugate(model)
9
+ }
10
+ end
11
+
12
+ it "should conjugate a simple model name" do
13
+ conjugate(Person).should == %w(Person person people)
14
+ end
15
+
16
+ it "should conjugate a String as model name" do
17
+ conjugate('Person').should == %w(Person person people)
18
+ end
19
+
20
+ it "should conjugate a model name in camel cases" do
21
+ conjugate('SomePerson').should == %w(SomePerson some_person some_people)
22
+ end
23
+
24
+ it "should conjugate a model name without module" do
25
+ conjugate('MyModule::ModulePerson').should == %w(ModulePerson module_person module_people)
26
+ end
27
+
28
+ end
29
+
@@ -0,0 +1,112 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'routes' do
4
+
5
+ before(:each) do
6
+ Person.reset!
7
+ end
8
+
9
+ describe 'one by one' do
10
+
11
+ before(:each) do
12
+ mock_rest Person
13
+ end
14
+
15
+ it 'should list all people on index by their id' do
16
+ index('/people').should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
17
+ end
18
+
19
+ it 'should prepare an empty item on new' do
20
+ new('/people/new').should == [200, '<person><id></id><name></name></person>']
21
+ end
22
+
23
+ it 'should create an item on post' do
24
+ create('/people', :name => 'new resource').should == [302, 'person created']
25
+ end
26
+
27
+ it 'should show an item on get' do
28
+ show('/people/1').should == [200, '<person><id>1</id><name>one</name></person>']
29
+ end
30
+
31
+ it 'should get the item for editing' do
32
+ edit('/people/1/edit').should == [200, '<person><id>1</id><name>one</name></person>']
33
+ end
34
+
35
+ it 'should update an item on put' do
36
+ update('/people/1', :name => 'another name').should == [302, 'person updated']
37
+ end
38
+
39
+ it 'should destroy an item on delete' do
40
+ destroy('/people/1').should == [302, 'person destroyed']
41
+ end
42
+
43
+ end
44
+
45
+ describe 'options' do
46
+
47
+ it 'should add :all routes' do
48
+ mock_rest Person
49
+
50
+ index('/people').should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
51
+ new('/people/new').should == [200, '<person><id></id><name></name></person>']
52
+ create('/people', :name => 'new person').should == [302, "person created"]
53
+ show('/people/1').should == [200, '<person><id>1</id><name>one</name></person>']
54
+ edit('/people/1/edit').should == [200, "<person><id>1</id><name>one</name></person>"]
55
+ update('/people/1', :name => 'new name').should == [302, "person updated"]
56
+ destroy('/people/1').should == [302, "person destroyed"]
57
+ end
58
+
59
+ it 'should add :readable routes' do
60
+ mock_rest Person, :routes => :readable
61
+
62
+ index('/people').should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
63
+ show('/people/1').should == [200, '<person><id>1</id><name>one</name></person>']
64
+
65
+ new('/people/new').should == [404, "route not found"]
66
+ create('/people', :name => 'new person').should == [404, "route not found"]
67
+ edit('/people/1/edit').should == [404, "route not found"]
68
+ update('/people/1', :name => 'new name').should == [404, "route not found"]
69
+ destroy('/people/1').should == [404, "route not found"]
70
+ end
71
+
72
+ it 'should add :writeable routes' do
73
+ mock_rest Person, :routes => :writeable
74
+
75
+ index('/people').should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
76
+ show('/people/1').should == [200, '<person><id>1</id><name>one</name></person>']
77
+ create('/people', :name => 'new person').should == [302, "person created"]
78
+ update('/people/1', :name => 'new name').should == [302, "person updated"]
79
+ destroy('/people/1').should == [302, "person destroyed"]
80
+
81
+ new('/people/new').should == [404, "route not found"]
82
+ edit('/people/1/edit').should == [404, "route not found"]
83
+ end
84
+
85
+ it 'should add :editable routes' do
86
+ mock_rest Person, :routes => :editable
87
+
88
+ index('/people').should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
89
+ new('/people/new').should == [200, '<person><id></id><name></name></person>']
90
+ create('/people', :name => 'new person').should == [302, "person created"]
91
+ show('/people/1').should == [200, '<person><id>1</id><name>one</name></person>']
92
+ edit('/people/1/edit').should == [200, "<person><id>1</id><name>one</name></person>"]
93
+ update('/people/1', :name => 'new name').should == [302, "person updated"]
94
+ destroy('/people/1').should == [302, "person destroyed"]
95
+ end
96
+
97
+ it 'should add routes by name' do
98
+ mock_rest Person, :routes => [:readable, :new, :create]
99
+
100
+ index('/people').should == [200, '<people><person><id>1</id></person><person><id>2</id></person><person><id>3</id></person></people>']
101
+ show('/people/1').should == [200, '<person><id>1</id><name>one</name></person>']
102
+ new('/people/new').should == [200, '<person><id></id><name></name></person>']
103
+ create('/people', :name => 'new person').should == [302, "person created"]
104
+
105
+ edit('/people/1/edit').should == [404, "route not found"]
106
+ update('/people/1', :name => 'new name').should == [404, "route not found"]
107
+ destroy('/people/1').should == [404, "route not found"]
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'test helpers' do
4
+
5
+ it 'should work with mock_app' do
6
+ Person.clear!
7
+ mock_app {
8
+ rest Person
9
+ }
10
+ index('/people').should == [200, '<people></people>']
11
+ end
12
+
13
+ it 'should work with mock_rest' do
14
+ Person.clear!
15
+ mock_rest Person
16
+ index('/people').should == [200, '<people></people>']
17
+ end
18
+
19
+ end
@@ -0,0 +1,4 @@
1
+ %person
2
+ %id= @person.id
3
+ %name= @person.name
4
+
@@ -0,0 +1,7 @@
1
+ %people
2
+ - @people.each do |person|
3
+ %person
4
+ %id= person.id
5
+
6
+
7
+
@@ -0,0 +1,4 @@
1
+ %person
2
+ %id= @person.id
3
+ %name= @person.name
4
+
@@ -0,0 +1,4 @@
1
+ %person
2
+ %id= @person.id
3
+ %name= @person.name
4
+
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-rest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.3
5
+ platform: ruby
6
+ authors:
7
+ - blindgaenger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-25 00:00:00 +02: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/sinatra/rest.rb
47
+ - lib/sinatra/rest/adapters.rb
48
+ - lib/sinatra/rest/rest.yaml
49
+ - test/call_order_spec.rb
50
+ - test/crud_spec.rb
51
+ - test/helper.rb
52
+ - test/helpers_spec.rb
53
+ - test/inflection_spec.rb
54
+ - test/routes_spec.rb
55
+ - test/test_spec.rb
56
+ - test/views/people/edit.haml
57
+ - test/views/people/index.haml
58
+ - test/views/people/new.haml
59
+ - test/views/people/show.haml
60
+ has_rdoc: true
61
+ homepage: http://github.com/blindgaenger/sinatra-rest
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.3.5
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Generates RESTful routes for the models of a Sinatra application (ActiveRecord, DataMapper, Stone)
88
+ test_files: []
89
+