roda 3.17.0 → 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +48 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +22 -4
  5. data/doc/release_notes/3.18.0.txt +170 -0
  6. data/lib/roda.rb +249 -26
  7. data/lib/roda/plugins/_after_hook.rb +4 -26
  8. data/lib/roda/plugins/_before_hook.rb +30 -2
  9. data/lib/roda/plugins/branch_locals.rb +2 -2
  10. data/lib/roda/plugins/class_level_routing.rb +9 -7
  11. data/lib/roda/plugins/default_headers.rb +15 -1
  12. data/lib/roda/plugins/default_status.rb +9 -10
  13. data/lib/roda/plugins/direct_call.rb +38 -0
  14. data/lib/roda/plugins/error_email.rb +1 -1
  15. data/lib/roda/plugins/error_handler.rb +37 -11
  16. data/lib/roda/plugins/hooks.rb +28 -30
  17. data/lib/roda/plugins/mail_processor.rb +16 -11
  18. data/lib/roda/plugins/mailer.rb +1 -1
  19. data/lib/roda/plugins/middleware.rb +13 -3
  20. data/lib/roda/plugins/multi_route.rb +3 -3
  21. data/lib/roda/plugins/named_templates.rb +4 -4
  22. data/lib/roda/plugins/path.rb +13 -8
  23. data/lib/roda/plugins/render.rb +2 -2
  24. data/lib/roda/plugins/route_block_args.rb +4 -3
  25. data/lib/roda/plugins/route_csrf.rb +9 -4
  26. data/lib/roda/plugins/sessions.rb +2 -1
  27. data/lib/roda/plugins/shared_vars.rb +1 -1
  28. data/lib/roda/plugins/static_routing.rb +7 -17
  29. data/lib/roda/plugins/status_handler.rb +5 -3
  30. data/lib/roda/plugins/view_options.rb +2 -2
  31. data/lib/roda/version.rb +1 -1
  32. data/spec/define_roda_method_spec.rb +257 -0
  33. data/spec/plugin/class_level_routing_spec.rb +0 -27
  34. data/spec/plugin/default_headers_spec.rb +7 -0
  35. data/spec/plugin/default_status_spec.rb +31 -1
  36. data/spec/plugin/direct_call_spec.rb +28 -0
  37. data/spec/plugin/error_handler_spec.rb +27 -0
  38. data/spec/plugin/hooks_spec.rb +21 -0
  39. data/spec/plugin/middleware_spec.rb +108 -36
  40. data/spec/plugin/multi_route_spec.rb +12 -0
  41. data/spec/plugin/route_csrf_spec.rb +27 -0
  42. data/spec/plugin/sessions_spec.rb +26 -1
  43. data/spec/plugin/static_routing_spec.rb +25 -3
  44. data/spec/plugin/status_handler_spec.rb +17 -0
  45. data/spec/route_spec.rb +39 -0
  46. data/spec/spec_helper.rb +2 -2
  47. metadata +9 -3
@@ -140,7 +140,7 @@ class Roda
140
140
  def mail(path, *args)
141
141
  mail = ::Mail.new
142
142
  catch(:no_mail) do
143
- unless mail.equal?(new("PATH_INFO"=>path, 'SCRIPT_NAME'=>'', "REQUEST_METHOD"=>"MAIL", 'rack.input'=>StringIO.new, 'roda.mail'=>mail, 'roda.mail_args'=>args).call(&@rack_app_route_block))
143
+ unless mail.equal?(new("PATH_INFO"=>path, 'SCRIPT_NAME'=>'', "REQUEST_METHOD"=>"MAIL", 'rack.input'=>StringIO.new, 'roda.mail'=>mail, 'roda.mail_args'=>args)._roda_handle_main_route)
144
144
  raise Error, "route did not return mail instance for #{path.inspect}, #{args.inspect}"
145
145
  end
146
146
  mail
@@ -127,16 +127,26 @@ class Roda
127
127
  Forwarder.new(self, app, *args, &block)
128
128
  end
129
129
  end
130
+ end
130
131
 
132
+ module InstanceMethods
131
133
  # Override the route block so that if no route matches, we throw so
132
- # that the next middleware is called.
133
- def route(*args, &block)
134
+ # that the next middleware is called. Old Dispatch API.
135
+ def call(&block)
134
136
  super do |r|
135
- res = instance_exec(r, &block)
137
+ res = instance_exec(r, &block) # call Fallback
136
138
  throw :next, true if r.forward_next
