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
121 def app
122   @app || build_rack_app
123 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
140 def call(env)
141   app.call(env)
142 end
clear_middleware! ()

Clear the middleware stack

[show source]
    # File lib/roda.rb
145 def clear_middleware!
146   @middleware.clear
147   @app = nil
148 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
173 def define_roda_method(meth, expected_arity, &block)
174   if meth.is_a?(String)
175     meth = roda_method_name(meth)
176   end
177   call_meth = meth
178 
179   if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
180     required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
181 
182     if keyword == :required && (expected_arity == 0 || expected_arity == 1)
183       raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}"
184     end
185 
186     case expected_arity
187     when 0
188       unless required_args == 0
189         if check_arity == :warn
190           RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}"
191         end
192         b = block
193         block = lambda{instance_exec(&b)} # Fallback
194       end
195     when 1
196       if required_args == 0 && optional_args == 0 && !rest
197         if check_arity == :warn
198           RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}"
199         end
200         temp_method = roda_method_name("temp")
201         class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__)
202         alias_method meth, temp_method
203         undef_method temp_method
204         private meth
205         meth = :"#{meth}_arity"
206       elsif required_args > 1
207         b = block
208         block = lambda{|r| instance_exec(r, &b)} # Fallback
209       end
210     when :any
211       if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity)
212         if keyword
213           # Complexity of handling keyword arguments using define_method is too high,
214           # Fallback to instance_exec in this case.
215           b = block
216           block = lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
217         else
218           arity_meth = meth
219           meth = :"#{meth}_arity"
220         end
221       end
222     else
223       raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}"
224     end
225   end
226 
227   define_method(meth, &block)
228   private meth
229 
230   if arity_meth
231     required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth))
232     max_args = required_args + optional_args
233     define_method(arity_meth) do |*a|
234       arity = a.length
235       if arity > required_args
236         if arity > max_args && !rest
237           if check_dynamic_arity == :warn
238             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}"
239           end
240           a = a.slice(0, max_args)
241         end
242       elsif arity < required_args
243         if check_dynamic_arity == :warn
244           RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}"
245         end
246         a.concat([nil] * (required_args - arity))
247       end
248 
249       send(meth, *a)
250     end
251     private arity_meth
252   end
253 
254   call_meth
255 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
258 def expand_path(path, root=opts[:root])
259   ::File.expand_path(path, root)
260 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
269 def freeze
270   return self if frozen?
271 
272   unless opts[:subclassed]
273     # If the _roda_run_main_route instance method has not been overridden,
274     # make it an alias to _roda_main_route for performance
275     if instance_method(:_roda_run_main_route).owner == InstanceMethods
276       class_eval("alias _roda_run_main_route _roda_main_route")
277     end
278     self::RodaResponse.class_eval do
279       if instance_method(:set_default_headers).owner == ResponseMethods &&
280          instance_method(:default_headers).owner == ResponseMethods
281 
282         def set_default_headers
283           @headers['Content-Type'] ||= 'text/html'
284         end
285       end
286     end
287 
288     if @middleware.empty? && use_new_dispatch_api?
289       plugin :direct_call
290     end
291   end
292 
293   build_rack_app
294   @opts.freeze
295   @middleware.freeze
296 
297   super
298 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
302 def include(*a)
303   res = super
304   def_roda_before
305   def_roda_after
306   res
307 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
311 def inherited(subclass)
312   raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
313 
314   # Mark current class as having been subclassed, as some optimizations
315   # depend on the class not being subclassed
316   opts[:subclassed] = true
317 
318   super
319   subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
320   subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
321   subclass.instance_variable_set(:@opts, opts.dup)
322   subclass.opts.delete(:subclassed)
323   subclass.opts.to_a.each do |k,v|
324     if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
325       subclass.opts[k] = v.dup
326     end
327   end
328   if block = @raw_route_block
329     subclass.route(&block)
330   end
331   
332   request_class = Class.new(self::RodaRequest)
333   request_class.roda_class = subclass
334   request_class.match_pattern_cache = RodaCache.new
335   subclass.const_set(:RodaRequest, request_class)
336 
337   response_class = Class.new(self::RodaResponse)
338   response_class.roda_class = subclass
339   subclass.const_set(:RodaResponse, response_class)
340 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 represented a registered plugin which will be required and then used. Returns nil.

Roda.plugin PluginModule
Roda.plugin :csrf
[show source]
    # File lib/roda.rb
348 def plugin(plugin, *args, &block)
349   raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen?
350   plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol)
351   plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
352   include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
353   extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
354   self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods)
355   self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods)
356   self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods)
357   self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods)
358   plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
359   @app = nil
360 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
375 def route(&block)
376   unless block
377     RodaPlugins.warn "no block passed to Roda.route"
378     return
379   end
380 
381   @raw_route_block = block
382   @route_block = block = convert_route_block(block)
383   @rack_app_route_block = block = rack_app_route_block(block)
384   public define_roda_method(:_roda_main_route, 1, &block)
385   @app = nil
386 end
set_default_headers ()
[show source]
    # File lib/roda.rb
282 def set_default_headers
283   @headers['Content-Type'] ||= 'text/html'
284 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
392 def use(*args, &block)
393   @middleware << [args, block].freeze
394   @app = nil
395 end