sinatra-rest 0.3.3

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