Class methods for the Roda
class.
Methods
Public Instance
Attributes
inherit_middleware | [RW] |
Whether middleware from the current class should be inherited by subclasses. True by default, should be set to false when using a design where the parent class accepts requests and uses run to dispatch the request to a subclass. |
opts | [R] |
The settings/options hash for the current class. |
route_block | [R] |
The route block that this class uses. |
Public Instance methods
The rack application that this class uses.
# File lib/roda.rb 33 def app 34 @app || build_rack_app 35 end
Call the internal rack application with the given environment. This allows the class itself to be used as a rack application. However, for performance, it’s better to use app
to get direct access to the underlying rack app.
# File lib/roda.rb 52 def call(env) 53 app.call(env) 54 end
Clear the middleware stack
# File lib/roda.rb 57 def clear_middleware! 58 @middleware.clear 59 @app = nil 60 end
Define an instance method using the block with the provided name and expected arity. If the name is given as a Symbol, it is used directly. If the name is given as a String, a unique name will be generated using that string. The expected arity should be either 0 (no arguments), 1 (single argument), or :any (any number of arguments).
If the :check_arity app option is not set to false, Roda
will check that the arity of the block matches the expected arity, and compensate for cases where it does not. If it is set to :warn, Roda
will warn in the cases where the arity does not match what is expected.
If the expected arity is :any, Roda
must perform a dynamic arity check when the method is called, which can hurt performance even in the case where the arity matches. The :check_dynamic_arity app option can be set to false to turn off the dynamic arity checks. The :check_dynamic_arity app option can be to :warn to warn if Roda
needs to adjust arity dynamically.
Roda
only checks arity for regular blocks, not lambda blocks, as the fixes Roda
uses for regular blocks would not work for lambda blocks.
Roda
does not support blocks with required keyword arguments if the expected arity is 0 or 1.
# File lib/roda.rb 85 def define_roda_method(meth, expected_arity, &block) 86 if meth.is_a?(String) 87 meth = roda_method_name(meth) 88 end 89 call_meth = meth 90 91 # RODA4: Switch to false # :warn in last Roda 3 version 92 if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda? 93 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block) 94 95 if keyword == :required && (expected_arity == 0 || expected_arity == 1) 96 raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}" 97 end 98 99 case expected_arity 100 when 0 101 unless required_args == 0 102 if check_arity == :warn 103 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}" 104 end 105 b = block 106 block = lambda{instance_exec(&b)} # Fallback 107 end 108 when 1 109 if required_args == 0 && optional_args == 0 && !rest 110 if check_arity == :warn 111 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}" 112 end 113 temp_method = roda_method_name("temp") 114 class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__) 115 alias_method meth, temp_method 116 undef_method temp_method 117 private meth 118 alias_method meth, meth 119 meth = :"#{meth}_arity" 120 elsif required_args > 1 121 if check_arity == :warn 122 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but multiple arguments required for #{block.inspect}" 123 end 124 b = block 125 block = lambda{|r| instance_exec(r, &b)} # Fallback 126 end 127 when :any 128 if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity) 129 if keyword 130 # Complexity of handling keyword arguments using define_method is too high, 131 # Fallback to instance_exec in this case. 132 b = block 133 block = if RUBY_VERSION >= '2.7' 134 eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback 135 else 136 # :nocov: 137 lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback 138 # :nocov: 139 end 140 else 141 arity_meth = meth 142 meth = :"#{meth}_arity" 143 end 144 end 145 else 146 raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}" 147 end 148 end 149 150 define_method(meth, &block) 151 private meth 152 alias_method meth, meth 153 154 if arity_meth 155 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth)) 156 max_args = required_args + optional_args 157 define_method(arity_meth) do |*a| 158 arity = a.length 159 if arity > required_args 160 if arity > max_args && !rest 161 if check_dynamic_arity == :warn 162 RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. At most #{max_args} arguments accepted, but #{arity} arguments given for #{block.inspect}" 163 end 164 a = a.slice(0, max_args) 165 end 166 elsif arity < required_args 167 if check_dynamic_arity == :warn 168 RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}" 169 end 170 a.concat([nil] * (required_args - arity)) 171 end 172 173 send(meth, *a) 174 end 175 private arity_meth 176 alias_method arity_meth, arity_meth 177 end 178 179 call_meth 180 end
Expand the given path, using the root argument as the base directory.
# File lib/roda.rb 183 def expand_path(path, root=opts[:root]) 184 ::File.expand_path(path, root) 185 end
Freeze the internal state of the class, to avoid thread safety issues at runtime. It’s optional to call this method, as nothing should be modifying the internal state at runtime anyway, but this makes sure an exception will be raised if you try to modify the internal state after calling this.
Note that freezing the class prevents you from subclassing it, mostly because it would cause some plugins to break.
# File lib/roda.rb 194 def freeze 195 return self if frozen? 196 197 unless opts[:subclassed] 198 # If the _roda_run_main_route instance method has not been overridden, 199 # make it an alias to _roda_main_route for performance 200 if instance_method(:_roda_run_main_route).owner == InstanceMethods 201 class_eval("alias _roda_run_main_route _roda_main_route") 202 end 203 self::RodaResponse.class_eval do 204 if instance_method(:set_default_headers).owner == ResponseMethods && 205 instance_method(:default_headers).owner == ResponseMethods 206 207 private 208 209 alias set_default_headers set_default_headers 210 def set_default_headers 211 @headers[RodaResponseHeaders::CONTENT_TYPE] ||= 'text/html' 212 end 213 end 214 end 215 216 if @middleware.empty? && use_new_dispatch_api? 217 plugin :direct_call 218 end 219 220 if ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods} 221 plugin :_optimized_matching 222 end 223 end 224 225 build_rack_app 226 @opts.freeze 227 @middleware.freeze 228 229 super 230 end
Rebuild the _roda_before and _roda_after methods whenever a plugin might have added a roda_before* or roda_after* method.
# File lib/roda.rb 234 def include(*a) 235 res = super 236 def_roda_before 237 def_roda_after 238 res 239 end
When inheriting Roda
, copy the shared data into the subclass, and setup the request and response subclasses.
# File lib/roda.rb 243 def inherited(subclass) 244 raise RodaError, "Cannot subclass a frozen Roda class" if frozen? 245 246 # Mark current class as having been subclassed, as some optimizations 247 # depend on the class not being subclassed 248 opts[:subclassed] = true 249 250 super 251 subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware) 252 subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : []) 253 subclass.instance_variable_set(:@opts, opts.dup) 254 subclass.opts.delete(:subclassed) 255 subclass.opts.to_a.each do |k,v| 256 if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen? 257 subclass.opts[k] = v.dup 258 end 259 end 260 if block = @raw_route_block 261 subclass.route(&block) 262 end 263 264 request_class = Class.new(self::RodaRequest) 265 request_class.roda_class = subclass 266 request_class.match_pattern_cache = RodaCache.new 267 subclass.const_set(:RodaRequest, request_class) 268 269 response_class = Class.new(self::RodaResponse) 270 response_class.roda_class = subclass 271 subclass.const_set(:RodaResponse, response_class) 272 end
Load a new plugin into the current class. A plugin can be a module which is used directly, or a symbol representing a registered plugin which will be required and then used. Returns nil.
Note that you should not load plugins into a Roda
class after the class has been subclassed, as doing so can break the subclasses.
Roda.plugin PluginModule Roda.plugin :csrf
# File lib/roda.rb 283 def plugin(plugin, *args, &block) 284 raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen? 285 plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol) 286 raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module) 287 288 if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block) 289 # RODA4: switch from warning to error 290 RodaPlugins.warn("Plugin #{plugin} does not accept arguments or a block, but arguments or a block was passed when loading this. This will raise an error in Roda 4.") 291 end 292 293 plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies) 294 include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods) 295 extend(plugin::ClassMethods) if defined?(plugin::ClassMethods) 296 self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods) 297 self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods) 298 self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods) 299 self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods) 300 plugin.configure(self, *args, &block) if plugin.respond_to?(:configure) 301 @app = nil 302 end
Setup routing tree for the current Roda
application, and build the underlying rack application using the stored middleware. Requires a block, which is yielded the request. By convention, the block argument should be named r
. Example:
Roda.route do |r| r.root do "Root" end end
This should only be called once per class, and if called multiple times will overwrite the previous routing.
# File lib/roda.rb 320 def route(&block) 321 unless block 322 RodaPlugins.warn "no block passed to Roda.route" 323 return 324 end 325 326 @raw_route_block = block 327 @route_block = block = convert_route_block(block) 328 @rack_app_route_block = block = rack_app_route_block(block) 329 public define_roda_method(:_roda_main_route, 1, &block) 330 @app = nil 331 end
# File lib/roda.rb 210 def set_default_headers 211 @headers[RodaResponseHeaders::CONTENT_TYPE] ||= 'text/html' 212 end
Add a middleware to use for the rack application. Must be called before calling route
to have an effect. Example:
Roda.use Rack::ShowExceptions
# File lib/roda.rb 337 def use(*args, &block) 338 @middleware << [args, block].freeze 339 @app = nil 340 end