Skip to content

Commit

Permalink
Add match_hook_args plugin, similar to match_hooks but support matche…
Browse files Browse the repository at this point in the history
…rs and block args as hook arguments
  • Loading branch information
jeremyevans committed Aug 7, 2023
1 parent 91ab4f7 commit 44a7e0e
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
= master

* Add match_hook_args plugin, similar to match_hooks but support matchers and block args as hook arguments (jeremyevans)

= 3.70.0 (2023-07-12)

* Add plain_hash_response_headers plugin, using a plain hash for response headers on Rack 3 for much better performance (jeremyevans)
Expand Down
4 changes: 3 additions & 1 deletion lib/roda/plugins/match_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
class Roda
module RodaPlugins
# The match_hook plugin adds hooks that are called upon a successful match
# by any of the matchers.
# by any of the matchers. The hooks do not take any arguments. If you would
# like hooks that pass the arguments/matchers and values yielded to the route block,
# use the match_hook_args plugin.
#
# plugin :match_hook
#
Expand Down
92 changes: 92 additions & 0 deletions lib/roda/plugins/match_hook_args.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen-string-literal: true

#
class Roda
module RodaPlugins
# The match_hook_args plugin adds hooks that are called upon a successful match
# by any of the matchers. It is similar to the match_hook plugin, but it allows
# for passing the matchers and block arguments for each match method.
#
# plugin :match_hook_args
#
# add_match_hook do |matchers, block_args|
# logger.debug("matchers: #{matchers.inspect}. #{block_args.inspect} yielded.")
# end
#
# # Term is an implicit matcher used for terminating matches, and
# # will be included in the array of matchers yielded to match block
# # if a terminating match is used.
# term = self.class::RodaRequest::TERM
#
# route do |r|
# r.root do
# # matchers: nil, block_args: nil
# end
#
# r.on 'a', ['b', 'c'], Integer |segment, id|
# # for a request for /a/b/1:
# # matchers: ['a', ['b', 'c'], Integer], block_args: ['b', 1]
# end
#
# r.get 'd' |segment, id|
# # for a request for /d
# # matchers: ['d', term], block_args: []
# end
# end
module MatchHookArgs
def self.configure(app)
app.opts[:match_hook_args] ||= []
end

module ClassMethods
# Freeze the array of hook methods when freezing the app
def freeze
opts[:match_hook_args].freeze
super
end

# Add a match hook that will be called with matchers and block args.
def add_match_hook(&block)
opts[:match_hook_args] << define_roda_method("match_hook_args", :any, &block)

if opts[:match_hook_args].length == 1
class_eval("alias _match_hook_args #{opts[:match_hook_args].first}", __FILE__, __LINE__)
else
class_eval("def _match_hook_args(v, a); #{opts[:match_hook_args].map{|m| "#{m}(v, a)"}.join(';')} end", __FILE__, __LINE__)
end

public :_match_hook_args

nil
end
end

module InstanceMethods
# Default empty method if no match hooks are defined.
def _match_hook_args(matchers, block_args)
end
end

module RequestMethods
private

# Call the match hook with matchers and block args if yielding to the block before yielding to the block.
def if_match(v)
super do |*a|
scope._match_hook_args(v, a)
yield(*a)
end
end

# Call the match hook with nil matchers and blocks before yielding to the block
def always
scope._match_hook_args(nil, nil)
super
end
end
end

register_plugin :match_hook_args, MatchHookArgs
end
end

89 changes: 89 additions & 0 deletions spec/plugin/match_hook_args_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
require_relative "../spec_helper"

describe "match_hook_args plugin" do
it "yields matchers and block args to match hooks" do
matches = []
app(:bare) do
plugin :match_hook_args
add_match_hook do |matchers, block_args|
matches << [matchers, block_args, request.matched_path, request.remaining_path]
end
route do |r|
r.on "foo" do
r.on "bar" do
r.get "baz" do
"fbb"
end
"fb"
end
"f"
end

r.get "bar" do
"b"
end

r.get "baz", Integer do |id|
"b-#{id}"
end

r.root do
"r"
end

"n"
end
end

term = app::RodaRequest::TERM

body("/foo").must_equal 'f'
matches.must_equal [[%w"foo", [], "/foo", ""]]

matches.clear
body("/foo/bar").must_equal 'fb'
matches.must_equal [[%w"foo", [], "/foo", "/bar"], [%w"bar", [], "/foo/bar", ""]]

matches.clear
body("/foo/bar/baz").must_equal 'fbb'
matches.must_equal [[%w"foo", [], "/foo", "/bar/baz"], [%w"bar", [], "/foo/bar", "/baz"], [["baz", term], [], "/foo/bar/baz", ""]]

matches.clear
body("/bar").must_equal 'b'
matches.must_equal [[["bar", term], [], "/bar", ""]]

matches.clear
body("/baz/1").must_equal 'b-1'
matches.must_equal [[["baz", Integer, term], [1], "/baz/1", ""]]

matches.clear
body.must_equal 'r'
matches.must_equal [[nil, nil, "", "/"]]

matches.clear
body('/x').must_equal 'n'
matches.must_be_empty

matches.clear
body("/foo/baz").must_equal 'f'
matches.must_equal [[%w"foo", [], "/foo", "/baz"]]

matches.clear
body("/foo/bar/bar").must_equal 'fb'
matches.must_equal [[%w"foo", [], "/foo", "/bar/bar"], [%w"bar", [], "/foo/bar", "/bar"]]

app.add_match_hook{|_,_|matches << :x }

matches.clear
body("/foo/bar/baz").must_equal 'fbb'
matches.must_equal [[%w"foo", [], "/foo", "/bar/baz"], :x, [%w"bar", [], "/foo/bar", "/baz"], :x, [["baz", term], [], "/foo/bar/baz", ""], :x]

app.freeze

matches.clear
body("/foo/bar/baz").must_equal 'fbb'
matches.must_equal [[%w"foo", [], "/foo", "/bar/baz"], :x, [%w"bar", [], "/foo/bar", "/baz"], :x, [["baz", term], [], "/foo/bar/baz", ""], :x]

app.opts[:match_hook_args].must_be :frozen?
end
end
1 change: 1 addition & 0 deletions www/pages/documentation.erb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<li><a href="rdoc/classes/Roda/RodaPlugins/Head.html">head</a>: Treat HEAD requests like GET requests with an empty response body.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/Hooks.html">hooks</a>: Adds before/after hook methods.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/MatchHook.html">match_hook</a>: Adds a hook method which is called when a path segment is matched.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/MatchHookArgs.html">match_hook_args</a>: Similar to match_hook plugin, but supports passing matchers and block args to hooks.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/MultiRoute.html">multi_route</a>: Allows dispatching to multiple named route blocks in a single call.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/MultiRun.html">multi_run</a>: Adds the ability to dispatch to multiple rack applications based on the request path prefix.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/NamedRoutes.html">named_routes</a>: Allows for multiple named route blocks that can be dispatched to inside the main route block.</li>
Expand Down

0 comments on commit 44a7e0e

Please sign in to comment.