module Roda::RodaPlugins::Base::ClassMethods

  1. lib/roda.rb

Class methods for the Roda class.

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

app()

The rack application that this class uses.

[show source]
   # File lib/roda.rb
33 def app
34   @app || build_rack_app
35 end
call(env)

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.

[show source]
   # File lib/roda.rb
52 def call(env)
53   app.call(env)
54 end
clear_middleware!()

Clear the middleware stack

[show source]
   # File lib/roda.rb
57 def clear_middleware!
58   @middleware.clear
59   @app = nil
60 end
define_roda_method(meth, expected_arity, &block)

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.

[show source]
    # 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_path(path, root=opts[:root])

Expand the given path, using the root argument as the base directory.

[show source]
    # File lib/roda.rb
179 def expand_path(path, root=opts[:root])
180   ::File.expand_path(path, root)
181 end
freeze()

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.

[show source]
    # 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
include(*a)

Rebuild the _roda_before and _roda_after methods whenever a plugin might have added a roda_before* or roda_after* method.

[show source]
    # File lib/roda.rb
230 def include(*a)
231   res = super
232   def_roda_before
233   def_roda_after
234   res
235 end
inherited(subclass)

When inheriting Roda, copy the shared data into the subclass, and setup the request and response subclasses.

[show source]
    # 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
plugin(plugin, *args, &block)

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
[show source]
    # 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
route(&block)

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.

[show source]
    # 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
set_default_headers()
[show source]
    # File lib/roda.rb
206 def set_default_headers
207   @headers['Content-Type'] ||= 'text/html'
208 end
use(*args, &block)

Add a middleware to use for the rack application. Must be called before calling route to have an effect. Example:

Roda.use Rack::ShowExceptions
[show source]
    # File lib/roda.rb
333 def use(*args, &block)
334   @middleware << [args, block].freeze
335   @app = nil
336 end