137
139
  res
138
140
  end
139
141
  end
142
+
143
+ # Override the route block so that if no route matches, we throw so
144
+ # that the next middleware is called.
145
+ def _roda_run_main_route(r)
146
+ res = super
147
+ throw :next, true if r.forward_next
148
+ res
149
+ end
140
150
  end
141
151
 
142
152
  module RequestMethods
@@ -169,8 +169,8 @@ class Roda
169
169
  # call super.
170
170
  def route(name=nil, namespace=nil, &block)
171
171
  if name
172
- opts[:namespaced_routes][namespace] ||= {}
173
- opts[:namespaced_routes][namespace][name] = convert_route_block(block)
172
+ routes = opts[:namespaced_routes][namespace] ||= {}
173
+ routes[name] = define_roda_method(routes[name] || "multi_route_#{namespace}_#{name}", 1, &convert_route_block(block))
174
174
  self::RodaRequest.clear_named_route_regexp!(namespace)
175
175
  else
176
176
  super(&block)
@@ -213,7 +213,7 @@ class Roda
213
213
 
214
214
  # Dispatch to the named route with the given name.
215
215
  def route(name, namespace=nil)
216
- scope.instance_exec(self, &roda_class.named_route(name, namespace))
216
+ scope.send(roda_class.named_route(name, namespace), self)
217
217
  end
218
218
  end
219
219
  end
@@ -69,7 +69,7 @@ class Roda
69
69
 
70
70
  # Store a new template block and options for the given template name.
71
71
  def template(name, options=nil, &block)
72
- opts[:named_templates][name.to_s] = [options, block].freeze
72
+ opts[:named_templates][name.to_s] = [options, define_roda_method("named_templates_#{name}", 0, &block)].freeze
73
73
  nil
74
74
  end
75
75
  end
@@ -80,14 +80,14 @@ class Roda
80
80
  # If a template name is given and it matches a named template, call
81
81
  # the named template block to get the inline template to use.
82
82
  def find_template(options)
83
- if options[:template] && (template_opts, block = opts[:named_templates][template_name(options)]; block)
83
+ if options[:template] && (template_opts, meth = opts[:named_templates][template_name(options)]; meth)
84
84
  if template_opts
85
- options = Hash[template_opts].merge!(options)
85
+ options = template_opts.merge(options)
86
86
  else
87
87
  options = Hash[options]
88
88
  end
89
89
 
90
- options[:inline] = instance_exec(&block)
90
+ options[:inline] = send(meth)
91
91
 
92
92
  super(options)
93
93
  else
@@ -10,7 +10,7 @@ class Roda
10
10
  #
11
11
  # Additionally, you can call the +path+ class method with a class and a block, and it will register
12
12
  # the class. You can then call the +path+ instance method with an instance of that class, and it will
13
- # instance_exec the block with the arguments provided to path.
13
+ # execute the block in the context of the route block scope with the arguments provided to path.
14
14
  #
15
15
  # Example:
16
16
  #
@@ -60,9 +60,8 @@ class Roda
60
60
  # method. If a Symbol or String, uses the value as the url method name.
61
61
  # :url_only :: Do not create a path method, just a url method.
62
62
  #
63
- # Note that if :add_script_name, :url, or :url_only is used, will also create a <tt>_*_path</tt>
64
- # method. This is necessary in order to support path methods that accept blocks, as you can't pass
65
- # a block to a block that is instance_execed.
63
+ # Note that if :add_script_name, :url, or :url_only is used, the path method will also create a
64
+ # <tt>_*_path</tt> private method.
66
65
  module Path
67
66
  DEFAULT_PORTS = {'http' => 80, 'https' => 443}.freeze
68
67
 
@@ -73,6 +72,7 @@ class Roda
73
72
  app.instance_eval do
74
73
  self.opts[:path_class_by_name] = opts.fetch(:by_name, ENV['RACK_ENV'] == 'development')
75
74
  self.opts[:path_classes] ||= {}
75
+ self.opts[:path_class_methods] ||= {}
76
76
  unless path_block(String)
77
77
  path(String){|str| str}
78
78
  end
@@ -88,6 +88,7 @@ class Roda
88
88
  # Freeze the path classes when freezing the app.
89
89
  def freeze
90
90
  path_classes.freeze
91
+ opts[:path_classes_methods].freeze
91
92
  super
92
93
  end
93
94
 
@@ -100,6 +101,7 @@ class Roda
100
101
  name = name.name
