scorched 1.1.0.pre → 1.1.0
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.
- checksums.yaml +4 -4
- data/CHANGES.md +13 -0
- data/lib/scorched/controller.rb +72 -28
- data/lib/scorched/version.rb +1 -1
- data/scorched.gemspec +1 -0
- data/spec/controller_spec.rb +96 -1
- metadata +17 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a838c96acf81063ddc76cc2a26fe3497b7fd83de1b095ba4d9cef042835b9ed
|
|
4
|
+
data.tar.gz: 0de14593266fcf5df232a6f56cc89ca4de8d9b7de9d8a4df2fbc173974d55aca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a76db1e57fc1a1a62428e3e3e728ecc4eeee9d504579e5a7894c4e07ed0d819306f0ea2b73725e605e129ee188d864e1cbaf145c2e7eda6e1aea39e4f51ef930
|
|
7
|
+
data.tar.gz: 7b7185d2a622ac50111c0e3fbd5104e962ae2afc26c9058d07493b1a3666111bbf2b274c8afef1967697f319a268f1ff97d94511914509de4ed1bd4fd008b872
|
data/CHANGES.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
### v1.1.0
|
|
5
|
+
* Added `around` filters to allow wrapping application logic around a request. `around` filters wrap around `before/after` filters. Ideal for `begin...ensure...end` where you need to ensure something happens before the request processing ends. For example:
|
|
6
|
+
```
|
|
7
|
+
around do |cont|
|
|
8
|
+
begin
|
|
9
|
+
cont.call
|
|
10
|
+
ensure
|
|
11
|
+
# Ensure something important happens here
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
* Compatible with Ruby 4.0
|
|
16
|
+
|
|
4
17
|
### v1.1.0.pre
|
|
5
18
|
* Updated dependancies
|
|
6
19
|
* Now compatible with (and requires) Rack 3.x
|
data/lib/scorched/controller.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Scorched
|
|
|
10
10
|
include Scorched::Options('symbol_matchers')
|
|
11
11
|
include Scorched::Collection('middleware')
|
|
12
12
|
include Scorched::Collection('before_filters')
|
|
13
|
+
include Scorched::Collection('around_filters')
|
|
13
14
|
include Scorched::Collection('after_filters', true)
|
|
14
15
|
include Scorched::Collection('error_filters')
|
|
15
16
|
|
|
@@ -108,7 +109,7 @@ module Scorched
|
|
|
108
109
|
end
|
|
109
110
|
|
|
110
111
|
def filters
|
|
111
|
-
@filters ||= {before: before_filters, after: after_filters, error: error_filters}
|
|
112
|
+
@filters ||= {before: before_filters, around: around_filters, after: after_filters, error: error_filters}
|
|
112
113
|
end
|
|
113
114
|
|
|
114
115
|
def call(env)
|
|
@@ -213,6 +214,16 @@ module Scorched
|
|
|
213
214
|
filter(:after, force: force, conditions: conditions, &block)
|
|
214
215
|
end
|
|
215
216
|
|
|
217
|
+
# Syntactic sugar for defining an around filter.
|
|
218
|
+
# The block is passed a single argument - a proc representing the rest of the filter chain (including the
|
|
219
|
+
# eventual dispatch to before/matching/after processing). The block must call it (e.g. `cont.call`) for
|
|
220
|
+
# request handling to proceed; if it doesn't, the wrapped chain simply never runs.
|
|
221
|
+
# Around filters defined earlier (including those defined in superclasses) wrap those defined later, with the
|
|
222
|
+
# innermost proc being the actual before/dispatch/after cycle.
|
|
223
|
+
def around(**conditions, &block)
|
|
224
|
+
filter(:around, conditions: conditions, &block)
|
|
225
|
+
end
|
|
226
|
+
|
|
216
227
|
# Syntactic sugar for defining an error filter.
|
|
217
228
|
# Takes one or more optional exception classes for which this error filter should handle. Handles all exceptions
|
|
218
229
|
# by default.
|
|
@@ -286,26 +297,29 @@ module Scorched
|
|
|
286
297
|
end
|
|
287
298
|
end
|
|
288
299
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
300
|
+
if config[:strip_trailing_slash] == :redirect && request.path =~ %r{[^/]/+$}
|
|
301
|
+
query_string = request.query_string.empty? ? '' : '?' << request.query_string
|
|
302
|
+
redirect(request.path.chomp('/') + query_string, status: 307, halt: false)
|
|
303
|
+
return response.finish
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
run_around_filters do
|
|
307
|
+
begin
|
|
308
|
+
pass if config[:auto_pass] && eligable_matches.empty?
|
|
309
|
+
|
|
310
|
+
if run_ba_filters(:before)
|
|
311
|
+
catch(:halt) {
|
|
312
|
+
begin
|
|
313
|
+
try_matches
|
|
314
|
+
rescue => inner_error
|
|
315
|
+
rescue_block.call(inner_error)
|
|
316
|
+
end
|
|
317
|
+
}
|
|
318
|
+
end
|
|
319
|
+
run_ba_filters(:after)
|
|
320
|
+
rescue => outer_error
|
|
321
|
+
outer_error == inner_error ? raise : catch(:halt) { rescue_block.call(outer_error) }
|
|
305
322
|
end
|
|
306
|
-
run_filters(:after)
|
|
307
|
-
rescue => outer_error
|
|
308
|
-
outer_error == inner_error ? raise : catch(:halt) { rescue_block.call(outer_error) }
|
|
309
323
|
end
|
|
310
324
|
response.finish
|
|
311
325
|
end
|
|
@@ -328,6 +342,9 @@ module Scorched
|
|
|
328
342
|
# Overriding this method provides the opportunity for one to have more control over how mapping targets are invoked.
|
|
329
343
|
def dispatch(match)
|
|
330
344
|
@_dispatched = true
|
|
345
|
+
if String === match.mapping[:target] || Symbol === match.mapping[:target]
|
|
346
|
+
match.mapping[:target] = Object.const_get(match.mapping[:target]) # Resolve symbol to constant, and replace target to avoid future lookups
|
|
347
|
+
end
|
|
331
348
|
target = match.mapping[:target]
|
|
332
349
|
response.merge! begin
|
|
333
350
|
if Proc === target
|
|
@@ -595,21 +612,48 @@ module Scorched
|
|
|
595
612
|
|
|
596
613
|
private
|
|
597
614
|
|
|
598
|
-
#
|
|
599
|
-
|
|
615
|
+
# Wraps the given block with the chain of around filters, outermost first, and calls it. Each filter is passed
|
|
616
|
+
# a proc representing the rest of the chain (the next around filter, or the wrapped block if there are no more),
|
|
617
|
+
# and is responsible for calling it to continue processing the request.
|
|
618
|
+
# As with before/after filters, a filter inherited by both an outer and inner controller (e.g. via `controller`
|
|
619
|
+
# sub-mapping) only runs once per request, tracked via the shared `env`.
|
|
620
|
+
def run_around_filters(&core)
|
|
621
|
+
tracker = filter_tracker(:around)
|
|
622
|
+
chain = filters[:around].to_a.reverse.reduce(core) do |inner, f|
|
|
623
|
+
proc do
|
|
624
|
+
if tracker.include?(f) || check_for_failed_condition(f[:conditions])
|
|
625
|
+
inner.call
|
|
626
|
+
else
|
|
627
|
+
tracker << f
|
|
628
|
+
instance_exec(inner, &f[:proc])
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
catch(:halt) { chain.call }
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# Returns false if any of the before/after filters halted the request. True otherwise.
|
|
636
|
+
def run_ba_filters(type)
|
|
600
637
|
halted = false
|
|
601
|
-
tracker =
|
|
602
|
-
filters[type].reject { |f| tracker
|
|
638
|
+
tracker = filter_tracker(type)
|
|
639
|
+
filters[type].reject { |f| tracker.include?(f) }.each do |f|
|
|
603
640
|
unless check_for_failed_condition(f[:conditions]) || (halted && !f[:force])
|
|
604
|
-
tracker
|
|
605
|
-
halted = true unless
|
|
641
|
+
tracker << f
|
|
642
|
+
halted = true unless run_ba_filter(f)
|
|
606
643
|
end
|
|
607
644
|
end
|
|
608
645
|
!halted
|
|
609
646
|
end
|
|
610
647
|
|
|
611
|
-
# Returns
|
|
612
|
-
|
|
648
|
+
# Returns the set of filters of the given type that have already executed for this request, tracked in `env` so
|
|
649
|
+
# an inherited filter isn't run twice across nested controller dispatches.
|
|
650
|
+
def filter_tracker(type)
|
|
651
|
+
tracker = env['scorched.executed_filters'] ||= {}
|
|
652
|
+
tracker[type] ||= Set.new
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# Returns false if the before/after filter halted. True otherwise.
|
|
656
|
+
def run_ba_filter(f)
|
|
613
657
|
catch(:halt) do
|
|
614
658
|
instance_exec(&f[:proc])
|
|
615
659
|
return true
|
data/lib/scorched/version.rb
CHANGED
data/scorched.gemspec
CHANGED
|
@@ -18,6 +18,7 @@ Gem::Specification.new 'scorched', Scorched::VERSION do |s|
|
|
|
18
18
|
s.add_dependency 'rack-accept', '~> 0.4' # Used for Accept-Charset, Accept-Encoding and Accept-Language headers.
|
|
19
19
|
s.add_dependency 'scorched-accept', '~> 0.1' # Used for Accept header.
|
|
20
20
|
s.add_dependency 'tilt', '~> 2.0'
|
|
21
|
+
s.add_dependency 'logger', '~> 1.7'
|
|
21
22
|
s.add_development_dependency 'rack-test', '>= 1.0'
|
|
22
23
|
s.add_development_dependency 'rspec', '>= 3.4'
|
|
23
24
|
s.add_development_dependency 'rake', '>= 10.4'
|
data/spec/controller_spec.rb
CHANGED
|
@@ -421,7 +421,7 @@ module Scorched
|
|
|
421
421
|
end
|
|
422
422
|
end
|
|
423
423
|
|
|
424
|
-
describe "before/after filters" do
|
|
424
|
+
describe "before/after/around filters" do
|
|
425
425
|
they "run directly before and after the target action" do
|
|
426
426
|
order = []
|
|
427
427
|
app.get('/') { order << :action }
|
|
@@ -540,6 +540,101 @@ module Scorched
|
|
|
540
540
|
end
|
|
541
541
|
end
|
|
542
542
|
|
|
543
|
+
describe "after filters" do
|
|
544
|
+
they "wrap around the before/action/after cycle, outermost first" do
|
|
545
|
+
order = []
|
|
546
|
+
app.around { |cont| order << :first_before; cont.call; order << :first_after }
|
|
547
|
+
app.around { |cont| order << :second_before; cont.call; order << :second_after }
|
|
548
|
+
app.before { order << :before }
|
|
549
|
+
app.after { order << :after }
|
|
550
|
+
app.get('/') { order << :action }
|
|
551
|
+
rt.get('/')
|
|
552
|
+
order.should == %i{first_before second_before before action after second_after first_after}
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
they "run in the context of the controller (same as the route)" do
|
|
556
|
+
route_instance = nil
|
|
557
|
+
around_instance = nil
|
|
558
|
+
app.get('/') { route_instance = self }
|
|
559
|
+
app.around { |cont| around_instance = self; cont.call }
|
|
560
|
+
rt.get('/')
|
|
561
|
+
route_instance.should == around_instance
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
they "run even if no route matches" do
|
|
565
|
+
counter = 0
|
|
566
|
+
app.around { |cont| counter += 1; cont.call }
|
|
567
|
+
rt.delete('/').status.should == 404
|
|
568
|
+
counter.should == 1
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
they "can take an optional set of conditions" do
|
|
572
|
+
counter = 0
|
|
573
|
+
app.around(method: ['GET', 'PUT']) { |cont| counter += 1; cont.call }
|
|
574
|
+
app.get('/') { }
|
|
575
|
+
app.put('/') { }
|
|
576
|
+
rt.post('/')
|
|
577
|
+
rt.get('/')
|
|
578
|
+
rt.put('/')
|
|
579
|
+
counter.should == 2
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
they "skip the target action, and everything else in the chain, if the continuation is never called" do
|
|
583
|
+
action_called = false
|
|
584
|
+
app.before { }
|
|
585
|
+
app.around { |cont| }
|
|
586
|
+
app.get('/') { action_called = true }
|
|
587
|
+
rt.get('/').status.should == 200
|
|
588
|
+
action_called.should == false
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
they "still unwind (run code after the continuation) even if the action halts" do
|
|
592
|
+
order = []
|
|
593
|
+
app.around { |cont| order << :before; cont.call; order << :after }
|
|
594
|
+
app.get('/') { order << :action; halt 200, 'stopped' }
|
|
595
|
+
response = rt.get('/')
|
|
596
|
+
response.body.should == 'stopped'
|
|
597
|
+
order.should == %i{before action after}
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
they "still unwind even if the action raises, once the error is handled" do
|
|
601
|
+
order = []
|
|
602
|
+
app.around { |cont| order << :before; cont.call; order << :after }
|
|
603
|
+
app.get('/') { raise 'boom' }
|
|
604
|
+
app.error { |e| order << :error; halt 500 }
|
|
605
|
+
rt.get('/').status.should == 500
|
|
606
|
+
order.should == %i{before error after}
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
describe "nesting" do
|
|
610
|
+
example "around filters inherit but only run once" do
|
|
611
|
+
around_counter = 0
|
|
612
|
+
app.around { |cont| around_counter += 1; cont.call }
|
|
613
|
+
subcontroller = app.controller { get('/') { 'wow' } }
|
|
614
|
+
subcontroller.filters[:around].should == app.filters[:around]
|
|
615
|
+
|
|
616
|
+
rt.get('/')
|
|
617
|
+
around_counter.should == 1
|
|
618
|
+
|
|
619
|
+
# Hitting the subcontroller directly should yield the same results.
|
|
620
|
+
around_counter = 0
|
|
621
|
+
Rack::Test::Session.new(subcontroller).get('/')
|
|
622
|
+
around_counter.should == 1
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
example "around filters wrap from outermost to innermost" do
|
|
626
|
+
order = []
|
|
627
|
+
app.around { |cont| order << :outer_before; cont.call; order << :outer_after }
|
|
628
|
+
app.controller do
|
|
629
|
+
around { |cont| order << :inner_before; cont.call; order << :inner_after }
|
|
630
|
+
get('/') { order << :action }
|
|
631
|
+
end
|
|
632
|
+
rt.get('/')
|
|
633
|
+
order.should == %i{outer_before inner_before action inner_after outer_after}
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
543
638
|
describe "error filters" do
|
|
544
639
|
let(:app) do
|
|
545
640
|
Class.new(Scorched::Controller) do
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scorched
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom Wardrop
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rack
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '2.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: logger
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.7'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.7'
|
|
82
96
|
- !ruby/object:Gem::Dependency
|
|
83
97
|
name: rack-test
|
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -202,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
202
216
|
- !ruby/object:Gem::Version
|
|
203
217
|
version: '0'
|
|
204
218
|
requirements: []
|
|
205
|
-
rubygems_version:
|
|
219
|
+
rubygems_version: 4.0.13
|
|
206
220
|
specification_version: 4
|
|
207
221
|
summary: Light-weight, DRY as a desert, web framework for Ruby
|
|
208
222
|
test_files:
|