sinatra-trails 0.0.1

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