Skip to content

Commit 6f29606

Browse files
committed
Add map_matcher plugin, for matching next segment in request path to a hash key, yielding hash value
This is designed to be a more efficient approach of a typical pattern for route metaprogramming. When dealing with many similar routes, it is common to use a hash keyed by route segment, and then match against an array of the keys: ```ruby map = { 'album' => Album, 'artist' => Artist, ... }.freeze keys = map.keys.freeze route do |r| r.on "type", keys do |key| value = map[key] # ... end end ``` For large maps, this is less efficient, since the array matcher checks whether each element is the next route segment. The map_matcher plugin allows for: ```ruby route do |r| r.on "type", map: map do |value| # ... end end ``` This gets the next route segment and checks whether it is a hash key, instead of iterating over the hash elements, so it is more efficient. It also results in simpler code.
1 parent 8e46d24 commit 6f29606

File tree

4 files changed

+79
-0
lines changed

4 files changed

+79
-0
lines changed

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
=== master
2+
3+
* Add map_matcher plugin, for matching next segment in request path to a hash key, yielding hash value (jeremyevans)
4+
15
=== 3.96.0 (2025-09-12)
26

37
* Add redirect_path plugin, for automatically calling path with the redirect argument if not given a string (jeremyevans)

lib/roda/plugins/map_matcher.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen-string-literal: true
2+
3+
#
4+
class Roda
5+
module RodaPlugins
6+
# The map_matcher plugin allows you to provide a string-keyed
7+
# hash during route matching, and match any of the keys in the hash
8+
# as the next segment in the request path, yielding the corresponding
9+
# value in the hash:
10+
#
11+
# class App < Roda
12+
# plugin :map_matcher
13+
#
14+
# map = { "foo" => "bar", "baz" => "quux" }.freeze
15+
#
16+
# route do
17+
# r.get map: map do |v|
18+
# v
19+
# # GET /foo => bar
20+
# # GET /baz => quux
21+
# end
22+
# end
23+
# end
24+
module MapMatcher
25+
module RequestMethods
26+
private
27+
28+
# Match only if the next segment in the path is one of the keys
29+
# in the hash, and yield the value of the hash.
30+
def match_map(hash)
31+
rp = @remaining_path
32+
if key = _match_class_String
33+
if value = hash[@captures[-1]]
34+
@captures[-1] = value
35+
true
36+
else
37+
@remaining_path = rp
38+
@captures.pop
39+
false
40+
end
41+
end
42+
end
43+
end
44+
end
45+
46+
register_plugin(:map_matcher, MapMatcher)
47+
end
48+
end

spec/plugin/map_matcher_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require_relative "../spec_helper"
2+
3+
describe "map_matcher plugin" do
4+
it "allows for matching next segment to a hash key, yielding hash value" do
5+
app(:bare) do
6+
plugin :map_matcher
7+
route do |r|
8+
r.is :map=>{'a'=>'x', 'b'=>'y'} do |f|
9+
f
10+
end
11+
r.is({:map=>{'a'=>'x', 'b'=>'y'}}, :map=>{'c'=>'z'}) do |*a|
12+
a.join('-')
13+
end
14+
r.remaining_path
15+
end
16+
end
17+
18+
body("/").must_equal '/'
19+
body("/a").must_equal 'x'
20+
body("/b").must_equal 'y'
21+
body("/c").must_equal '/c'
22+
body("/a/c").must_equal 'x-z'
23+
body("/b/c").must_equal 'y-z'
24+
body("/a/d").must_equal '/a/d'
25+
end
26+
end

www/pages/documentation.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
<li><a href="rdoc/classes/Roda/RodaPlugins/HashMatcher.html">hash_matcher</a>: Adds hash_matcher class method for easily defining hash matchers.</li>
138138
<li><a href="rdoc/classes/Roda/RodaPlugins/HeaderMatchers.html">header_matchers</a>: Adds matchers using information from the request headers.</li>
139139
<li><a href="rdoc/classes/Roda/RodaPlugins/IntegerMatcherMax.html">Integer_matcher_max</a>: Sets a maximum integer value that will be matched by the default Integer matcher.</li>
140+
<li><a href="rdoc/classes/Roda/RodaPlugins/MapMatcher.html">map_matcher</a>: Adds support for :map hash matcher for matching next route segment by hash key, yielding hash value.</li>
140141
<li><a href="rdoc/classes/Roda/RodaPlugins/MatchAffix.html">match_affix</a>: Adds support for overriding default prefix/suffix used in match patterns.</li>
141142
<li><a href="rdoc/classes/Roda/RodaPlugins/MultibyteStringMatcher.html">multibyte_string_matcher</a>: Makes string matcher handle multibyte characters.</li>
142143
<li><a href="rdoc/classes/Roda/RodaPlugins/ParamMatchers.html">param_matchers</a>: Adds matchers using information from the request params.</li>

0 commit comments

Comments
 (0)