scorched 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,11 +6,12 @@ The only data exchanged between controllers is the Rack environment hash. Using
6
6
 
7
7
  The Rack idiom is to namespace your keys, typically with your project name, using dots as delimiters. You can further group your keys using more dot-delimited namespaces. Perhaps an example is due:
8
8
 
9
- # ruby
10
- before user_agent: /MSIE|Windows/ do
11
- env['myapp.dangerous_request'] = true
12
- end
13
-
14
- get '/' do
15
- "Welcome to #{env['myapp.settings.site_name']}!"
16
- end
9
+ ```ruby
10
+ before user_agent: /MSIE|Windows/ do
11
+ env['myapp.dangerous_request'] = true
12
+ end
13
+
14
+ get '/' do
15
+ "Welcome to #{env['myapp.settings.site_name']}!"
16
+ end
17
+ ```
@@ -6,37 +6,38 @@ Effortless REST
6
6
  ---------------
7
7
  An DRY way to serve multiple content-types:
8
8
 
9
- # ruby
10
- class App < Scorched::Controller
11
- def view(view = nil)
12
- view ? env['app.view'] = view : env['app.view']
13
- end
14
-
15
- after do
16
- data = response.body.join('')
17
- response['Content-type'] = 'text/html'
18
- if check_condition?(:media_type, 'text/html')
19
- response.body = render(view, locals: {data: data})
20
- elsif check_condition?(:media_type, 'application/json')
21
- response['Content-type'] = 'application/json'
22
- response.body = data.to_json
23
- elsif check_condition?(:media_type, 'application/pdf')
24
- response['Content-type'] = 'text/plain'
25
- response.status = 406
26
- response.body = 'PDF rendering service currently unavailable.'
27
- else
28
- response.body = render(view, locals: {data: data})
29
- end
30
- end
31
-
32
- get '/' do
33
- view :index
34
- [
35
- {title: 'Sweet Purple Unicorns', date: '08/03/2013'},
36
- {title: 'Mellow Grass Men', date: '21/03/2013'}
37
- ]
38
- end
9
+ ```ruby
10
+ class App < Scorched::Controller
11
+ def view(view = nil)
12
+ view ? env['app.view'] = view : env['app.view']
13
+ end
14
+
15
+ after do
16
+ data = response.body.join('')
17
+ response['Content-type'] = 'text/html'
18
+ if check_condition?(:media_type, 'text/html')
19
+ response.body = render(view, locals: {data: data})
20
+ elsif check_condition?(:media_type, 'application/json')
21
+ response['Content-type'] = 'application/json'
22
+ response.body = data.to_json
23
+ elsif check_condition?(:media_type, 'application/pdf')
24
+ response['Content-type'] = 'text/plain'
25
+ response.status = 406
26
+ response.body = 'PDF rendering service currently unavailable.'
27
+ else
28
+ response.body = render(view, locals: {data: data})
39
29
  end
30
+ end
31
+
32
+ get '/' do
33
+ view :index
34
+ [
35
+ {title: 'Sweet Purple Unicorns', date: '08/03/2013'},
36
+ {title: 'Mellow Grass Men', date: '21/03/2013'}
37
+ ]
38
+ end
39
+ end
40
+ ```
40
41
 
41
42
 
42
43
  Authentication and Permissions
@@ -7,7 +7,7 @@ A sub-set of the most popular application reloaders are detailed below.
7
7
 
8
8
  Rerun
9
9
  -----
10
- Rerun is a general purpose gem (``gem install rerun``) for watching files and rerunning some command when any of those files change. The following example uses rerun to watch for changes to ".rb" and ".ru" files anywhere in the current directory tree. If a change is made, any previous instance of ``rackup`` are shutdown and restarted.
10
+ Rerun is a general purpose gem (``gem install rerun``) for watching files and rerunning some command when any of those files change. The following example uses rerun to watch for changes to ".rb" and ".ru" files anywhere in the current directory tree. If a change is made, any previous instance of `rackup` are shutdown and restarted.
11
11
 
12
12
  rerun -p "**/*.{rb,ru}" rackup
13
13
 
@@ -21,4 +21,4 @@ Phusion Passenger
21
21
  -----------------
22
22
  The popular Apache/Nginx module for hosting Rack applications, Phusion Passenger, can be configured to reload your application on every request much like Shotgun does. Passenger can be faster that Shogun, as you can let the underlying web server (Apache or Nginx) to take care of the static file serving, which avoids having to reload your application for every static file request.