101
102
  end
102
103
  path_classes[name] = block
104
+ self.opts[:path_class_methods][name] = define_roda_method("path_#{name}", :any, &block)
103
105
  return
104
106
  end
105
107
 
@@ -164,6 +166,7 @@ class Roda
164
166
 
165
167
  # Return the block related to the given class, or nil if there is no block.
166
168
  def path_block(klass)
169
+ # RODA4: Remove
167
170
  if opts[:path_class_by_name]
168
171
  klass = klass.name
169
172
  end
@@ -175,14 +178,16 @@ class Roda
175
178
  # Return a path based on the class of the object. The object passed must have
176
179
  # had its class previously registered with the application. If the app's
177
180
  # :add_script_name option is true, this prepends the SCRIPT_NAME to the path.
178
- def path(obj, *args)
181
+ def path(obj, *args, &block)
179
182
  app = self.class
180
- unless blk = app.path_block(obj.class)
183
+ opts = app.opts
184
+ klass = opts[:path_class_by_name] ? obj.class.name : obj.class
185
+ unless meth = opts[:path_class_methods][klass]
181
186
  raise RodaError, "unrecognized object given to Roda#path: #{obj.inspect}"
182
187
  end
183
188
 
184
- path = instance_exec(obj, *args, &blk)
185
- path = request.script_name.to_s + path if app.opts[:add_script_name]
189
+ path = send(meth, obj, *args, &block)
190
+ path = request.script_name.to_s + path if opts[:add_script_name]
186
191
  path
187
192
  end
188
193
  end
@@ -342,10 +342,10 @@ class Roda
342
342
  opts = found_template_opts || find_template(opts)
343
343
  template_opts = render_opts[:template_opts]
344
344
  if engine_opts = render_opts[:engine_opts][opts[:engine]]
345
- template_opts = Hash[template_opts].merge!(engine_opts)
345
+ template_opts = template_opts.merge(engine_opts)
346
346
  end
347
347
  if current_template_opts = opts[:template_opts]
348
- template_opts = Hash[template_opts].merge!(current_template_opts)
348
+ template_opts = template_opts.merge(current_template_opts)
349
349
  end
350
350
  opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block])
351
351
  end
@@ -25,7 +25,7 @@ class Roda
25
25
  module RouteBlockArgs
26
26
  def self.configure(app, &block)
27
27
  app.instance_exec do
28
- opts[:route_block_args] = block
28
+ define_roda_method(:_roda_route_block_args, 0, &block)
29
29
  route(&@raw_route_block) if @raw_route_block
30
30
  end
31
31
  end
@@ -37,8 +37,9 @@ class Roda
37
37
  private
38
38
 
39
39
  def convert_route_block(block)
40
- proc do |r|
41
- instance_exec(*instance_exec(&opts[:route_block_args]), &block)
40
+ meth = define_roda_method("convert_route_block_args", :any, &super(block))
41
+ lambda do |_|
42
+ send(meth, *_roda_route_block_args)
42
43
  end
43
44
  end
44
45
  end
@@ -164,11 +164,13 @@ class Roda
164
164
 
165
165
  def self.configure(app, opts=OPTS, &block)
166
166
  options = app.opts[:route_csrf] = (app.opts[:route_csrf] || DEFAULTS).merge(opts)
167
- if block
168
- if opts[:csrf_failure]
167
+ if block || opts[:csrf_failure].is_a?(Proc)
168
+ if block && opts[:csrf_failure]
169
169
  raise RodaError, "Cannot specify both route_csrf plugin block and :csrf_failure option"
170
170
  end
171
- options[:csrf_failure] = block
171
+ block ||= opts[:csrf_failure]
172
+ options[:csrf_failure] = :csrf_failure_method
173
+ app.define_roda_method(:_roda_route_csrf_failure, 1, &app.send(:convert_route_block, block))
172
174
  end
173
175
  options[:env_header] = "HTTP_#{options[:header].to_s.gsub('-', '_').upcase}".freeze
174
176
  options.freeze
@@ -192,8 +194,11 @@ class Roda
192
194
  throw :halt, [403, {'Content-Type'=>'text/html', 'Content-Length'=>'0'}, []]
193
195
  when :clear_session
194
196
  session.clear
197
+ when :csrf_failure_method
198
+ @_request.on{_roda_route_csrf_failure(@_request)}
195
199
  when Proc
