sinatra-trails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ *.swp
3
+ *.swo
4
+ .bundle
5
+ .DS_Store
6
+ Gemfile.lock
7
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in trails.gemspec
4
+ gemspec
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require "rspec/core/rake_task"
3
+
4
+ task :default => [:spec]
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,302 @@
1
+ require "sinatra/base"
2
+ require "active_support/inflector"
3
+ require "active_support/inflections"
4
+ require "active_support/core_ext/string/inflections"
5
+
6
+ libdir = File.dirname( __FILE__)
7
+ $:.unshift(libdir) unless $:.include?(libdir)
8
+ require "trails/version"
9
+
10
+ module Sinatra
11
+ module Trails
12
+ class RouteNotDefined < Exception; end
13
+
14
+ class Route
15
+ attr_reader :name, :full_name, :scope, :keys, :to_route, :to_regexp
16
+ Match = Struct.new(:captures)
17
+
18
+ def initialize route, name, ancestors, scope
19
+ @name = name.to_s
20
+ @full_name = ancestors.map { |ancestor| Scope === ancestor ? ancestor.name : ancestor }.push(name).select{ |name| Symbol === name }.join('_')
21
+ @components = Array === route ? route.compact : route.to_s.scan(/[^\/]+/)
22
+ @components.unshift *ancestors.map { |ancestor| ancestor.path if Scope === ancestor }.compact
23
+ @scope = scope
24
+ @captures = []
25
+ @to_route = "/#{@components.join('/')}"
26
+ namespace = ancestors.reverse.find { |ancestor| ancestor.class == Scope && ancestor.name }
27
+
28
+ @to_regexp, @keys = Sinatra::Base.send(:compile, to_route)
29
+ add_param 'resource', scope.name if [Resource, Resources].include?(scope.class)
30
+ add_param 'namespace', namespace.name if namespace
31
+ add_param 'action', name
32
+ scope.routes << self
33
+ end
34
+
35
+ def match str
36
+ if match_data = to_regexp.match(str)
37
+ Match.new(match_data.captures + @captures)
38
+ end
39
+ end
40
+
41
+ def to_path *args
42
+ query = args.pop if Hash === args.last
43
+ components = @components.dup
44
+ components.each_with_index do |component, index|
45
+ next unless /^:/ === component
46
+ val = args.pop or raise ArgumentError.new("Please provide `#{component}`")
47
+ components[index] =
48
+ case val
49
+ when Numeric, String, Symbol then val
50
+ else val.to_param end
51
+ end
52
+ raise ArgumentError.new("Too many params where passed") unless args.empty?
53
+ "/#{components.join('/')}#{'?' + Rack::Utils.build_nested_query(query) if query}"
54
+ end
55
+
56
+ private
57
+ def add_param key, capture
58
+ unless keys.include? key
59
+ @keys << key
60
+ @captures << capture
61
+ end
62
+ end
63
+ end
64
+
65
+ class ScopeMatcher
66
+ def initialize scope, matchers
67
+ @scope = scope
68
+ @names, @matchers = matchers.partition { |el| Symbol === el }
69
+ end
70
+
71
+ def match str
72
+ if @matchers.empty? && @names.empty?
73
+ Regexp.union(@scope.routes).match str
74
+ else
75
+ Regexp.union(*@matchers, *@names.map{ |name| @scope[name] }).match str
76
+ end
77
+ end
78
+ end
79
+
80
+ class Scope
81
+ attr_reader :name, :path, :ancestors, :routes
82
+
83
+ def initialize app, path, ancestors = []
84
+ @ancestors, @routes = ancestors, []
85
+ @path = path.to_s.sub(/^\//, '') if path
86
+ @name = path if Symbol === path
87
+ @sinatra_app = app
88
+ end
89
+
90
+ def map name, opts = {}, &block
91
+ path = opts.delete(:to) || name
92
+ route = Route.new(path, name, [*ancestors, self], self)
93
+ instance_eval &block if block_given?
94
+ route
95
+ end
96
+
97
+ def namespace path, &block
98
+ @routes += Scope.new(@sinatra_app, path, [*ancestors, self]).generate_routes!(&block)
99
+ end
100
+
101
+ def resource *names, &block
102
+ restful_routes Resource, names, &block
103
+ end
104
+
105
+ def resources *names, &block
106
+ restful_routes Resources, names, &block
107
+ end
108
+
109
+ def before *args, &block
110
+ opts = Hash === args.last ? args.pop : {}
111
+ @sinatra_app.before ScopeMatcher.new(self, args), opts, &block
112
+ end
113
+
114
+ def generate_routes! &block
115
+ instance_eval &block if block_given?
116
+ @routes
117
+ end
118
+
119
+ def routes_hash &block
120
+ Hash[*generate_routes!(&block).map{ |route| [route.full_name, route]}.flatten]
121
+ end
122
+
123
+ def route_for name
124
+ name = name.to_s
125
+ @routes.find{ |route| route.full_name == name || route.scope == self && route.name == name }
126
+ end
127
+ alias :[] :route_for
128
+
129
+ private
130
+ def method_missing name, *args, &block
131
+ return @sinatra_app.send(name, *args, &block) if @sinatra_app.respond_to?(name)
132
+ if route = route_for(name)
133
+ return route unless block_given?
134
+ @routes = @routes | route.scope.generate_routes!(&block)
135
+ else
136
+ super
137
+ end
138
+ end
139
+
140
+ def restful_routes builder, names, &block
141
+ opts = {}
142
+ hash = Hash === names.last ? names.pop : {}
143
+ hash.delete_if { |key, val| opts[key] = val if !(Symbol === val || Enumerable === val) }
144
+
145
+ nested = []
146
+ mash = Proc.new do |hash, acc|
147
+ hash.map do |key, val|
148
+ case val
149
+ when Hash
150
+ [*acc, key, *mash.call(val, key).flatten]
151
+ when Array
152
+ nested += val.map{ |r| [*acc, key, r] } and next
153
+ else
154
+ [key, val]
155
+ end
156
+ end
157
+ end
158
+
159
+ hash = (mash.call(hash) + nested).compact.map do |array|
160
+ array.reverse.inject(Proc.new{}) do |proc, name|
161
+ Proc.new{ send(builder == Resource ? :resource : :resources, name, opts, &proc) }
162
+ end.call
163
+ end
164
+
165
+ make = lambda do |name|
166
+ builder.new(@sinatra_app, name, [*ancestors, self], (@opts || {}).merge(opts))
167
+ end
168
+
169
+ if names.size == 1 && hash.empty?
170
+ @routes += make.call(names.first).generate_routes!(&block)
171
+ else
172
+ names.each { |name| @routes += make.call(name).generate_routes! }
173
+ instance_eval &block if block_given?
174
+ end
175
+ end
176
+ end
177
+
178
+ class Resource < Scope
179
+ attr_reader :opts
180
+ def initialize app, name, ancestors, opts
181
+ super app, name.to_sym, ancestors
182
+ @opts = opts
183
+ end
184
+
185
+ def member action
186
+ ancestors = [*self.ancestors, name]
187
+ path = @plural_name ? [plural_name, ':id'] : [name]
188
+
189
+ ancestors.unshift(action) and path.push(action) unless action == :show
190
+ ancestors.reject!{ |ancestor| Resource === ancestor } if opts[:shallow]
191
+ Route.new(path, action.to_s, ancestors, self)
192
+ end
193
+
194
+ def generate_routes! &block
195
+ define_routes and super
196
+ end
197
+
198
+ private
199
+ def define_routes
200
+ member(:show) and member(:new) and member(:edit)
201
+ end
202
+ end
203
+
204
+ class Resources < Resource
205
+ attr_reader :plural_name, :name_prefix
206
+
207
+ def initialize app, raw_name, ancestors, opts
208
+ super
209
+ @plural_name = name
210
+ @name = name.to_s.singularize.to_sym
211
+ @path = [plural_name, ":#{name}_id"]
212
+ end
213
+
214
+ def collection action
215
+ ancestors = [*self.ancestors, action == :new ? name : plural_name]
216
+ path = [plural_name]
217
+ ancestors.unshift(action) and path.push(action) unless action == :index
218
+
219
+ ancestors[0, ancestors.size - 2] = ancestors[0..-3].reject{ |ancestor| Resource === ancestor } if opts[:shallow]
220
+ Route.new(path, action.to_s, ancestors, self)
221
+ end
222
+
223
+ private
224
+ def define_routes
225
+ collection(:index) and collection(:new)
226
+ member(:show) and member(:edit)
227
+ end
228
+ end
229
+
230
+ def namespace name, &block
231
+ named_routes.merge! Scope.new(self, name).routes_hash(&block)
232
+ end
233
+
234
+ def map name, opts = {}, &block
235
+ namespace(nil) { map name, opts, &block }
236
+ route_for name
237
+ end
238
+
239
+ def resource *args, &block
240
+ namespace(nil) { resource *args, &block }
241
+ end
242
+
243
+ def resources *args, &block
244
+ namespace(nil) { resources *args, &block }
245
+ end
246
+
247
+ def route_for name
248
+ Trails.route_for(self, name).to_route
249
+ end
250
+
251
+ def path_for name, *args
252
+ Trails.route_for(self, name).to_path(*args)
253
+ end
254
+
255
+ def print_routes
256
+ trails = named_routes.map { |name, route| [name, route.to_route] }
257
+ name_padding = trails.sort_by{ |e| e.first.size }.last.first.size + 3
258
+ trails.each do |name, route|
259
+ puts sprintf("%#{name_padding}s => %s", name, route)
260
+ end
261
+ end
262
+
263
+ private
264
+ def named_routes
265
+ self::Routes.instance_variable_get :@routes
266
+ end
267
+
268
+ module Helpers
269
+ def path_for name, *args
270
+ Trails.route_for(self.class, name).to_path(*args)
271
+ end
272
+
273
+ def url_for *args
274
+ url path_for(*args)
275
+ end
276
+ end
277
+
278
+ class << self
279
+ def route_for klass, name
280
+ klass.ancestors.each do |ancestor|
281
+ next unless ancestor.class == Module && ancestor.include?(Helpers)
282
+ if route = ancestor.instance_variable_get(:@routes)[name]
283
+ return route
284
+ end
285
+ end
286
+ raise RouteNotDefined.new("The route `#{name}` is not defined")
287
+ end
288
+
289
+ def registered app
290
+ routes_module = Module.new do
291
+ include Helpers
292
+ @routes = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
293
+ end
294
+
295
+ app.const_set :Routes, routes_module
296
+ app.send :include, routes_module
297
+ end
298
+ end
299
+ end
300
+
301
+ Sinatra.register Trails
302
+ end
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module Trails
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,595 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'trails' do
4
+ include Rack::Test::Methods
5
+
6
+ let(:app) do
7
+ app = Class.new(Sinatra::Base)
8
+ app.register Sinatra::Trails
9
+ app.set :environment, :test
10
+ end
11
+
12
+ describe 'map' do
13
+ describe 'basic' do
14
+ before :all do
15
+ app.map :home, :to => '/'
16
+ app.map :dashboard
17
+ app.map :edit_user, :to => '/users/:id/edit'
18
+ end
19
+
20
+ describe 'routes' do
21
+ it { app.route_for(:home).should == '/' }
22
+ it { app.route_for(:dashboard).should == '/dashboard' }
23
+ it { app.route_for('dashboard').should == '/dashboard' }
24
+ it { lambda{ app.route_for(:missing) }.should raise_error Sinatra::Trails::RouteNotDefined }
25
+ end
26
+
27
+ describe 'paths' do
28
+ it { app.path_for(:home).should == '/' }
29
+ it { app.path_for(:home, :q => 'q', :a => 'a').should == '/?q=q&a=a' }
30
+
31
+ describe 'with placeholders' do
32
+ before { @mock_user = mock(:user, :to_param => 1)}
33
+ it { app.path_for(:edit_user, 1).should == '/users/1/edit' }
34
+ it { app.path_for(:edit_user, @mock_user).should == '/users/1/edit' }
35
+
36
+ describe 'wrong arg count' do
37
+ it 'should raise error when not passing :id' do
38
+ lambda { app.path_for(:edit_user) }.should raise_error(ArgumentError)
39
+ end
40
+
41
+ it 'should raise error when too many params are passed' do
42
+ lambda { app.path_for(:edit_user, 1, 2) }.should raise_error(ArgumentError)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ describe 'with namespace' do
50
+ before :all do
51
+ app.namespace '/admin' do
52
+ map :dashboard
53
+ map :logout
54
+ end
55
+ end
56
+ it { app.route_for(:dashboard).should == '/admin/dashboard' }
57
+ it { app.route_for(:logout).should == '/admin/logout' }
58
+ end
59
+
60
+ describe 'with named namespace' do
61
+ before :all do
62
+ app.namespace :admin do
63
+ map :dashboard
64
+ end
65
+ end
66
+ it { app.route_for(:admin_dashboard).should == '/admin/dashboard' }
67
+ end
68
+
69
+ describe 'with nested namespace' do
70
+ before :all do
71
+ app.namespace '/blog' do
72
+ map :users
73
+ namespace '/admin' do
74
+ map :dashboard
75
+ namespace '/auth' do
76
+ map :logout
77
+ map :login
78
+ end
79
+ end
80
+ end
81
+ end
82
+ it { app.route_for(:users).should == '/blog/users' }
83
+ it { app.route_for(:dashboard).should == '/blog/admin/dashboard' }
84
+ it { app.route_for(:logout).should == '/blog/admin/auth/logout' }
85
+ it { app.route_for(:login).should == '/blog/admin/auth/login' }
86
+ end
87
+
88
+ describe 'with named nested namespace' do
89
+ before :all do
90
+ app.namespace :blog do
91
+ map :users
92
+ namespace :admin do
93
+ map :dashboard
94
+ namespace :auth do
95
+ map :logout
96
+ map :login
97
+ end
98
+ end
99
+ end
100
+ end
101
+ it { app.route_for(:blog_users).should == '/blog/users' }
102
+ it { app.route_for(:blog_admin_dashboard).should == '/blog/admin/dashboard' }
103
+ it { app.route_for(:blog_admin_auth_logout).should == '/blog/admin/auth/logout' }
104
+ it { app.route_for(:blog_admin_auth_login).should == '/blog/admin/auth/login' }
105
+ end
106
+ end
107
+
108
+ describe 'resources' do
109
+ shared_examples_for 'generates routes for users' do
110
+ it { app.route_for(:users).should == '/users' }
111
+ it { app.route_for(:new_user).should == '/users/new' }
112
+ it { app.route_for(:user).should == '/users/:id' }
113
+ it { app.route_for(:edit_user).should == '/users/:id/edit' }
114
+ end
115
+
116
+ shared_examples_for 'generates routes for posts' do
117
+ it { app.route_for(:posts).should == '/posts' }
118
+ it { app.route_for(:new_post).should == '/posts/new' }
119
+ it { app.route_for(:post).should == '/posts/:id' }
120
+ it { app.route_for(:edit_post).should == '/posts/:id/edit' }
121
+ end
122
+
123
+ shared_examples_for 'generates routes for nested user posts collections' do
124
+ it { app.route_for(:user_posts).should == '/users/:user_id/posts' }
125
+ it { app.route_for(:new_user_post).should == '/users/:user_id/posts/new' }
126
+ end
127
+
128
+ shared_examples_for 'generates routes for nested user posts members' do
129
+ it { app.route_for(:user_post).should == '/users/:user_id/posts/:id' }
130
+ it { app.route_for(:edit_user_post).should == '/users/:user_id/posts/:id/edit' }
131
+ end
132
+
133
+ shared_examples_for 'generates routes for nested user posts' do
134
+ it_should_behave_like 'generates routes for nested user posts collections'
135
+ it_should_behave_like 'generates routes for nested user posts members'
136
+ end
137
+
138
+ shared_examples_for 'generates routes for shallow user posts' do
139
+ it_should_behave_like 'generates routes for nested user posts collections'
140
+ it { app.route_for(:post).should == '/posts/:id' }
141
+ it { app.route_for(:edit_post).should == '/posts/:id/edit' }
142
+ end
143
+
144
+ shared_examples_for 'generates routes for nested post comments collections' do
145
+ it { app.route_for(:post_comments).should == '/posts/:post_id/comments' }
146
+ it { app.route_for(:new_post_comment).should == '/posts/:post_id/comments/new' }
147
+ end
148
+
149
+ shared_examples_for 'generates routes for shallow post comments' do
150
+ it_should_behave_like 'generates routes for nested post comments collections'
151
+ it { app.route_for(:comment).should == '/comments/:id' }
152
+ it { app.route_for(:edit_comment).should == '/comments/:id/edit' }
153
+ end
154
+
155
+ describe 'basic' do
156
+ before :all do
157
+ app.resources :users, 'posts' do
158
+ # map :flag
159
+ end
160
+ end
161
+ it_should_behave_like 'generates routes for users'
162
+ it_should_behave_like 'generates routes for posts'
163
+ # it { app.route_for(:flag).should == '/flag' }
164
+ end
165
+
166
+ describe 'nested with block' do
167
+ before :all do
168
+ app.resources :users do
169
+ resources :posts
170
+ end
171
+ end
172
+ it_should_behave_like 'generates routes for users'
173
+ it_should_behave_like 'generates routes for nested user posts'
174
+ end
175
+
176
+ describe 'with namespace' do
177
+ before :all do
178
+ app.namespace :admin do
179
+ resources :users
180
+ end
181
+ end
182
+ it { app.route_for(:admin_users).should == '/admin/users' }
183
+ it { app.route_for(:new_admin_user).should == '/admin/users/new' }
184
+ it { app.route_for(:admin_user).should == '/admin/users/:id' }
185
+ it { app.route_for(:edit_admin_user).should == '/admin/users/:id/edit' }
186
+ end
187
+
188
+ describe 'nested with block and namespace' do
189
+ before :all do
190
+ app.resources :users do
191
+ namespace :admin do
192
+ resources :posts
193
+ end
194
+ end
195
+ end
196
+ it_should_behave_like 'generates routes for users'
197
+ it { app.route_for(:user_admin_posts).should == '/users/:user_id/admin/posts' }
198
+ it { app.route_for(:new_user_admin_post).should == '/users/:user_id/admin/posts/new' }
199
+ it { app.route_for(:user_admin_post).should == '/users/:user_id/admin/posts/:id' }
200
+ it { app.route_for(:edit_user_admin_post).should == '/users/:user_id/admin/posts/:id/edit' }
201
+ end
202
+
203
+ describe 'deep nested' do
204
+ before :all do
205
+ app.resources :users do
206
+ resources :posts do
207
+ resources :comments
208
+ end
209
+ end
210
+ end
211
+ it_should_behave_like 'generates routes for users'
212
+ it_should_behave_like 'generates routes for nested user posts'
213
+ describe 'exageration' do
214
+ it { app.route_for(:user_post_comments).should == '/users/:user_id/posts/:post_id/comments' }
215
+ it { app.route_for(:new_user_post_comment).should == '/users/:user_id/posts/:post_id/comments/new' }
216
+ it { app.route_for(:user_post_comment).should == '/users/:user_id/posts/:post_id/comments/:id' }
217
+ it { app.route_for(:edit_user_post_comment).should == '/users/:user_id/posts/:post_id/comments/:id/edit' }
218
+ end
219
+ end
220
+
221
+ describe 'shallow deep nested' do
222
+ before :all do
223
+ app.resources :users, :shallow => true do
224
+ resources :posts do
225
+ resources :comments
226
+ end
227
+ end
228
+ end
229
+ it_should_behave_like 'generates routes for users'
230
+ it_should_behave_like 'generates routes for shallow user posts'
231
+ it_should_behave_like 'generates routes for shallow post comments'
232
+ end
233
+
234
+ describe 'nested shallow' do
235
+ before :all do
236
+ app.resources :users, :shallow => true do
237
+ resources :posts
238
+ end
239
+ end
240
+ it_should_behave_like 'generates routes for users'
241
+ it_should_behave_like 'generates routes for shallow user posts'
242
+ end
243
+
244
+ describe 'hash nested' do
245
+ before :all do
246
+ app.resources :users => :posts
247
+ end
248
+ it_should_behave_like 'generates routes for users'
249
+ it_should_behave_like 'generates routes for nested user posts'
250
+ end
251
+
252
+ describe 'hash nested with block' do
253
+ before :all do
254
+ app.resources :users => :posts do
255
+ map :flag
256
+ end
257
+ end
258
+ it_should_behave_like 'generates routes for users'
259
+ it_should_behave_like 'generates routes for nested user posts'
260
+ it { app.route_for(:flag).should == '/flag' }
261
+ end
262
+
263
+ describe 'nested shallow with hash' do
264
+ before :all do
265
+ app.resources :users => :posts, :shallow => true
266
+ end
267
+ it_should_behave_like 'generates routes for users'
268
+ it_should_behave_like 'generates routes for shallow user posts'
269
+ end
270
+
271
+ describe 'deep nested with hash' do
272
+ before :all do
273
+ app.resources :users => {:posts => :comments}
274
+ end
275
+ it_should_behave_like 'generates routes for users'
276
+ it_should_behave_like 'generates routes for nested user posts'
277
+ describe 'exageration' do
278
+ it { app.route_for(:user_post_comments).should == '/users/:user_id/posts/:post_id/comments' }
279
+ it { app.route_for(:new_user_post_comment).should == '/users/:user_id/posts/:post_id/comments/new' }
280
+ it { app.route_for(:user_post_comment).should == '/users/:user_id/posts/:post_id/comments/:id' }
281
+ it { app.route_for(:edit_user_post_comment).should == '/users/:user_id/posts/:post_id/comments/:id/edit' }
282
+ end
283
+ end
284
+
285
+ describe 'deep nested shallow with hash' do
286
+ before :all do
287
+ app.resources :users => {:posts => :comments}, :shallow => true
288
+ end
289
+ it_should_behave_like 'generates routes for users'
290
+ it_should_behave_like 'generates routes for shallow user posts'
291
+ it { app.route_for(:post_comments).should == '/posts/:post_id/comments' }
292
+ it { app.route_for(:new_post_comment).should == '/posts/:post_id/comments/new' }
293
+ it { app.route_for(:comment).should == '/comments/:id' }
294
+ it { app.route_for(:edit_comment).should == '/comments/:id/edit' }
295
+ end
296
+
297
+ describe 'nested with array' do
298
+ before :all do
299
+ app.resources :users => [:posts, :comments]
300
+ end
301
+ it { app.route_for(:users).should == '/users' }
302
+ it { app.route_for(:user_comments).should == '/users/:user_id/comments' }
303
+ it { app.route_for(:user_posts).should == '/users/:user_id/posts' }
304
+ end
305
+ end
306
+
307
+ describe 'single resource' do
308
+ before :all do
309
+ app.resource :user => :profile
310
+ end
311
+ it { app.route_for(:user).should == '/user' }
312
+ it { app.route_for(:new_user).should == '/user/new' }
313
+ it { app.route_for(:edit_user).should == '/user/edit' }
314
+ it { app.route_for(:user_profile).should == '/user/profile' }
315
+ it { app.route_for(:new_user_profile).should == '/user/profile/new' }
316
+ it { app.route_for(:edit_user_profile).should == '/user/profile/edit' }
317
+ end
318
+
319
+ describe 'finding route for scope' do
320
+ before :all do
321
+ @scope = Sinatra::Trails::Scope.new(app, :admin)
322
+ @scope.generate_routes!{ map(:index) }
323
+ end
324
+
325
+ it { @scope.route_for(:admin_index).should_not be_nil }
326
+ it { @scope.route_for(:index).should_not be_nil }
327
+ it "should find resources index without full name"
328
+ end
329
+
330
+ describe 'finding route for resources' do
331
+ before :all do
332
+ @scope = Sinatra::Trails::Resources.new(app, :users, [], {})
333
+ @scope.generate_routes!
334
+ end
335
+ it { @scope.route_for(:users).should_not be_nil }
336
+ it { @scope.route_for(:new_user).should_not be_nil }
337
+ it { @scope.route_for(:edit_user).should_not be_nil }
338
+ it { @scope.route_for(:user).should_not be_nil }
339
+ end
340
+
341
+ describe 'finding route for resources' do
342
+ before :all do
343
+ @scope = Sinatra::Trails::Resources.new(app, :users, [], {})
344
+ @scope.generate_routes! do
345
+ resources :posts
346
+ end
347
+ end
348
+ it { @scope.route_for(:user_posts).should_not be_nil }
349
+ it { @scope.route_for(:new_user_post).should_not be_nil }
350
+ it { @scope.route_for(:edit_user_post).should_not be_nil }
351
+ it { @scope.route_for(:user_post).should_not be_nil }
352
+ end
353
+
354
+ describe 'sinatra integration' do
355
+ describe 'delegation to sinatra and helpers' do
356
+ before :all do
357
+ app.map(:home) { get(home){ path_for(:home) } }
358
+ app.instance_eval { get(map(:about)){ url_for(:about) } }
359
+ end
360
+
361
+ it "should get path for route" do
362
+ get '/home'
363
+ last_response.body.should == '/home'
364
+ end
365
+
366
+ it "should get url for route" do
367
+ get '/about'
368
+ last_response.body.should == 'http://example.org/about'
369
+ end
370
+ end
371
+
372
+ describe 'using route as scope' do
373
+ before :all do
374
+ app.resources(:users => :posts, :shallow => true) do
375
+ users do
376
+ get(member(:aprove)) { path_for(:aprove_user, params[:id]) }
377
+ end
378
+
379
+ user_posts do
380
+ get(member(:aprove)) { path_for(:aprove_post, params[:id]) }
381
+ end
382
+ end
383
+ end
384
+
385
+ it 'should define action for users member' do
386
+ get '/users/1/aprove'
387
+ last_response.body.should == '/users/1/aprove'
388
+ end
389
+
390
+ it 'should define action for user_posts member' do
391
+ get '/posts/1/aprove'
392
+ last_response.body.should == '/posts/1/aprove'
393
+ end
394
+ end
395
+
396
+ describe 'before without args' do
397
+ before :all do
398
+ app.instance_eval do
399
+ namespace(:admin) do
400
+ before { @admin = true }
401
+ get map(:index, :to => '/') do
402
+ @admin.to_s
403
+ end
404
+ end
405
+
406
+ namespace(:not_admin) do
407
+ get map(:index, :to => '/') do
408
+ @admin.to_s
409
+ end
410
+ end
411
+ end
412
+ end
413
+
414
+ it 'should set before filter within scope' do
415
+ get '/admin'
416
+ last_response.body.should == 'true'
417
+ end
418
+
419
+ it 'should not set before filter outside scope' do
420
+ get '/not_admin'
421
+ last_response.body.should == ''
422
+ end
423
+ end
424
+
425
+ describe 'before passing routes' do
426
+ before :all do
427
+ app.instance_eval do
428
+ namespace(:admin) do
429
+ get map(:index, :to => '/') do
430
+ @auth.to_s
431
+ end
432
+
433
+ get map(:sign_in, :to => '/sign_in') do
434
+ @auth.to_s
435
+ end
436
+
437
+ get map(:sign_up, :to => '/sign_up') do
438
+ @auth.to_s
439
+ end
440
+
441
+ before(admin_sign_in, admin_sign_up) { @auth = true }
442
+ end
443
+ end
444
+ end
445
+
446
+ it 'should set before filter for passed routes' do
447
+ get '/admin/sign_in'
448
+ last_response.body.should == 'true'
449
+ get '/admin/sign_up'
450
+ last_response.body.should == 'true'
451
+ end
452
+
453
+ it 'should not set before filter for not passed routes' do
454
+ get '/admin'
455
+ last_response.body.should == ''
456
+ end
457
+ end
458
+
459
+ describe 'before filter lazy match passing symbols' do
460
+ before :all do
461
+ app.instance_eval do
462
+ namespace(:admin) do
463
+ before(:admin_sign_in, :admin_sign_up) { @auth = true }
464
+
465
+ get map(:index, :to => '/') do
466
+ @auth.to_s
467
+ end
468
+
469
+ get map(:sign_in, :to => '/sign_in') do
470
+ @auth.to_s
471
+ end
472
+
473
+ get map(:sign_up, :to => '/sign_up') do
474
+ @auth.to_s
475
+ end
476
+ end
477
+ end
478
+ end
479
+
480
+ it 'should set before filter for passed routes' do
481
+ get '/admin/sign_in'
482
+ last_response.body.should == 'true'
483
+ get '/admin/sign_up'
484
+ last_response.body.should == 'true'
485
+ end
486
+
487
+ it 'should not set before filter for not passed routes' do
488
+ get '/admin'
489
+ last_response.body.should == ''
490
+ end
491
+ end
492
+
493
+ describe 'having access to resource name' do
494
+ before :all do
495
+ app.resources(:users => :posts) do
496
+ get(users){ params[:resource].to_s }
497
+ get(user_posts){ params[:resource].to_s }
498
+ end
499
+ end
500
+
501
+ it 'should access resource name for users' do
502
+ get '/users'
503
+ last_response.body.should == 'user'
504
+ end
505
+
506
+ it 'should access resource name for nested posts' do
507
+ get '/users/1/posts'
508
+ last_response.body.should == 'post'
509
+ end
510
+ end
511
+
512
+ describe 'having access to namespace and action' do
513
+ before :all do
514
+ app.namespace(:admin) do
515
+ get(map(:index, :to => '/')){ params[:namespace].to_s }
516
+ get(map(:sign_in)){ params[:action].to_s }
517
+ end
518
+ end
519
+
520
+ it 'should access namespace' do
521
+ get '/admin'
522
+ last_response.body.should == 'admin'
523
+ end
524
+
525
+ it 'should access action' do
526
+ get '/admin/sign_in'
527
+ last_response.body.should == 'sign_in'
528
+ end
529
+ end
530
+
531
+ describe 'finding the right route for resources' do
532
+ it 'should find the right route' do
533
+ app.resources(:users) do
534
+ users.to_route.should == '/users'
535
+ new_user.to_route.should == '/users/new'
536
+ user.to_route.should == '/users/:id'
537
+ edit_user.to_route.should == '/users/:id/edit'
538
+ end
539
+ end
540
+ end
541
+
542
+ describe 'having access to action for resources' do
543
+ before :all do
544
+ app.resources(:users) do
545
+ get(users){ params[:action].to_s }
546
+ get(new_user){ params[:action].to_s }
547
+ get(user){ params[:action].to_s }
548
+ get(edit_user){ params[:action].to_s }
549
+ end
550
+ end
551
+
552
+ it 'should set index as action' do
553
+ get '/users'
554
+ last_response.body.should == 'index'
555
+ end
556
+
557
+ it 'should set index as show' do
558
+ get '/users/1'
559
+ last_response.body.should == 'show'
560
+ end
561
+
562
+ it 'should set index as new' do
563
+ get '/users/new'
564
+ last_response.body.should == 'new'
565
+ end
566
+
567
+ it 'should set index as edit' do
568
+ get '/users/1/edit'
569
+ last_response.body.should == 'edit'
570
+ end
571
+ end
572
+ end
573
+
574
+ describe 'accessing routes from outside the app by module inclusion' do
575
+ let(:other_app) do
576
+ app = Class.new(Sinatra::Base)
577
+ app.register Sinatra::Trails
578
+ app.set :environment, :test
579
+ end
580
+
581
+ let(:klass) { Class.new }
582
+
583
+ before :all do
584
+ app.map :heaven
585
+ other_app.map :hell
586
+ klass.send :include, app::Routes
587
+ klass.send :include, other_app::Routes
588
+ @obj = klass.new
589
+ end
590
+
591
+ it { @obj.path_for(:heaven).should == '/heaven' }
592
+ it { @obj.path_for(:hell).should == '/hell' }
593
+ it { lambda{ @obj.path_for(:purgatory) }.should raise_error Sinatra::Trails::RouteNotDefined }
594
+ end
595
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ require 'rack/test'
4
+
5
+ $:.unshift File.join(File.dirname( __FILE__), '..', 'lib')
6
+
7
+ require 'sinatra/trails'
8
+
9
+ RSpec::Matchers.define :match_route do |expected|
10
+ match do |actual|
11
+ expected.match actual
12
+ end
13
+
14
+ description do
15
+ "should match route #{expected}"
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "sinatra/trails/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "sinatra-trails"
7
+ s.version = Sinatra::Trails::VERSION
8
+ s.authors = ["Macario"]
9
+ s.email = ["macarui@gmail.com"]
10
+ s.homepage = "http://github.com/maca/trails"
11
+ s.summary = %q{A named routes Sinatra DSL inspired by Rails routing}
12
+ s.description = %q{A named routes Sinatra DSL inspired by Rails routing}
13
+
14
+ s.rubyforge_project = "sinatra-trails"
15
+ s.add_dependency 'sinatra'
16
+ s.add_dependency 'i18n'
17
+ s.add_dependency 'activesupport', '>= 3.0'
18
+
19
+ s.add_development_dependency 'rake'
20
+ s.add_development_dependency 'rspec'
21
+ s.add_development_dependency 'rack-test'
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-trails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Macario
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: &70283828463680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70283828463680
25
+ - !ruby/object:Gem::Dependency
26
+ name: i18n
27
+ requirement: &70283828463260 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70283828463260
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ requirement: &70283828486540 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70283828486540
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &70283828486120 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70283828486120
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &70283828485660 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70283828485660
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: &70283828485240 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70283828485240
80
+ description: A named routes Sinatra DSL inspired by Rails routing
81
+ email:
82
+ - macarui@gmail.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - .rspec
89
+ - Gemfile
90
+ - Rakefile
91
+ - lib/sinatra/trails.rb
92
+ - lib/sinatra/trails/version.rb
93
+ - spec/route_generation_spec.rb
94
+ - spec/spec_helper.rb
95
+ - trails.gemspec
96
+ homepage: http://github.com/maca/trails
97
+ licenses: []
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project: sinatra-trails
116
+ rubygems_version: 1.8.8
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: A named routes Sinatra DSL inspired by Rails routing
120
+ test_files:
121
+ - spec/route_generation_spec.rb
122
+ - spec/spec_helper.rb