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 38 def app 39 @app || build_rack_app 40 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 60 def call(env) 61 app.call(env) 62 end
Clear the middleware stack
# File lib/roda.rb 65 def clear_middleware! 66 @middleware.clear 67 @app = nil 68 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 98 def define_roda_method(meth, expected_arity, &block) 99 if meth.is_a?(String) 100 meth = roda_method_name(meth) 101 end 102 call_meth = meth 103 104 # RODA4: Remove support for :check_arity, default to false behavior 105 if (check_arity = opts.fetch(:check_arity, :warn)) && !block.lambda? 106 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block) 107 108 if keyword == :required && (expected_arity == 0 || expected_arity == 1) 109 raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}" 110 end 111 112 case expected_arity 113 when 0 114 unless required_args == 0 115 if check_arity == :warn 116 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}" 117 end 118 b = block 119 block = lambda{instance_exec(&b)} # Fallback 120 end 121 when 1 122 if required_args == 0 && optional_args == 0 && !rest 123 if check_arity == :warn 124 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}" 125 end 126 temp_method = roda_method_name("temp") 127 class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__) 128 alias_method meth, temp_method 129 undef_method temp_method 130 private meth 131 alias_method meth, meth 132 meth = :"#{meth}_arity" 133 elsif required_args > 1 134 if check_arity == :warn 135 RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but multiple arguments required for #{block.inspect}" 136 end 137 b = block 138 block = lambda{|r| instance_exec(r, &b)} # Fallback 139 end 140 when :any 141 if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity) 142 if keyword 143 # Complexity of handling keyword arguments using define_method is too high, 144 # Fallback to instance_exec in this case. 145 b = block 146 block = if RUBY_VERSION >= '2.7' 147 lambda{|*a, **kw| instance_exec(*a, **kw, &b)} # Keyword arguments fallback 148 else 149 # simplecov:disable 150 lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback 151 # simplecov:enable 152 end 153 else 154 arity_meth = meth 155 meth = :"#{meth}_arity" 156 end 157 end 158 else 159 raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}" 160 end 161 end 162 163 define_method(meth, &block) 164 private meth 165 alias_method meth, meth 166 167 if arity_meth 168 required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth)) 169 max_args = required_args + optional_args 170 define_method(arity_meth) do |*a| 171 arity = a.length 172 if arity > required_args 173 if arity > max_args && !rest 174 if check_dynamic_arity == :warn 175 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}" 176 end 177 a = a.slice(0, max_args) 178 end 179 elsif arity < required_args 180 if check_dynamic_arity == :warn 181 RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}" 182 end 183 a.concat([nil] * (required_args - arity)) 184 end 185 186 send(meth, *a) 187 end 188 private arity_meth 189 alias_method arity_meth, arity_meth 190 end 191 192 call_meth 193 end
Expand the given path, using the root argument as the base directory.
# File lib/roda.rb 196 def expand_path(path, root=app_root) 197 ::File.expand_path(path, root) 198 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 207 def freeze 208 return self if frozen? 209 210 unless opts[:subclassed] 211 # If the _roda_run_main_route instance method has not been overridden, 212 # make it an alias to _roda_main_route for performance 213 if instance_method(:_roda_run_main_route).owner == InstanceMethods 214 alias_method :_roda_run_main_route, :_roda_main_route 215 end 216 self::RodaResponse.class_exec do 217 if instance_method(:set_default_headers).owner == ResponseMethods && 218 instance_method(:default_headers).owner == ResponseMethods 219 220 private 221 222 alias set_default_headers set_default_headers 223 def set_default_headers 224 @headers[RodaResponseHeaders::CONTENT_TYPE] ||= 'text/html' 225 end 226 end 227 end 228 229 if @middleware.empty? && use_new_dispatch_api? 230 plugin :direct_call 231 end 232 233 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, :always]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods} 234 plugin :_optimized_matching 235 end 236 end 237 238 build_rack_app 239 @opts.freeze 240 @plugins.freeze 241 @middleware.freeze 242 243 super 244 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 248 def include(*a) 249 res = super 250 def_roda_before 251 def_roda_after 252 res 253 end
When inheriting Roda, copy the shared data into the subclass, and setup the request and response subclasses.
# File lib/roda.rb 257 def inherited(subclass) 258 raise RodaError, "Cannot subclass a frozen Roda class" if frozen? 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 265 opts = RodaOpts.new 266 opts.roda_class = subclass 267 @opts.each do |k,v| 268 opts[k] = if k == :subclassed 269 false 270 elsif (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen? 271 v.dup 272 else 273 v 274 end 275 end 276 subclass.instance_variable_set(:@opts, opts) 277 278 # Mark current class as having been subclassed, as some optimizations 279 # depend on the class not being subclassed 280 @opts[:subclassed] = true 281 282 if block = @raw_route_block 283 subclass.route(&block) 284 end 285 286 request_class = Class.new(self::RodaRequest) 287 request_class.roda_class = subclass 288 request_class.match_pattern_cache = RodaCache.new 289 subclass.const_set(:RodaRequest, request_class) 290 291 response_class = Class.new(self::RodaResponse) 292 response_class.roda_class = subclass 293 subclass.const_set(:RodaResponse, response_class) 294 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 305 def plugin(plugin, *args, &block) 306 raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen? 307 plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol) 308 raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module) 309 310 if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block) 311 # RODA4: switch from warning to error 312 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.") 313 end 314 315 plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies) 316 @plugins << plugin unless @plugins.include?(plugin) 317 include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods) 318 extend(plugin::ClassMethods) if defined?(plugin::ClassMethods) 319 self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods) 320 self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods) 321 self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods) 322 self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods) 323 plugin.configure(self, *args, &block) if plugin.respond_to?(:configure) 324 @app = nil 325 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 343 def route(&block) 344 unless block 345 RodaPlugins.warn "no block passed to Roda.route" 346 return 347 end 348 349 @raw_route_block = block 350 @route_block = block = convert_route_block(block) 351 @rack_app_route_block = block = rack_app_route_block(block) 352 public define_roda_method(:_roda_main_route, 1, &block) 353 @app = nil 354 end
# File lib/roda.rb 223 def set_default_headers 224 @headers[RodaResponseHeaders::CONTENT_TYPE] ||= 'text/html' 225 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 360 def use(*args, &block) 361 @middleware << [args, block].freeze 362 @app = nil 363 end