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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5df1a902354207de82c8dcb0a6f00c68d9455d20edc05b3e70a5ef2c806a229a
4
- data.tar.gz: 2bd5dac53da50545d9d6f0d832e7928aeefb07ebfcbbd1749c300b0fd71355e4
3
+ metadata.gz: 7a838c96acf81063ddc76cc2a26fe3497b7fd83de1b095ba4d9cef042835b9ed
4
+ data.tar.gz: 0de14593266fcf5df232a6f56cc89ca4de8d9b7de9d8a4df2fbc173974d55aca
5
5
  SHA512:
6
- metadata.gz: 3becda9a8c64fd085500003132cd5bd982d3ba6b67c33c5cabe9d4011377df4072f76e79baedd34a2544ee1546c105305e506dd1b2c5347844fd3742abf396a4
7
- data.tar.gz: 51988a63c584113976d5a99080174d0d008e08f91f1f1aac17d74b25d353e514e54a7a1a1ea95385a96fdba187846a543be82b933721936387fd9b439ceef849
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
@@ -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
- begin
290
- if config[:strip_trailing_slash] == :redirect && request.path =~ %r{[^/]/+$}
291
- query_string = request.query_string.empty? ? '' : '?' << request.query_string
292
- redirect(request.path.chomp('/') + query_string, status: 307, halt: false)
293
- return response.finish
294
- end
295
- pass if config[:auto_pass] && eligable_matches.empty?
296
-
297
- if run_filters(:before)
298
- catch(:halt) {
299
- begin
300
- try_matches
301
- rescue => inner_error
302
- rescue_block.call(inner_error)
303
- end
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
- # Returns false if any of the filters halted the request. True otherwise.
599
- def run_filters(type)
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 = env['scorched.executed_filters'] ||= {before: Set.new, after: Set.new}
602
- filters[type].reject { |f| tracker[type].include?(f) }.each do |f|
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[type] << f
605
- halted = true unless run_filter(f)
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 false if the filter halted. True otherwise.
612
- def run_filter(f)
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
@@ -1,3 +1,3 @@
1
1
  module Scorched
2
- VERSION = '1.1.0.pre'
2
+ VERSION = '1.1.0'
3
3
  end
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'
@@ -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.pre
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: 2025-02-21 00:00:00.000000000 Z
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: 3.6.5
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: