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 if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda? 92 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block) 93 94 if keyword == :required && (expected_arity == 0 || expected_arity == 1) 95 raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}" 96 end 97 98 case expected_arity 99 when 0 100 unless required_args == 0 101 if check_arity == :warn 102 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}" 103 end 104 b = block 105 block = lambda{instance_exec(&b)} # Fallback 106 end 107 when 1 108 if required_args == 0 && optional_args == 0 && !rest 109 if check_arity == :warn 110 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}" 111 end 112 temp_method = roda_method_name("temp") 113 class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__) 114 alias_method meth, temp_method 115 undef_method temp_method 116 private meth 117 alias_method meth, meth 118 meth = :"#{meth}_arity" 119 elsif required_args > 1 120 b = block 121 block = lambda{|r| instance_exec(r, &b)} # Fallback 122 end 123 when :any 124 if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity) 125 if keyword 126 # Complexity of handling keyword arguments using define_method is too high, 127 # Fallback to instance_exec in this case. 128 b = block 129 block = if RUBY_VERSION >= '2.7' 130 eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback 131 else 132 # :nocov: 133 lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback 134 # :nocov: 135 end 136 else 137 arity_meth = meth 138 meth = :"#{meth}_arity" 139 end 140 end 141 else 142 raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}" 143 end 144 end 145 146 define_method(meth, &block) 147 private meth 148 alias_method meth, meth 149 150 if arity_meth 151 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth)) 152 max_args = required_args + optional_args 153 define_method(arity_meth) do |*a| 154 arity = a.length 155 if arity > required_args 156 if arity > max_args && !rest 157 if check_dynamic_arity == :warn 158 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}" 159 end 160 a = a.slice(0, max_args) 161 end 162 elsif arity < required_args 163 if check_dynamic_arity == :warn 164 RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}" 165 end 166 a.concat([nil] * (required_args - arity)) 167 end 168 169 send(meth, *a) 170 end 171 private arity_meth 172 alias_method arity_meth, arity_meth 173 end 174 175 call_meth 176 end
Expand the given path, using the root argument as the base directory.
# File lib/roda.rb 179 def expand_path(path, root=opts[:root]) 180 ::File.expand_path(path, root) 181 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 190 def freeze 191 return self if frozen? 192 193 unless opts[:subclassed] 194 # If the _roda_run_main_route instance method has not been overridden, 195 # make it an alias to _roda_main_route for performance 196 if instance_method(:_roda_run_main_route).owner == InstanceMethods 197 class_eval("alias _roda_run_main_route _roda_main_route") 198 end 199 self::RodaResponse.class_eval do 200 if instance_method(:set_default_headers).owner == ResponseMethods && 201 instance_method(:default_headers).owner == ResponseMethods 202 203 private 204 205 alias set_default_headers set_default_headers 206 def set_default_headers 207 @headers['Content-Type'] ||= 'text/html' 208 end 209 end 210 end 211 212 if @middleware.empty? && use_new_dispatch_api? 213 plugin :direct_call 214 end 215 216 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} 217 plugin :_optimized_matching 218 end 219 end 220 221 build_rack_app 222 @opts.freeze 223 @middleware.freeze 224 225 super 226 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 230 def include(*a) 231 res = super 232 def_roda_before 233 def_roda_after 234 res 235 end
When inheriting Roda
, copy the shared data into the subclass, and setup the request and response subclasses.
# File lib/roda.rb 239 def inherited(subclass) 240 raise RodaError, "Cannot subclass a frozen Roda class" if frozen? 241 242 # Mark current class as having been subclassed, as some optimizations 243 # depend on the class not being subclassed 244 opts[:subclassed] = true 245 246 super 247 subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware) 248 subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : []) 249 subclass.instance_variable_set(:@opts, opts.dup) 250 subclass.opts.delete(:subclassed) 251 subclass.opts.to_a.each do |k,v| 252 if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen? 253 subclass.opts[k] = v.dup 254 end 255 end 256 if block = @raw_route_block 257 subclass.route(&block) 258 end 259 260 request_class = Class.new(self::RodaRequest) 261 request_class.roda_class = subclass 262 request_class.match_pattern_cache = RodaCache.new 263 subclass.const_set(:RodaRequest, request_class) 264 265 response_class = Class.new(self::RodaResponse) 266 response_class.roda_class = subclass 267 subclass.const_set(:RodaResponse, response_class) 268 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 279 def plugin(plugin, *args, &block) 280 raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen? 281 plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol) 282 raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module) 283 284 if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block) 285 # RODA4: switch from warning to error 286 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.") 287 end 288 289 plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies) 290 include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods) 291 extend(plugin::ClassMethods) if defined?(plugin::ClassMethods) 292 self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods) 293 self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods) 294 self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods) 295 self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods) 296 plugin.configure(self, *args, &block) if plugin.respond_to?(:configure) 297 @app = nil 298 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 316 def route(&block) 317 unless block 318 RodaPlugins.warn "no block passed to Roda.route" 319 return 320 end 321 322 @raw_route_block = block 323 @route_block = block = convert_route_block(block) 324 @rack_app_route_block = block = rack_app_route_block(block) 325 public define_roda_method(:_roda_main_route, 1, &block) 326 @app = nil 327 end
# File lib/roda.rb 206 def set_default_headers 207 @headers['Content-Type'] ||= 'text/html' 208 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 333 def use(*args, &block) 334 @middleware << [args, block].freeze 335 @app = nil 336 end