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