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. |
| plugins | [R] |
The plugins loaded into 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 34 def app 35 @app || build_rack_app 36 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 56 def call(env) 57 app.call(env) 58 end
Clear the middleware stack
# File lib/roda.rb 61 def clear_middleware! 62 @middleware.clear 63 @app = nil 64 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 89 def define_roda_method(meth, expected_arity, &block) 90 if meth.is_a?(String) 91 meth = roda_method_name(meth) 92 end 93 call_meth = meth 94 95 # RODA4: Switch to false # :warn in last Roda 3 version 96 if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda? 97 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block) 98 99 if keyword == :required && (expected_arity == 0 || expected_arity == 1) 100 raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}" 101 end 102 103 case expected_arity 104 when 0 105 unless required_args == 0 106 if check_arity == :warn 107 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}" 108 end 109 b = block 110 block = lambda{instance_exec(&b)} # Fallback 111 end 112 when 1 113 if required_args == 0 && optional_args == 0 && !rest 114 if check_arity == :warn 115 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}" 116 end 117 temp_method = roda_method_name("temp") 118 class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__) 119 alias_method meth, temp_method 120 undef_method temp_method 121 private meth 122 alias_method meth, meth 123 meth = :"#{meth}_arity" 124 elsif required_args > 1 125 if check_arity == :warn 126 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but multiple arguments required for #{block.inspect}" 127 end 128 b = block 129 block = lambda{|r| instance_exec(r, &b)} # Fallback 130 end 131 when :any 132 if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity) 133 if keyword 134 # Complexity of handling keyword arguments using define_method is too high, 135 # Fallback to instance_exec in this case. 136 b = block 137 block = if RUBY_VERSION >= '2.7' 138 eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback 139 else 140 # :nocov: 141 lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback 142 # :nocov: 143 end 144 else 145 arity_meth = meth 146 meth = :"#{meth}_arity" 147 end 148 end 149 else 150 raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}" 151 end 152 end 153 154 define_method(meth, &block) 155 private meth 156 alias_method meth, meth 157 158 if arity_meth 159 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth)) 160 max_args = required_args + optional_args 161 define_method(arity_meth) do |*a| 162 arity = a.length 163 if arity > required_args 164 if arity > max_args && !rest 165 if check_dynamic_arity == :warn 166 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}" 167 end 168 a = a.slice(0, max_args) 169 end 170 elsif arity < required_args 171 if check_dynamic_arity == :warn 172 RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}" 173 end 174 a.concat([nil] * (required_args - arity)) 175 end 176 177 send(meth, *a) 178 end 179 private arity_meth 180 alias_method arity_meth, arity_meth 181 end 182 183 call_meth 184 end
Expand the given path, using the root argument as the base directory.
# File lib/roda.rb 187 def expand_path(path, root=opts[:root]) 188 ::File.expand_path(path, root) 189 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 198 def freeze 199 return self if frozen? 200 201 unless opts[:subclassed] 202 # If the _roda_run_main_route instance method has not been overridden, 203 # make it an alias to _roda_main_route for performance 204 if instance_method(:_roda_run_main_route).owner == InstanceMethods 205 class_eval("alias _roda_run_main_route _roda_main_route") 206 end 207 self::RodaResponse.class_eval do 208 if instance_method(:set_default_headers).owner == ResponseMethods && 209 instance_method(:default_headers).owner == ResponseMethods 210 211 private 212 213 alias set_default_headers set_default_headers 214 def set_default_headers 215 @headers[RodaResponseHeaders::CONTENT_TYPE] ||= 'text/html' 216 end 217 end 218 end 219 220 if @middleware.empty? && use_new_dispatch_api? 221 plugin :direct_call 222 end 223 224 if RUBY_VERSION > "2.4" && ([: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} 225 plugin :_optimized_matching 226 end 227 end 228 229 build_rack_app 230 @opts.freeze 231 @plugins.freeze 232 @middleware.freeze 233 234 super 235 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 239 def include(*a) 240 res = super 241 def_roda_before 242 def_roda_after 243 res 244 end
When inheriting Roda, copy the shared data into the subclass, and setup the request and response subclasses.
# File lib/roda.rb 248 def inherited(subclass) 249 raise RodaError, "Cannot subclass a frozen Roda class" if frozen? 250 251 # Mark current class as having been subclassed, as some optimizations 252 # depend on the class not being subclassed 253 opts[:subclassed] = true 254 255 super 256 subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware) 257 subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : []) 258 subclass.instance_variable_set(:@plugins, @plugins.dup) 259 subclass.instance_variable_set(:@opts, opts.dup) 260 subclass.opts.delete(:subclassed) 261 subclass.opts.to_a.each do |k,v| 262 if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen? 263 subclass.opts[k] = v.dup 264 end 265 end 266 if block = @raw_route_block 267 subclass.route(&block) 268 end 269 270 request_class = Class.new(self::RodaRequest) 271 request_class.roda_class = subclass 272 request_class.match_pattern_cache = RodaCache.new 273 subclass.const_set(:RodaRequest, request_class) 274 275 response_class = Class.new(self::RodaResponse) 276 response_class.roda_class = subclass 277 subclass.const_set(:RodaResponse, response_class) 278 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 289 def plugin(plugin, *args, &block) 290 raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen? 291 plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol) 292 raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module) 293 294 if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block) 295 # RODA4: switch from warning to error 296 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.") 297 end 298 299 plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies) 300 @plugins << plugin unless @plugins.include?(plugin) 301 include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods) 302 extend(plugin::ClassMethods) if defined?(plugin::ClassMethods) 303 self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods) 304 self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods) 305 self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods) 306 self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods) 307 plugin.configure(self, *args, &block) if plugin.respond_to?(:configure) 308 @app = nil 309 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 327 def route(&block) 328 unless block 329 RodaPlugins.warn "no block passed to Roda.route" 330 return 331 end 332 333 @raw_route_block = block 334 @route_block = block = convert_route_block(block) 335 @rack_app_route_block = block = rack_app_route_block(block) 336 public define_roda_method(:_roda_main_route, 1, &block) 337 @app = nil 338 end
# File lib/roda.rb 214 def set_default_headers 215 @headers[RodaResponseHeaders::CONTENT_TYPE] ||= 'text/html' 216 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 344 def use(*args, &block) 345 @middleware << [args, block].freeze 346 @app = nil 347 end