196
- @_request.on{instance_exec(@_request, &failure_action)}
200
+ RodaPlugins.warn "Passing a Proc as the :csrf_failure option value to check_csrf! is deprecated"
201
+ @_request.on{instance_exec(@_request, &failure_action)} # Deprecated
197
202
  else
198
203
  raise RodaError, "Unsupported :csrf_failure option: #{failure_action.inspect}"
199
204
  end
@@ -162,6 +162,7 @@ class Roda
162
162
  plugin_opts = opts
163
163
  opts = (app.opts[:sessions] || DEFAULT_OPTIONS).merge(opts)
164
164
  co = opts[:cookie_options] = DEFAULT_COOKIE_OPTIONS.merge(opts[:cookie_options] || OPTS).freeze
165
+ opts[:remove_cookie_options] = co.merge(:max_age=>'0', :expires=>Time.at(0))
165
166
  opts[:parser] ||= app.opts[:json_parser] || JSON.method(:parse)
166
167
  opts[:serializer] ||= app.opts[:json_serializer] || :to_json.to_proc
167
168
 
@@ -231,7 +232,7 @@ class Roda
231
232
  if session.empty?
232
233
  if env[SESSION_SERIALIZED]
233
234
  # If session was submitted and is now empty, remove the cookie
234
- Rack::Utils.delete_cookie_header!(headers, opts[:key])
235
+ Rack::Utils.delete_cookie_header!(headers, opts[:key], opts[:remove_cookie_options])
235
236
  # else
236
237
  # If no session was submitted, and the session is empty
237
238
  # then there is no need to do anything
@@ -64,7 +64,7 @@ class Roda
64
64
  if block_given?
65
65
  if vars
66
66
  begin
67
- env['roda.shared'] = Hash[h].merge!(vars)
67
+ env['roda.shared'] = h.merge(vars)
68
68
  yield
69
69
  ensure
70
70
  env['roda.shared'] = h
@@ -50,7 +50,7 @@ class Roda
50
50
  # while still handling the request methods differently.
51
51
  module StaticRouting
52
52
  def self.configure(app)
53
- app.opts[:static_routes] = {}
53
+ app.opts[:static_routes] ||= {}
54
54
  end
55
55
 
56
56
  module ClassMethods
@@ -96,7 +96,8 @@ class Roda
96
96
 
97
97
  # Add a static route for the given method.
98
98
  def add_static_route(method, path, &block)
99
- (opts[:static_routes][path] ||= {})[method] = convert_route_block(block)
99
+ routes = opts[:static_routes][path] ||= {}
100
+ routes[method] = define_roda_method(routes[method] || "static_route_#{method}_#{path}", 1, &convert_route_block(block))
100
101
  end
101
102
  end
102
103
 
@@ -107,21 +108,10 @@ class Roda
107
108
  # instead having the routing tree handle the request.
108
109
  def _roda_before_30__static_routing
109
110
  r = @_request
110
- if route = self.class.static_route_for(r.request_method, r.path_info)
111
- r.static_route(&route)
112
- end
113
- end
114
- end
115
-
116
- module RequestMethods
117
- # Assume that this request matches a static route, setting
118
- # the remaining path to the emptry string and passing
119
- # control to the given block.
120
- def static_route(&block)
121
- @remaining_path = ''
122
-
123
- always do
124
- scope.instance_exec(self, &block)
111
+ if meth = self.class.static_route_for(r.request_method, r.path_info)
112
+ r.instance_variable_set(:@remaining_path, '')
113
+ r.send(:block_result, send(meth, r))
114
+ throw :halt, @_response.finish
125
115
  end
126
116
  end
127
117
  end
@@ -30,7 +30,9 @@ class Roda
30
30
  module ClassMethods
31
31
  # Install the given block as a status handler for the given HTTP response code.
32
32
  def status_handler(code, &block)
33
- opts[:status_handler][code] = block
33
+ # For backwards compatibility, pass request argument if block accepts argument
34
+ arity = block.arity == 0 ? 0 : 1
35
+ opts[:status_handler][code] = [define_roda_method(:"_roda_status_handler_#{code}", arity, &block), arity]
34
36
  end
35
37
 
36
38
  # Freeze the hash of status handlers so that there can be no thread safety issues at runtime.
@@ -45,11 +47,11 @@ class Roda
45
47
 
46
48
  # If routing returns a response we have a handler for, call that handler.
47
49
  def _roda_after_20__status_handler(result)