23
23
 
24
- To have Passenger automatically restart your application on every request, create a file named ``tmp/always_restart.txt`` in the root of your application directory. Passenger will automatically detect this file and behave as intended.
24
+ To have Passenger automatically restart your application on every request, create a file named `tmp/always_restart.txt` in the root of your application directory. Passenger will automatically detect this file and behave as intended.
@@ -1,6 +1,6 @@
1
1
  Running Unit Tests
2
2
  ==================
3
3
 
4
- If doing any development on Scorched, such as if you want to fork it or contribute patches to it, you will probably want to run the suite of tests that accompany it. The few dependancies required for running the Scorched unit tests are installed either when you install the Scorched gem, or by running ``bundle`` in the root of the Scorched source tree.
4
+ If doing any development on Scorched, such as if you want to fork it or contribute patches to it, you will probably want to run the suite of tests that accompany it. The few dependancies required for running the Scorched unit tests are installed either when you install the Scorched gem, or by running `bundle` in the root of the Scorched source tree.
5
5
 
6
- All unit tests have been written using RSpec. To run the tests, ``cd`` into the root of the Scorched source tree from a terminal, and run ``rspec``.
6
+ All unit tests have been written using RSpec. To run the tests, `cd` into the root of the Scorched source tree from a terminal, and run `rspec`.
@@ -11,13 +11,13 @@ class MediaTypesExample < Scorched::Controller
11
11
  HTML
12
12
  end
13
13
 
14
- controller pattern: '/*' do
14
+ controller '/*' do
15
15
  get '/*' do |*captures|
16
16
  request.breadcrumb.inspect
17
17
  end
18
18
  end
19
19
 
20
- get '/,', media_type: 'application/json' do
20
+ get '/', media_type: 'application/json' do
21
21
  {name: 'John Falkon', age: 39, occupation: 'Carpet Cleaner'}.to_json
22
22
  end
23
23
 
File without changes
@@ -12,7 +12,8 @@ module Scorched
12
12
  :strip_trailing_slash => :redirect, # :redirect => Strips and redirects URL ending in forward slash, :ignore => internally ignores trailing slash, false => does nothing.
13
13
  :static_dir => 'public', # The directory Scorched should serve static files from. Set to false if web server or anything else is serving static files.
14
14
  :logger => nil,
15
- :show_exceptions => false
15
+ :show_exceptions => false,
16
+ :auto_pass => false, # Automatically _pass_ request back to outer controller if no route matches.
16
17
  }
17
18
 
