lib/roda.rb in roda-3.16.0 vs lib/roda.rb in roda-3.17.0
- old
+ new
@@ -58,11 +58,13 @@
@app = nil
@inherit_middleware = true
@middleware = []
@opts = {}
+ @raw_route_block = nil
@route_block = nil
+ @rack_app_route_block = nil
# Module in which all Roda plugins should be stored. Also contains logic for
# registering and loading plugins.
module RodaPlugins
OPTS = {}.freeze
@@ -159,10 +161,19 @@
@opts.freeze
@middleware.freeze
super
end
+ # Rebuild the _roda_before and _roda_after methods whenever a plugin might
+ # have added a _roda_before_* or _roda_after_* method.
+ def include(*a)
+ res = super
+ def_roda_before
+ def_roda_after
+ res
+ end
+
# When inheriting Roda, copy the shared data into the subclass,
# and setup the request and response subclasses.
def inherited(subclass)
raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
super
@@ -172,12 +183,13 @@
subclass.opts.to_a.each do |k,v|
if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
subclass.opts[k] = v.dup
end
end
- subclass.instance_variable_set(:@route_block, @route_block)
- subclass.send(:build_rack_app)
+ if block = @raw_route_block
+ subclass.route(&block)
+ end
request_class = Class.new(self::RodaRequest)
request_class.roda_class = subclass
request_class.match_pattern_cache = RodaCache.new
subclass.const_set(:RodaRequest, request_class)
@@ -219,11 +231,13 @@
# end
#
# This should only be called once per class, and if called multiple
# times will overwrite the previous routing.
def route(&block)
- @route_block = block
+ @raw_route_block = block
+ @route_block = block = convert_route_block(block)
+ @rack_app_route_block = rack_app_route_block(block)
build_rack_app
end
# Add a middleware to use for the rack application. Must be
# called before calling #route to have an effect. Example:
@@ -236,27 +250,69 @@
private
# Build the rack app to use
def build_rack_app
- if block = @route_block
- block = rack_app_route_block(block)
+ if block = @rack_app_route_block
app = lambda{|env| new(env).call(&block)}
@middleware.reverse_each do |args, bl|
mid, *args = args
app = mid.new(app, *args, &bl)
app.freeze if opts[:freeze_middleware]
end
@app = app
end
end
- # The route block to use when building the rack app.
+ # Modify the route block to use for any route block provided as input,
+ # which can include route blocks that are delegated to by the main route block.
# Can be modified by plugins.
- def rack_app_route_block(block)
+ def convert_route_block(block)
block
end
+
+ # Build a _roda_before method that calls each _roda_before_* method
+ # in order, if any _roda_before_* methods are defined. Also, rebuild
+ # the route block if a _roda_before method is defined.
+ def def_roda_before
+ meths = private_instance_methods.grep(/\A_roda_before_\d\d/).sort.join(';')
+ unless meths.empty?
+ class_eval("def _roda_before; #{meths} end", __FILE__, __LINE__)
+ private :_roda_before
+ if @raw_route_block
+ route(&@raw_route_block)
+ end
+ end
+ end
+
+ # Build a _roda_after method that calls each _roda_after_* method
+ # in order, if any _roda_after_* methods are defined. Also, use
+ # the internal after hook plugin if the _roda_after method is defined.
+ def def_roda_after
+ meths = private_instance_methods.grep(/\A_roda_after_\d\d/).sort.map{|s| "#{s}(res)"}.join(';')
+ unless meths.empty?
+ plugin :_after_hook unless private_method_defined?(:_roda_after)
+ class_eval("def _roda_after(res); #{meths} end", __FILE__, __LINE__)
+ private :_roda_after
+ end
+ end
+
+ # The route block to use when building the rack app (or other initial
+ # entry point to the route block).
+ # By default, modifies the rack app route block to support before hooks
+ # if any before hooks are defined.
+ # Can be modified by plugins.
+ def rack_app_route_block(block)
+ if private_method_defined?(:_roda_before)
+ lambda do |r|
+ _roda_before
+ instance_exec(r, &block)
+ end
+ else
+ block
+ end
+ end
end
# Instance methods for the Roda class.
#
# In addition to the listed methods, the following two methods are available:
@@ -934,11 +990,10 @@
# code for non-empty responses and a 404 code for empty responses.
attr_accessor :status
# Set the default headers when creating a response.
def initialize
- @status = nil
@headers = {}
@body = []
@length = 0
end
@@ -984,17 +1039,21 @@
# # => [200,
# # {'Content-Type'=>'text/html', 'Content-Length'=>'0'},
# # []]
def finish
b = @body
- empty = b.empty?
- s = (@status ||= empty ? 404 : default_status)
set_default_headers
h = @headers
- if empty && (s == 304 || s == 204 || s == 205 || (s >= 100 && s <= 199))
- h.delete("Content-Type")
+ if b.empty?
+ s = @status || 404
+ if (s == 304 || s == 204 || s == 205 || (s >= 100 && s <= 199))
+ h.delete("Content-Type")
+ else
+ h["Content-Length"] ||= '0'
+ end
else
+ s = @status || default_status
h["Content-Length"] ||= @length.to_s
end
[s, h, b]
end