48
- if result && (block = opts[:status_handler][result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
50
+ if result && (meth, arity = opts[:status_handler][result[0]]; meth) && (v = result[2]).is_a?(Array) && v.empty?
49
51
  res = @_response
50
52
  res.headers.clear
51
53
  res.status = result[0]
52
- result.replace(_call(&block))
54
+ result.replace(_roda_handle_route{arity == 1 ? send(meth, @_request) : send(meth)})
53
55
  end
54
56
  end
55
57
  end
@@ -107,7 +107,7 @@ class Roda
107
107
  # Set branch/route options to use when rendering the layout
108
108
  def set_layout_options(opts)
109
109
  if options = @_layout_options
110
- @_layout_options = Hash[options].merge!(opts)
110
+ @_layout_options = options.merge!(opts)
111
111
  else
112
112
  @_layout_options = opts
113
113
  end
@@ -116,7 +116,7 @@ class Roda
116
116
  # Set branch/route options to use when rendering the view
117
117
  def set_view_options(opts)
118
118
  if options = @_view_options
119
- @_view_options = Hash[options].merge!(opts)
119
+ @_view_options = options.merge!(opts)
120
120
  else
121
121
  @_view_options = opts
122
122
  end
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 17
7
+ RodaMinorVersion = 18
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -0,0 +1,257 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "Roda.define_roda_method" do
4
+ before do
5
+ @scope = app.new({})
6
+ end
7
+
8
+ it "should define methods using block" do
9
+ m0 = app.define_roda_method("x", 0){1}
10
+ m0.must_be_kind_of Symbol
11
+ m0.must_match /\A_roda_x_\d+\z/
12
+ @scope.send(m0).must_equal 1
13
+
14
+ m1 = app.define_roda_method("x", 1){|x| [x, 2]}
15
+ m1.must_be_kind_of Symbol
16
+ m1.must_match /\A_roda_x_\d+\z/
17
+ @scope.send(m1, 3).must_equal [3, 2]
18
+ end
19
+
20
+ it "should define private methods" do
21
+ proc{@scope.public_send(app.define_roda_method("x", 0){1})}.must_raise NoMethodError
22
+ end
23
+
24
+ it "should accept symbols as method name and return the same symbol" do
25
+ m0 = app.define_roda_method(:_roda_foo, 0){1}
26
+ m0.must_equal :_roda_foo
27
+ @scope.send(m0).must_equal 1
28
+ end
29
+
30
+ it "should handle optional arguments and splats for expected_arity 0" do
31
+ m2 = app.define_roda_method("x", 0){|*x| [x, 3]}
32
+ @scope.send(m2).must_equal [[], 3]
33
+
34
+ m3 = app.define_roda_method("x", 0){|x=5| [x, 4]}
35
+ @scope.send(m3).must_equal [5, 4]
36
+
37
+ m4 = app.define_roda_method("x", 0){|x=6, *y| [x, y, 5]}
38
+ @scope.send(m4).must_equal [6, [], 5]
39
+ end
40
+
41
+ it "should should optional arguments and splats for expected_arity 1" do
42
+ m2 = app.define_roda_method("x", 1){|y, *x| [y, x, 3]}
43
+ @scope.send(m2, :a).must_equal [:a, [], 3]
44
+
45
+ m3 = app.define_roda_method("x", 1){|y, x=5| [y, x, 4]}
46
+ @scope.send(m3, :b).must_equal [:b, 5, 4]
47
+
48
+ m4 = app.define_roda_method("x", 1){|y, x=6, *z| [y, x, z, 5]}
49
+ @scope.send(m4, :c).must_equal [:c, 6, [], 5]
50
+ end
51
+
52
+ it "should handle differences in arity" do
53
+ m0 = app.define_roda_method("x", 0){|x| [x, 1]}
54
+ @scope.send(m0).must_equal [nil, 1]
55
+
56
+ m1 = app.define_roda_method("x", 1){2}
57
+ @scope.send(m1, 3).must_equal 2
58
+ end
59
+
60
+ it "should raise for unexpected expected_arity" do
61
+ proc{app.define_roda_method("x", 2){|x|}}.must_raise Roda::RodaError
62
+ end
63
+
64
+ it "should fail if :check_arity false app option is used and a block with invalid arity is passed" do
65
+ app.opts[:check_arity] = false
66
+ m0 = app.define_roda_method("x", 0){|x| [x, 1]}
67
+ proc{@scope.send(m0)}.must_raise ArgumentError
68
+
69
+ m1 = app.define_roda_method("x", 1){2}
70
+ proc{@scope.send(m1, 1)}.must_raise ArgumentError
71
+ end
72
+
73
+ deprecated "should warn if :check_arity :warn app option is used and a block with invalid arity is passed" do
74
+ app.opts[:check_arity] = :warn
75
+ m0 = app.define_roda_method("x", 0){|x| [x, 1]}
76
+ @scope.send(m0).must_equal [nil, 1]
77
+
78
+ m1 = app.define_roda_method("x", 1){2}
79
+ @scope.send(m1, 3).must_equal 2
80
+ end
81
+
82
+ [false, true].each do |warn_dynamic_arity|
83
+ meth = warn_dynamic_arity ? :deprecated : :it
84
+ send meth, "should handle expected_arity :any and do dynamic arity check/fix" do
85
+ if warn_dynamic_arity
86
+ app.opts[:check_dynamic_arity] = :warn
87
+ end
88
+
89
+ m0 = app.define_roda_method("x", :any){1}
90
+ @scope.send(m0).must_equal 1
91
+ @scope.send(m0, 2).must_equal 1
92
+
93
+ m1 = app.define_roda_method("x", :any){|x| [x, 1]}
94
+ @scope.send(m1).must_equal [nil, 1]
95
+ @scope.send(m1, 2).must_equal [2, 1]
96
+ @scope.send(m1, 2, 3).must_equal [2, 1]
97
+
98
+ m2 = app.define_roda_method("x", :any){|x=5| [x, 2]}
99
+ @scope.send(m2).must_equal [5, 2]
100
+ @scope.send(m2, 2).must_equal [2, 2]
101
+ @scope.send(m2, 2, 3).must_equal [2, 2]
102
+
103
+ m3 = app.define_roda_method("x", :any){|y, x=5| [x, y, 3]}
104
+ @scope.send(m3).must_equal [5, nil, 3]
105
+ @scope.send(m3, 2).must_equal [5, 2, 3]
106
+ @scope.send(m3, 2, 3).must_equal [3, 2, 3]
107
+ @scope.send(m3, 2, 3, 4).must_equal [3, 2, 3]
108
+
109
+ m4 = app.define_roda_method("x", :any){|*z| [z, 1]}
110
+ @scope.send(m4).must_equal [[], 1]
111
+ @scope.send(m4, 2).must_equal [[2], 1]
112
+
113
+ m5 = app.define_roda_method("x", :any){|x, *z| [x, z, 1]}
114
+ @scope.send(m5).must_equal [nil, [], 1]
115
+ @scope.send(m5, 2).must_equal [2, [], 1]
116
+ @scope.send(m5, 2, 3).must_equal [2, [3], 1]
117
+
118
+ m6 = app.define_roda_method("x", :any){|x=5, *z| [x, z, 2]}
119
+ @scope.send(m6).must_equal [5, [], 2]
120
+ @scope.send(m6, 2).must_equal [2, [], 2]
121
+ @scope.send(m6, 2, 3).must_equal [2, [3], 2]
122
+
123
+ m7 = app.define_roda_method("x", :any){|y, x=5, *z| [x, y, z, 3]}
124
+ @scope.send(m7).must_equal [5, nil, [], 3]
125
+ @scope.send(m7, 2).must_equal [5, 2, [], 3]
126
+ @scope.send(m7, 2, 3).must_equal [3, 2, [], 3]
127
+ @scope.send(m7, 2, 3, 4).must_equal [3, 2, [4], 3]
128
+ end
129
+ end
130
+
131
+ it "should not fix dynamic arity issues if :check_dynamic_arity false app option is used" do
132
+ app.opts[:check_dynamic_arity] = false
133
+
134
+ m0 = app.define_roda_method("x", :any){1}
135
+ @scope.send(m0).must_equal 1
136
+ proc{@scope.send(m0, 2)}.must_raise ArgumentError
137
+
138
+ m1 = app.define_roda_method("x", :any){|x| [x, 1]}
139
+ proc{@scope.send(m1)}.must_raise ArgumentError
140
+ @scope.send(m1, 2).must_equal [2, 1]
141
+ proc{@scope.send(m1, 2, 3)}.must_raise ArgumentError
142
+
143
+ m2 = app.define_roda_method("x", :any){|x=5| [x, 2]}
144
+ @scope.send(m2).must_equal [5, 2]
145
+ @scope.send(m2, 2).must_equal [2, 2]
146
+ proc{@scope.send(m2, 2, 3)}.must_raise ArgumentError
147
+
148
+ m3 = app.define_roda_method("x", :any){|y, x=5| [x, y, 3]}
149
+ proc{@scope.send(m3)}.must_raise ArgumentError
150
+ @scope.send(m3, 2).must_equal [5, 2, 3]
151
+ @scope.send(m3, 2, 3).must_equal [3, 2, 3]
152
+ proc{@scope.send(m3, 2, 3, 4)}.must_raise ArgumentError
153
+
154
+ m4 = app.define_roda_method("x", :any){|*z| [z, 1]}
155
+ @scope.send(m4).must_equal [[], 1]
156
+ @scope.send(m4, 2).must_equal [[2], 1]
157
+
158
+ m5 = app.define_roda_method("x", :any){|x, *z| [x, z, 1]}
159
+ proc{@scope.send(m5)}.must_raise ArgumentError
160
+ @scope.send(m5, 2).must_equal [2, [], 1]
161
+ @scope.send(m5, 2, 3).must_equal [2, [3], 1]
162
+
163
+ m6 = app.define_roda_method("x", :any){|x=5, *z| [x, z, 2]}
164
+ @scope.send(m6).must_equal [5, [], 2]
165
+ @scope.send(m6, 2).must_equal [2, [], 2]
166
+ @scope.send(m6, 2, 3).must_equal [2, [3], 2]
167
+
168
+ m7 = app.define_roda_method("x", :any){|y, x=5, *z| [x, y, z, 3]}
169
+ proc{@scope.send(m7)}.must_raise ArgumentError
170
+ @scope.send(m7, 2).must_equal [5, 2, [], 3]
171
+ @scope.send(m7, 2, 3).must_equal [3, 2, [], 3]
172
+ @scope.send(m7, 2, 3, 4).must_equal [3, 2, [4], 3]
173
+ end
174
+
175
+ if RUBY_VERSION > '2.1'
176
+ it "should raise for required keyword arguments for expected_arity 0 or 1" do
177
+ proc{eval("app.define_roda_method('x', 0){|b:| [b, 1]}", binding)}.must_raise Roda::RodaError
178
+ proc{eval("app.define_roda_method('x', 0){|c=1, b:| [c, b, 1]}", binding)}.must_raise Roda::RodaError
179
+ proc{eval("app.define_roda_method('x', 1){|x, b:| [b, 1]}", binding)}.must_raise Roda::RodaError
180
+ proc{eval("app.define_roda_method('x', 1){|x, c=1, b:| [c, b, 1]}", binding)}.must_raise Roda::RodaError
181
+ end
182
+
183
+ it "should ignore keyword arguments for expected_arity 0" do
184
+ @scope.send(eval("app.define_roda_method('x', 0){|b:2| [b, 1]}", binding)).must_equal [2, 1]
185
+ @scope.send(eval("app.define_roda_method('x', 0){|**b| [b, 1]}", binding)).must_equal [{}, 1]
186
+ @scope.send(eval("app.define_roda_method('x', 0){|c=1, b:2| [c, b, 1]}", binding)).must_equal [1, 2, 1]
187
+ @scope.send(eval("app.define_roda_method('x', 0){|c=1, **b| [c, b, 1]}", binding)).must_equal [1, {}, 1]
188
+ @scope.send(eval("app.define_roda_method('x', 0){|x, b:2| [x, b, 1]}", binding)).must_equal [nil, 2, 1]
189
+ @scope.send(eval("app.define_roda_method('x', 0){|x, **b| [x, b, 1]}", binding)).must_equal [nil, {}, 1]
190
+ @scope.send(eval("app.define_roda_method('x', 0){|x, c=1, b:2| [x, c, b, 1]}", binding)).must_equal [nil, 1, 2, 1]
191
+ @scope.send(eval("app.define_roda_method('x', 0){|x, c=1, **b| [x, c, b, 1]}", binding)).must_equal [nil, 1, {}, 1]
192
+ end
193
+
194
+ it "should ignore keyword arguments for expected_arity 1" do
195
+ @scope.send(eval("app.define_roda_method('x', 1){|b:2| [b, 1]}", binding), 3).must_equal [2, 1]
196
+ @scope.send(eval("app.define_roda_method('x', 1){|**b| [b, 1]}", binding), 3).must_equal [{}, 1]
197
+ @scope.send(eval("app.define_roda_method('x', 1){|c=1, b:2| [c, b, 1]}", binding), 3).must_equal [3, 2, 1]
198
+ @scope.send(eval("app.define_roda_method('x', 1){|c=1, **b| [c, b, 1]}", binding), 3).must_equal [3, {}, 1]
199
+ @scope.send(eval("app.define_roda_method('x', 1){|x, b:2| [x, b, 1]}", binding), 3).must_equal [3, 2, 1]
200
+ @scope.send(eval("app.define_roda_method('x', 1){|x, **b| [x, b, 1]}", binding), 3).must_equal [3, {}, 1]
201
+ @scope.send(eval("app.define_roda_method('x', 1){|x, c=1, b:2| [x, c, b, 1]}", binding), 3).must_equal [3, 1, 2, 1]
202
+ @scope.send(eval("app.define_roda_method('x', 1){|x, c=1, **b| [x, c, b, 1]}", binding), 3).must_equal [3, 1, {}, 1]
203
+ end
204
+
205
+ it "should handle expected_arity :any with keyword arguments" do
206
+ m = eval('app.define_roda_method("x", :any){|b:2| b}', binding)
207
+ @scope.send(m).must_equal 2
208
+ @scope.send(m, 4).must_equal 2
209
+ @scope.send(m, b: 3).must_equal 3
210
+ @scope.send(m, 4, b: 3).must_equal 3
211
+
212
+ m = eval('app.define_roda_method("x", :any){|b:| b}', binding)
213
+ proc{@scope.send(m)}.must_raise ArgumentError
214
+ proc{@scope.send(m, 4)}.must_raise ArgumentError
215
+ @scope.send(m, b: 3).must_equal 3
216
+ @scope.send(m, 4, b: 3).must_equal 3
217
+
218
+ m = eval('app.define_roda_method("x", :any){|**b| b}', binding)
219
+ @scope.send(m).must_equal({})
220
+ @scope.send(m, 4).must_equal({})
221
+ @scope.send(m, b: 3).must_equal(b: 3)
222
+ @scope.send(m, 4, b: 3).must_equal(b: 3)
223
+
224
+ m = eval('app.define_roda_method("x", :any){|x, b:9| [x, b, 1]}', binding)
225
+ @scope.send(m).must_equal [nil, 9, 1]
226
+ @scope.send(m, 2).must_equal [2, 9, 1]
227
+ @scope.send(m, 2, 3).must_equal [2, 9, 1]
228
+ @scope.send(m, b: 4).must_equal [{b: 4}, 9, 1]
229
+ @scope.send(m, 2, b: 4).must_equal [2, 4, 1]
230
+ @scope.send(m, 2, 3, b: 4).must_equal [2, 4, 1]
231
+
232
+ m = eval('app.define_roda_method("x", :any){|x, b:| [x, b, 1]}', binding)
233
+ proc{@scope.send(m)}.must_raise ArgumentError
234
+ proc{@scope.send(m, 2)}.must_raise ArgumentError
235
+ proc{@scope.send(m, 2, 3)}.must_raise ArgumentError
236
+ proc{@scope.send(m, b: 4)}.must_raise ArgumentError
237
+ @scope.send(m, 2, b: 4).must_equal [2, 4, 1]
238
+ @scope.send(m, 2, 3, b: 4).must_equal [2, 4, 1]
239
+
240
+ m = eval('app.define_roda_method("x", :any){|x, **b| [x, b, 1]}', binding)
241
+ @scope.send(m).must_equal [nil, {}, 1]
242
+ @scope.send(m, 2).must_equal [2, {}, 1]
243
+ @scope.send(m, 2, 3).must_equal [2, {}, 1]
244
+ @scope.send(m, b: 4).must_equal [{b: 4}, {}, 1]
245
+ @scope.send(m, 2, b: 4).must_equal [2, {b: 4}, 1]
246
+ @scope.send(m, 2, 3, b: 4).must_equal [2, {b: 4}, 1]
247
+
248
+ m = eval('m = app.define_roda_method("x", :any){|x=5, b:9| [x, b, 2]}', binding)
249
+ @scope.send(m).must_equal [5, 9, 2]
250
+ @scope.send(m, 2).must_equal [2, 9, 2]
251
+ @scope.send(m, 2, 3).must_equal [2, 9, 2]
252
+ @scope.send(m, b: 4).must_equal [5, 4, 2]
253
+ @scope.send(m, 2, b: 4).must_equal [2, 4, 2]
254
+ @scope.send(m, 2, 3, b: 4).must_equal [2, 4, 2]
255
+ end
256
+ end
257
+ end