18
19
  render_defaults << {
@@ -114,15 +115,16 @@ module Scorched
114
115
  # Creates a new controller as a sub-class of self (by default), mapping it to self using the provided mapping
115
116
  # hash if one is provided. Returns the new anonymous controller class.
116
117
  #
117
- # Takes two optional arguments and a block: a parent class from which the generated controller class inherits
118
- # from, a mapping hash to automatically map the new controller, and of course a block which defines the
118
+ # Takes three optional arguments and a block: a pattern, a parent class from which the generated controller class
119
+ # inherits from, a mapping hash for setting conditions and so on, and of course a block which defines the
119
120
  # controller class.
120
121
  #
121
122
  # It's worth noting, however obvious, that the resulting class will only be a controller if the parent class is
122
123
  # (or inherits from) a Scorched::Controller.
123
- def controller(parent_class = self, **mapping, &block)
124
+ def controller(pattern = '/', parent_class = self, **mapping, &block)
124
125
  c = Class.new(parent_class, &block)
125
- self << {pattern: '/', target: c}.merge(mapping)
126
+ c.config[:auto_pass] = true if parent_class < Scorched::Controller
127
+ self << {pattern: pattern, target: c}.merge(mapping)
126
128
  c
127
129
  end
128
130
 
@@ -199,28 +201,31 @@ module Scorched
199
201
  (f[:args].empty? || f[:args].any? { |type| e.is_a?(type) }) && check_conditions?(f[:conditions]) && instance_exec(e, &f[:proc])
200
202
  end
201
203
  end
202
-
203
- match = matches(true).first
204
+
204
205
  begin
205
206
  catch(:halt) do
206
207
  if config[:strip_trailing_slash] == :redirect && request.path =~ %r{./$}
207
208
  redirect(request.path.chomp('/'))
208
209
  end
209
210
 
211
+ all_matches = matches
212
+ if all_matches.empty?
213
+ pass if config[:auto_pass]
214
+ response.status = 404
215
+ end
216
+
210
217
  run_filters(:before)
211
- if match
212
- request.breadcrumb << match
213
- # Proc's are executed in the context of this controller instance.
214
- target = match[:mapping][:target]
215
- begin
216
- catch(:halt) do
218
+ begin
219
+ all_matches.each do |match|
220
+ request.breadcrumb << match
221
+ processed = catch(:pass) {
222
+ target = match[:mapping][:target]
217
223
  response.merge! (Proc === target) ? instance_exec(request.env, &target) : target.call(request.env)
218
- end
219
- rescue => inner_error
220
- rescue_block.call(inner_error)
224
+ }
225
+ processed ? break : request.breadcrumb.pop
221
226
  end
222
- else
223
- response.status = 404
227
+ rescue => inner_error
228
+ rescue_block.call(inner_error)
224
229
  end
225
230
  run_filters(:after)
226
231
  end
@@ -281,6 +286,10 @@ module Scorched
281
286
  throw :halt
282
287
  end
283
288
 
289
+ def pass
290
+ throw :pass
291
+ end
292
+
284
293
  # Convenience method for accessing Rack request.
285
294
  def request
286
295
  env['scorched.request']
File without changes
File without changes
File without changes
@@ -1,3 +1,3 @@
1
1
  module Scorched
2
- VERSION = '0.7'
2
+ VERSION = '0.8'
3
3
  end
@@ -11,6 +11,8 @@ Gem::Specification.new 'scorched', Scorched::VERSION do |s|
11
11
  s.test_files = Dir.glob('spec/**/*_spec.rb')
12
12
  s.rdoc_options = %w[--line-numbers --inline-source --title Scorched --encoding=UTF-8]
13
13
 
14
+ s.required_ruby_version = '>= 2.0.0'
15
+
14
16
  s.add_dependency 'rack', '~> 1.4'
15
17
  s.add_dependency 'rack-accept', '~> 0.4'
16
18
  s.add_dependency 'tilt', '~> 1.3'
File without changes
@@ -217,34 +217,57 @@ module Scorched
217
217
  end
218
218
 
219
219
  describe "sub-controllers" do
220
- it "can be given no arguments" do
221
- app.controller do
222
- get('/') { 'hello' }
223
- end
224
- response = rt.get('/')
225
- response.status.should == 200
226
- response.body.should == 'hello'
220
+ it "should ignore the already matched portions of the path" do
221
+ app << {pattern: '/article', target: Class.new(Scorched::Controller) do
222
+ get('/*') { |title| title }
223
+ end}
224
+ rt.get('/article/hello-world').body.should == 'hello-world'
227
225
  end
228
226
 
229
- it "can take mapping options" do
230
- app.controller priority: -1, conditions: {methods: 'POST'} do
231
- route('/') { 'ok' }
227
+ describe "controller helper" do
228
+ it "can be given no arguments" do
229
+ app.controller do
230
+ get('/') { 'hello' }
231
+ end
232
+ response = rt.get('/')
233
+ response.status.should == 200
234
+ response.body.should == 'hello'
232
235
  end
233
- app.mappings.first[:priority].should == -1
234
- rt.get('/').status.should == 404
235
- rt.post('/').body.should == 'ok'
236
- end
237
236
 
238
- it "should ignore the already matched portions of the path" do
239
- app.controller pattern: '/article' do
240
- get('/*') { |title| title }
237
+ it "can be given a pattern" do
238
+ app.controller '/dog' do
239
+ get('/') { 'roof' }
240
+ end
241
+ response = rt.get('/dog')
242
+ response.status.should == 200
243
+ response.body.should == 'roof'
241
244
  end
242
- rt.get('/article/hello-world').body.should == 'hello-world'
243
- end
244
245
 
245
- it "inherits from parent class, or any other class" do
246
- app.controller.superclass.should == app
247
- app.controller(String).superclass.should == String
246
+ it "inherits from parent class, or any other class" do
247
+ app.controller.superclass.should == app
248
+ app.controller('/', String).superclass.should == String
249
+ end
250
+
251
+ it "can take mapping options" do
252
+ app.controller priority: -1, conditions: {methods: 'POST'} do
253
+ route('/') { 'ok' }
254
+ end
255
+ app.mappings.first[:priority].should == -1
256
+ rt.get('/').status.should == 404
257
+ rt.post('/').body.should == 'ok'
258
+ end
259
+
260
+ it "automatically passes to the outer controller when no match" do
261
+ filters_run = 0
262
+ app.controller do
263
+ before { filters_run += 1 }
264
+ get('/sub') { 'goodbye' }
265
+ after { filters_run += 1 }
266
+ end
267
+ app.get('/') { 'hello' }
268
+ rt.get('/').body.should == 'hello'
269
+ filters_run.should == 0
270
+ end
248
271
  end
249
272
  end
250
273
 
@@ -311,7 +334,10 @@ module Scorched
311
334
  example "before filters run from outermost to inner" do
312
335
  order = []
313
336
  app.before { order << :outer }
314
- app.controller { before { order << :inner } }
337
+ app.controller do
338
+ before { order << :inner }
339
+ get('/') { }
340
+ end
315
341
  rt.get('/')
316
342
  order.should == [:outer, :inner]
317
343
  end
@@ -319,7 +345,10 @@ module Scorched
319
345
  example "after filters run from innermost to outermost" do
320
346
  order = []
321
347
  app.after { order << :outer }
322
- app.controller { after { order << :inner } }
348
+ app.controller do
349
+ get('/') { }
350
+ after { order << :inner }
351
+ end
323
352
  rt.get('/')
324
353
  order.should == [:inner, :outer]
325
354
  end
@@ -419,7 +448,7 @@ module Scorched
419
448
  get '/'do
420
449
  request.env['scorched.simple_counter']
421
450
  end
422
- controller pattern: '/sub_controller' do
451
+ controller '/sub_controller' do
423
452
  get '/' do
424
453
  request.env['scorched.simple_counter']
425
454
  end
@@ -452,10 +481,10 @@ module Scorched
452
481
  rt.get('/').status.should == 401
453
482
  end
454
483
 
455
- it "still processes filters" do
484
+ it "skips processing filters" do
456
485
  app.after { response.status = 403 }
457
486
  app.get('/') { halt }
458
- rt.get('/').status.should == 403
487
+ rt.get('/').status.should == 200
459
488
  end
460
489
 
461
490
  it "short circuits filters if halted within filter" do
@@ -465,6 +494,36 @@ module Scorched
465
494
  end
466
495
  end
467
496
 
497
+ describe "passing" do
498
+ it "invokes the next match" do
499
+ app.get('/') { response.body << 'hello'; pass }
500
+ app.get('/') { response.body << ' there'; pass }
501
+ app.get('/') { response.body << ' sir' }
502
+ app.get('/') { response.body << '!' } # Shouldn't be hit
503
+ rt.get('/').body.should == 'hello there sir'
504
+ end
505
+
506
+ it "invokes the next match in parent controller if passed from filter" do
507
+ app.controller '/sub' do
508
+ get('/') { }
509
+ after do
510
+ response.body << 'hello'
511
+ pass
512
+ end
513
+ end
514
+ app.get('/sub') { response.body << ' there' }
515
+ rt.get('/sub').body.should == 'hello there'
516
+ end
517
+
518
+ it "passing within filter of root controller results in uncaught symbol" do
519
+ app.before { pass }
520
+ expect {
521
+ app.get('/') { }
522
+ rt.get('/')
523
+ }.to raise_error(ArgumentError)
524
+ end
525
+ end
526
+
468
527
  describe "configuration" do
469
528
  describe "strip_trailing_slash" do
470
529
  it "can be set to strip trailing slash and redirect" do
@@ -525,6 +584,27 @@ module Scorched
525
584
  }.to raise_error(RuntimeError)
526
585
  end
527
586
  end
587
+
588
+ describe "auto_pass" do
589
+ it "if no match, passes to the outer controller without running any filters" do
590
+ sub = Class.new(Scorched::Controller) do
591
+ config[:auto_pass] = true
592
+ before { response.status = 600 }
593
+ get('/hello') { 'hello' }
594
+ after { response.status = 600 }
595
+ end
596
+ app << {pattern: '/', target: sub}
597
+ app.get('/') { 'ok' }
598
+ rt.get('/').body.should == 'ok'
599
+ rt.get('/').status.should == 200
600
+ rt.get('/hello').body.should == 'hello'
601
+ rt.get('/hello').status.should == 600
602
+
603
+ sub.config[:auto_pass] = false
604
+ rt.get('/').body.should == ''
605
+ rt.get('/').status.should == 600
606
+ end
607
+ end
528
608
  end
529
609
 
530
610
  describe "sessions" do
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes