roda 2.26.0 → 2.27.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ac08181ea0c35fe056de0d73654c46acd01a541
4
- data.tar.gz: bafe66a0feb7e5b3e4d461f8eb05fd3762ccf730
3
+ metadata.gz: e2d73336ee24aee0fa3afe57096ef4d2f47c14f5
4
+ data.tar.gz: 9c0dbc60da0f5d73a24ad48a807f413c0f84e300
5
5
  SHA512:
6
- metadata.gz: '01974a864d4ba36f2173c6a166405f77359a8329e8ae8a7eba65ad1b28549377c52ca50fc4e61f61a828c2323fb5bb1d2dc80bcdb555449117c227144f8d3160'
7
- data.tar.gz: bfec42253904890e2ba6104f9d07b47eae8a3ade1cb45e43f36f5def8280219bce70baec7322545e4040195d5d946ec8038002e6e73edfe15e18c48151d261bb
6
+ metadata.gz: d0e19332b1d9c17b18d4d9e3514a90389e65373508874b41521c3fb4fcfa4f0dc97da1fdfbb09273c9b84ac08addf4c286d85c4467538a1481e459a5307c6ae2
7
+ data.tar.gz: 4d7de5e3b2331e0e9a6c1983a2b79ba2a5f6d5e105b6d7fd8f5acf5d52128f34762cabf68627e5f2866415667fbfcd41b23f7fc315a0ea14ab52cb88b19217ba
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ = 2.27.0 (2017-06-14)
2
+
3
+ * Add class_matchers plugin for matching other classes (in addition to String/Integer), with user specified regexps and type conversion (jeremyevans)
4
+
5
+ * Support String class matcher for non-empty segments, same behavior as symbol matchers but more intuitive and DRY (jeremyevans)
6
+
7
+ * Support Integer class matcher for \d+ segments, yielding matched values as integers (jeremyevans)
8
+
1
9
  = 2.26.0 (2017-05-16)
2
10
 
3
11
  * Support :skip_middleware option to csrf plugin to add only the methods and not add the middleware (luciusgone) (#118)
data/README.rdoc CHANGED
@@ -227,12 +227,12 @@ Here's an example showcasing how different matchers work:
227
227
  end
228
228
 
229
229
  # GET /post/2011/02/16/hello
230
- r.get "post", :y, :m, :d, :slug do |y, m, d, slug|
231
- "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
230
+ r.get "post", Integer, Integer, Integer, String do |year, month, day, slug|
231
+ "#{year}-#{month}-#{day} #{slug}" #=> "2011-02-16 hello"
232
232
  end
233
233
 
234
234
  # GET /username/foobar branch
235
- r.on "username", :username, :method=>:get do |username|
235
+ r.on "username", String, :method=>:get do |username|
236
236
  user = User.find_by_username(username)
237
237
 
238
238
  # GET /username/foobar/posts
@@ -300,8 +300,8 @@ You can use multiple colons in a string:
300
300
  Note that instead of using colons in strings, it is recommended to use separate
301
301
  symbol arguments, as it is faster and simpler:
302
302
 
303
- "foo", :id # instead of "foo/:id"
304
- :x, :y # instead of ":x/:y"
303
+ "foo", String # instead of "foo/:id"
304
+ String, String # instead of ":x/:y"
305
305
 
306
306
  It is possible in future versions of Roda, colons will not be treated specially
307
307
  in strings, and will just match a literal colon character.
@@ -325,6 +325,25 @@ If any patterns are captured by the Regexp, they are yielded:
325
325
  /foo\w+/ # matches "/foobar", yields nothing
326
326
  /foo(\w+)/ # matches "/foobar", yields "bar"
327
327
 
328
+ === Class
329
+
330
+ There are two classes that are supported as matchers, String
331
+ and Integer.
332
+
333
+ String :: matches any non-empty segment
334
+ Integer :: matches any segment of 0-9, returns matched values as integers
335
+
336
+ Using String and Integer is the recommended way to handle
337
+ arbitrary segments
338
+
339
+ String # matches "/foo", yields "foo"
340
+ String # matches "/1", yields "1"
341
+ String # does not match "/"
342
+
343
+ Integer # does not match "/foo"
344
+ Integer # matches "/1", yields 1
345
+ Integer # does not match "/"
346
+
328
347
  === Symbol
329
348
 
330
349
  Symbols match any nonempty segment,
@@ -333,6 +352,11 @@ yielding the segment except for the preceding slash:
333
352
  :id # matches "/foo" yields "foo"
334
353
  :id # does not match "/"
335
354
 
355
+ Symbol matchers operate the same as the class String matcher,
356
+ and is the historical way to do arbitrary segment matching.
357
+ It is recommended to use the class String matcher in new code
358
+ as it is a bit more intuitive.
359
+
336
360
  === Proc
337
361
 
338
362
  Procs match unless they return false or nil:
@@ -381,20 +405,20 @@ allows for easily defining your own:
381
405
 
382
406
  The +:all+ matcher matches if all of the entries in the given array match, so
383
407
 
384
- r.on :all=>[:a, :b] do
408
+ r.on :all=>[String, String] do
385
409
  # ...
386
410
  end
387
411
 
388
412
  is the same as:
389
413
 
390
- r.on :a, :b do
414
+ r.on String, String do
391
415
  # ...
392
416
  end
393
417
 
394
418
  The reason it also exists as a separate hash matcher
395
419
  is so you can use it inside an array matcher, so:
396
420
 
397
- r.on ['foo', {:all=>['foos', :id]}] do
421
+ r.on ['foo', {:all=>['foos', Integer]}] do
398
422
  end
399
423
 
400
424
  would match +/foo+ and +/foos/10+, but not +/foos+.
@@ -413,7 +437,9 @@ If +false+ or +nil+ is given directly as a matcher, it doesn't match anything.
413
437
 
414
438
  === Everything else
415
439
 
416
- Everything else matches anything.
440
+ Everything else matches anything. Note that future versions of Roda will probably
441
+ raise exceptions for unsupported matchers, so it is not recommended to rely on this
442
+ behavior.
417
443
 
418
444
  == Optional segments
419
445
 
@@ -424,11 +450,11 @@ the item's id, and 456 being some optional data.
424
450
  The simplest way to handle this is by treating this as two separate routes with a
425
451
  shared branch:
426
452
 
427
- r.on "items", :id do |item_id|
453
+ r.on "items", String do |item_id|
428
454
  # Shared code for branch here
429
455
 
430
456
  # /items/123/456
431
- r.is :opt_data do |optional_data|
457
+ r.is String do |optional_data|
432
458
  end
433
459
 
434
460
  # /items/123
@@ -440,7 +466,7 @@ This works well for many cases, but there are also cases where you really want t
440
466
  treat it as one route with an optional segment. One simple way to do that is to
441
467
  use a parameter instead of an optional segment (e.g. +/items/123?opt=456+).
442
468
 
443
- r.is "items", :id do |item_id|
469
+ r.is "items", Integer do |item_id|
444
470
  optional_data = r['opt']
445
471
  end
446
472
 
@@ -448,7 +474,7 @@ However, if you really do want to use a optional segment, there are a couple dif
448
474
  ways to use matchers to do so. One is using an array matcher where the last element
449
475
  is true:
450
476
 
451
- r.is "items", :id, [:opt_data, true] do |item_id, optional_data|
477
+ r.is "items", Integer, [String, true] do |item_id, optional_data|
452
478
  end
453
479
 
454
480
  Note that this technically yields only one argument instead of two arguments if the
@@ -0,0 +1,56 @@
1
+ = New Features
2
+
3
+ * String and Integer class matchers have been added. The
4
+ String class matches any non-empty segment and yields it as a
5
+ string. This is the same as the behavior of the symbol matchers,
6
+ but without the duplication. So instead of:
7
+
8
+ r.is "album", :album_name do |album_name|
9
+ end
10
+
11
+ you can now do:
12
+
13
+ r.is "album", String do |album_name|
14
+ end
15
+
16
+ This makes it a bit more intuitive that you want to match
17
+ any string, and avoids the redundancy between the symbol
18
+ name and block argument name.
19
+
20
+ The Integer class matches any integer segment (\d+) and yields it
21
+ as an integer:
22
+
23
+ r.is "album", Integer do |album_id|
24
+ # does not match "/albums/foo"
25
+ # matches "/albums/1", yielding 1 (not "1")
26
+ end
27
+
28
+ Previously, the :d matcher in the symbol_matchers plugin could
29
+ be used to only match integer segments, but it yielded results
30
+ as strings and not integers, so you still needed to convert the
31
+ type manually. Using Integer is a bit more intuitive than
32
+ using :d, and it handles the type conversion for you.
33
+
34
+ * A class_matchers plugin has been added for matching additional
35
+ classes, with user-specified regexps and type conversion. For
36
+ example, if you want to match YYYY-MM-DD segments and yield
37
+ them to the match blocks as ruby Date objects, you can do:
38
+
39
+ plugin :class_matchers
40
+
41
+ class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
42
+ [Date.new(y.to_i, m.to_i, d.to_i)]
43
+ end
44
+
45
+ and then in your routing tree, you can do:
46
+
47
+ r.on "posts", Date do |date|
48
+ # does not match "/posts/foo" or "/posts/2017-01"
49
+ # matches "/posts/2017-01-13", yielding Date.new(2017, 1, 13)
50
+ end
51
+
52
+ = Backwards Compatibility
53
+
54
+ * If you were using the Integer and String classes as matchers
55
+ before and expected them to always match, you'll need to
56
+ change your code to use true instead.
data/lib/roda.rb CHANGED
@@ -687,11 +687,31 @@ class Roda
687
687
  end
688
688
  end
689
689
 
690
+ # Match the given class. Currently, the following classes
691
+ # are supported by default:
692
+ # Integer :: Match an integer segment, yielding result to block as an integer
693
+ # String :: Match any non-empty segment, yielding result to block as a string
694
+ def _match_class(klass)
695
+ meth = :"_match_class_#{klass}"
696
+ if respond_to?(meth, true)
697
+ send(meth)
698
+ else
699
+ unsupported_matcher(klass)
700
+ end
701
+ end
702
+
690
703
  # Match the given hash if all hash matchers match.
691
704
  def _match_hash(hash)
692
705
  hash.all?{|k,v| send("match_#{k}", v)}
693
706
  end
694
707
 
708
+ # Match integer segment, and yield resulting value as an
709
+ # integer.
710
+ def _match_class_Integer
711
+ consume(/\A\/(\d+)(?=\/|\z)/){|i| [i.to_i]}
712
+ end
713
+
714
+ # Match only if all of the arguments in the given array match.
695
715
  # Match the given regexp exactly if it matches a full segment.
696
716
  def _match_regexp(re)
697
717
  consume(self.class.cached_matcher(re){re})
@@ -725,7 +745,7 @@ class Roda
725
745
  end
726
746
 
727
747
  # Match the given symbol if any segment matches.
728
- def _match_symbol(sym)
748
+ def _match_symbol(sym=nil)
729
749
  rp = @remaining_path
730
750
  if rp[0, 1] == SLASH
731
751
  if last = rp.index('/', 1)
@@ -740,6 +760,9 @@ class Roda
740
760
  end
741
761
  end
742
762
 
763
+ # Match any nonempty segment. This should be called without an argument.
764
+ alias _match_class_String _match_symbol
765
+
743
766
  # The regular expression to use for matching symbols. By default, any non-empty
744
767
  # segment matches.
745
768
  def _match_symbol_regexp(s)
@@ -791,7 +814,9 @@ class Roda
791
814
  def consume(pattern)
792
815
  if matchdata = remaining_path.match(pattern)
793
816
  @remaining_path = matchdata.post_match
794
- @captures.concat(matchdata.captures)
817
+ captures = matchdata.captures
818
+ captures = yield(*captures) if block_given?
819
+ @captures.concat(captures)
795
820
  end
796
821
  end
797
822
 
@@ -836,17 +861,19 @@ class Roda
836
861
  false
837
862
  end
838
863
  end
839
-
864
+
840
865
  # Attempt to match the argument to the given request, handling
841
866
  # common ruby types.
842
867
  def match(matcher)
843
868
  case matcher
844
869
  when String
845
870
  _match_string(matcher)
846
- when Symbol
847
- _match_symbol(matcher)
871
+ when Class
872
+ _match_class(matcher)
848
873
  when TERM
849
874
  empty_path?
875
+ when Symbol
876
+ _match_symbol(matcher)
850
877
  when Regexp
851
878
  _match_regexp(matcher)
852
879
  when Hash
@@ -858,10 +885,7 @@ class Roda
858
885
  when true, false, nil
859
886
  matcher
860
887
  else
861
- if roda_class.opts[:unsupported_matcher] == :raise
862
- raise RodaError, "unsupported matcher: #{matcher.inspect}"
863
- end
864
- matcher
888
+ unsupported_matcher(matcher)
865
889
  end
866
890
  end
867
891
 
@@ -885,6 +909,14 @@ class Roda
885
909
  def placeholder_string_matcher?
886
910
  !roda_class.opts[:verbatim_string_matcher]
887
911
  end
912
+
913
+ # Handle an unsupported matcher.
914
+ def unsupported_matcher(matcher)
915
+ if roda_class.opts[:unsupported_matcher] == :raise
916
+ raise RodaError, "unsupported matcher: #{matcher.inspect}"
917
+ end
918
+ matcher
919
+ end
888
920
  end
889
921
 
890
922
  # Class methods for RodaResponse
@@ -8,7 +8,7 @@ class Roda
8
8
  # For proper caching, you should use either the +last_modified+ or
9
9
  # +etag+ request methods.
10
10
  #
11
- # r.get 'albums', :d do |album_id|
11
+ # r.get 'albums', Integer do |album_id|
12
12
  # @album = Album[album_id]
13
13
  # r.last_modified @album.updated_at
14
14
  # view('album')
@@ -16,7 +16,7 @@ class Roda
16
16
  #
17
17
  # # or
18
18
  #
19
- # r.get 'albums', :d do |album_id|
19
+ # r.get 'albums', Integer do |album_id|
20
20
  # @album = Album[album_id]
21
21
  # r.etag @album.sha1
22
22
  # view('album')
@@ -44,7 +44,7 @@ class Roda
44
44
  # block will be delayed until rendering the content template. This is
45
45
  # useful if you want to delay execution for all routes under a branch:
46
46
  #
47
- # r.on 'albums', :d do |album_id|
47
+ # r.on 'albums', Integer do |album_id|
48
48
  # delay do
49
49
  # @album = Album[album_id]
50
50
  # end
@@ -0,0 +1,51 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The class_matchers plugin allows you do define custom regexps and
7
+ # conversion procs to use for specific classes. For example, if you
8
+ # have multiple routes similar to:
9
+ #
10
+ # r.on /(\d\d\d\d)-(\d\d)-(\d\d)/ do |y, m, d|
11
+ # date = Date.new(y.to_i, m.to_i, d.to_i)
12
+ # # ...
13
+ # end
14
+ #
15
+ # You can register a Date class matcher for that regexp (note that
16
+ # the block must return an array):
17
+ #
18
+ # class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
19
+ # [Date.new(y.to_i, m.to_i, d.to_i)]
20
+ # end
21
+ #
22
+ # And then use the Date class as a matcher, and it will yield a Date object:
23
+ #
24
+ # r.on Date do |date|
25
+ # # ...
26
+ # end
27
+ #
28
+ # This is useful to DRY up code if you are using the same type of pattern and
29
+ # type conversion in multiple places in your application.
30
+ #
31
+ # This plugin does not work with the params capturing plugin, as it does not
32
+ # offer the ability to associate block arguments with named keys.
33
+ module ClassMatchers
34
+ module ClassMethods
35
+ # Set the regexp to use for the given class. The block given will be
36
+ # called with all matched values from the regexp, and should return an
37
+ # array with the captures to yield to the match block.
38
+ def class_matcher(klass, re, &block)
39
+ meth = :"_match_class_#{klass}"
40
+ self::RodaRequest.class_eval do
41
+ consume_re = consume_pattern(re)
42
+ define_method(meth){consume(consume_re, &block)}
43
+ private meth
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ register_plugin(:class_matchers, ClassMatchers)
50
+ end
51
+ end
@@ -60,8 +60,8 @@ class Roda
60
60
  # email bodies, you can use all of Roda's usual routing tree features
61
61
  # to DRY up your code:
62
62
  #
63
- # r.on "albums", :d do |album_id|
64
- # @album = Album[album_id.to_i]
63
+ # r.on "albums", Integer do |album_id|
64
+ # @album = Album[album_id]
65
65
  # from 'from@example.com'
66
66
  # to 'to@example.com'
67
67
  #
@@ -92,7 +92,7 @@ class Roda
92
92
  # If while preparing the email you figure out you don't want to send an
93
93
  # email, call +no_mail!+:
94
94
  #
95
- # r.mail 'welcome', :d do |id|
95
+ # r.mail 'welcome', Integer do |id|
96
96
  # no_mail! unless user = User[id]
97
97
  # # ...
98
98
  # end
@@ -36,9 +36,9 @@ class Roda
36
36
  # Patterns can be rewritten dynamically by providing a block accepting a MatchData
37
37
  # object and evaluating to the replacement.
38
38
  #
39
- # rewrite_path(/\A\/a/(\w+)/){|match| "/a/#{match[1].capitalize}"}
39
+ # rewrite_path(/\A\/a\/(\w+)/){|match| "/a/#{match[1].capitalize}"}
40
40
  # # PATH_INFO '/a/moo' => remaining_path '/a/Moo'
41
- # rewrite_path(/\A\/a/(\w+)/, :path_info => true){|match| "/a/#{match[1].capitalize}"}
41
+ # rewrite_path(/\A\/a\/(\w+)/, :path_info => true){|match| "/a/#{match[1].capitalize}"}
42
42
  # # PATH_INFO '/a/moo' => PATH_INFO '/a/Moo'
43
43
  #
44
44
  # All path rewrites are applied in order, so if a path is rewritten by one rewrite,
@@ -18,7 +18,7 @@ class Roda
18
18
  # plugin :shared_vars
19
19
  #
20
20
  # route do |r|
21
- # r.on :user_id do |user_id|
21
+ # r.on Integer do |user_id|
22
22
  # shared[:user] = User[user_id]
23
23
  # r.run API
24
24
  # end
@@ -29,7 +29,7 @@ class Roda
29
29
  # vars with the content of the hash:
30
30
  #
31
31
  # route do |r|
32
- # r.on :user_id do |user_id|
32
+ # r.on Integer do |user_id|
33
33
  # shared(:user => User[user_id])
34
34
  # r.run API
35
35
  # end
@@ -40,7 +40,7 @@ class Roda
40
40
  # previous shared variables afterward:
41
41
  #
42
42
  # route do |r|
43
- # r.on :user_id do |user_id|
43
+ # r.on Integer do |user_id|
44
44
  # shared(:user => User[user_id]) do
45
45
  # r.run API
46
46
  # end
@@ -39,7 +39,7 @@ class Roda
39
39
  # r.on "users" do
40
40
  # set_view_subdir 'users'
41
41
  #
42
- # r.get :id do
42
+ # r.get Integer do |id|
43
43
  # append_view_subdir 'profile'
44
44
  # view 'index' # uses ./views/users/profile/index.erb
45
45
  # end
@@ -27,7 +27,7 @@ class Roda
27
27
  # end
28
28
  #
29
29
  # route do |r|
30
- # r.get "room", :d do |room_id|
30
+ # r.get "room", Integer do |room_id|
31
31
  # room = sync{ROOMS[room_id] ||= []}
32
32
  #
33
33
  # r.websocket do |ws|
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 26
7
+ RodaMinorVersion = 27
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -1,7 +1,7 @@
1
1
  require File.expand_path("spec_helper", File.dirname(__FILE__))
2
2
 
3
3
  describe "capturing" do
4
- it "doesn't yield the verb" do
4
+ it "doesn't yield the verb for verb matcher" do
5
5
  app do |r|
6
6
  r.get do |*args|
7
7
  args.size.to_s
@@ -11,7 +11,7 @@ describe "capturing" do
11
11
  body.must_equal '0'
12
12
  end
13
13
 
14
- it "doesn't yield the path" do
14
+ it "doesn't yield the path for string matcher" do
15
15
  app do |r|
16
16
  r.get "home" do |*args|
17
17
  args.size.to_s
@@ -21,7 +21,7 @@ describe "capturing" do
21
21
  body('/home').must_equal '0'
22
22
  end
23
23
 
24
- it "yields the segment" do
24
+ it "yields the segment for symbol matcher" do
25
25
  app do |r|
26
26
  r.get "user", :id do |id|
27
27
  id
@@ -31,7 +31,7 @@ describe "capturing" do
31
31
  body("/user/johndoe").must_equal 'johndoe'
32
32
  end
33
33
 
34
- it "yields a number" do
34
+ it "yields an integer segment as a string when using symbol matcher" do
35
35
  app do |r|
36
36
  r.get "user", :id do |id|
37
37
  id
@@ -41,7 +41,7 @@ describe "capturing" do
41
41
  body("/user/101").must_equal '101'
42
42
  end
43
43
 
44
- it "yields a segment per nested block" do
44
+ it "yields a segment per nested block for symbol matcher" do
45
45
  app do |r|
46
46
  r.on :one do |one|
47
47
  r.on :two do |two|
@@ -55,7 +55,17 @@ describe "capturing" do
55
55
  body("/one/two/three").must_equal "onetwothree"
56
56
  end
57
57
 
58
- it "regex captures in regex format" do
58
+ it "yields a segment per argument for symbol matcher" do
59
+ app do |r|
60
+ r.on :one, :two, :three do |one, two, three|
61
+ one + two + three
62
+ end
63
+ end
64
+
65
+ body("/one/two/three").must_equal "onetwothree"
66
+ end
67
+
68
+ it "yields regex captures as separate arguments" do
59
69
  app do |r|
60
70
  r.get %r{posts/(\d+)-(.*)} do |id, slug|
61
71
  id + slug
@@ -64,6 +74,28 @@ describe "capturing" do
64
74
 
65
75
  body("/posts/123-postal-service").must_equal "123postal-service"
66
76
  end
77
+
78
+ it "yields an integer segment as an integer when using Integer matcher " do
79
+ app do |r|
80
+ r.get "user", Integer do |id|
81
+ "#{id}-#{id.is_a?(Integer)}"
82
+ end
83
+ "b"
84
+ end
85
+
86
+ body("/user/101").must_equal '101-true'
87
+ body("/user/a").must_equal 'b'
88
+ end
89
+
90
+ it "yields the segment for symbol matcher" do
91
+ app do |r|
92
+ r.get "user", String do |id|
93
+ id
94
+ end
95
+ end
96
+
97
+ body("/user/johndoe").must_equal 'johndoe'
98
+ end
67
99
  end
68
100
 
69
101
  describe "r.is" do
@@ -0,0 +1,40 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+ require 'date'
3
+
4
+ describe "class_matchers plugin" do
5
+ it "allows class specific regexps with type conversion for class matchers" do
6
+ app(:bare) do
7
+ plugin :class_matchers
8
+ class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/){|y,m,d| [Date.new(y.to_i, m.to_i, d.to_i)]}
9
+ class_matcher(Array, /(\w+)\/(\w+)/){|a, b| [[a, 1], [b, 2]]}
10
+ class_matcher(Hash, /(\d+)\/(\d+)/){|a, b| [{a.to_i=>b.to_i}]}
11
+
12
+ route do |r|
13
+ r.on Array do |(a,b), (c,d)|
14
+ r.get Date do |date|
15
+ [date.year, date.month, date.day, a, b, c, d].join('-')
16
+ end
17
+ r.get Hash do |h|
18
+ [h.inspect, a, b, c, d].join('-')
19
+ end
20
+ r.get Array do |(a1,b1), (c1,d1)|
21
+ [a1, b1, c1, d1, a, b, c, d].join('-')
22
+ end
23
+ r.is do
24
+ [a, b, c, d].join('-')
25
+ end
26
+ "array"
27
+ end
28
+ ""
29
+ end
30
+ end
31
+
32
+ body("/c").must_equal ''
33
+ body("/c/d").must_equal 'c-1-d-2'
34
+ body("/c/d/e").must_equal 'array'
35
+ body("/c/d/2009-10-a").must_equal 'array'
36
+ body("/c/d/2009-10-01").must_equal '2009-10-1-c-1-d-2'
37
+ body("/c/d/1/2").must_equal '{1=>2}-c-1-d-2'
38
+ body("/c/d/e/f").must_equal 'e-1-f-2-c-1-d-2'
39
+ end
40
+ end
@@ -23,7 +23,7 @@ describe "error_mail plugin" do
23
23
  end
24
24
  end
25
25
 
26
- before do
26
+ after do
27
27
  Mail::TestMailer.deliveries.clear
28
28
  end
29
29
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-16 00:00:00.000000000 Z
11
+ date: 2017-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -206,6 +206,7 @@ extra_rdoc_files:
206
206
  - doc/release_notes/2.24.0.txt
207
207
  - doc/release_notes/2.25.0.txt
208
208
  - doc/release_notes/2.26.0.txt
209
+ - doc/release_notes/2.27.0.txt
209
210
  files:
210
211
  - CHANGELOG
211
212
  - MIT-LICENSE
@@ -236,6 +237,7 @@ files:
236
237
  - doc/release_notes/2.24.0.txt
237
238
  - doc/release_notes/2.25.0.txt
238
239
  - doc/release_notes/2.26.0.txt
240
+ - doc/release_notes/2.27.0.txt
239
241
  - doc/release_notes/2.3.0.txt
240
242
  - doc/release_notes/2.4.0.txt
241
243
  - doc/release_notes/2.5.0.txt
@@ -253,6 +255,7 @@ files:
253
255
  - lib/roda/plugins/caching.rb
254
256
  - lib/roda/plugins/chunked.rb
255
257
  - lib/roda/plugins/class_level_routing.rb
258
+ - lib/roda/plugins/class_matchers.rb
256
259
  - lib/roda/plugins/content_for.rb
257
260
  - lib/roda/plugins/cookies.rb
258
261
  - lib/roda/plugins/csrf.rb
@@ -344,6 +347,7 @@ files:
344
347
  - spec/plugin/caching_spec.rb
345
348
  - spec/plugin/chunked_spec.rb
346
349
  - spec/plugin/class_level_routing_spec.rb
350
+ - spec/plugin/class_matchers_spec.rb
347
351
  - spec/plugin/content_for_spec.rb
348
352
  - spec/plugin/cookies_spec.rb
349
353
  - spec/plugin/csrf_spec.rb