class Controller
A middleware which loads controller classes and invokes functionality based on the requested path.
Nested
Definitions
CONTROLLER_RB = 'controller.rb'.freeze
The controller filename.
def initialize(app, root: Utopia::default_root, base: Controller::Base)
Implementation
def initialize(app, root: Utopia::default_root, base: Controller::Base)
@app = app
@root = root
@controller_cache = Concurrent::Map.new
@base = base
end
def lookup_controller(path)
Fetch the controller for the given relative path. May be cached.
Implementation
def lookup_controller(path)
@controller_cache.fetch_or_store(path.to_s) do
load_controller_file(path)
end
end
def load_controller_file(uri_path)
Loads the controller file for the given relative url_path.
Implementation
def load_controller_file(uri_path)
base_path = File.join(@root, uri_path.components)
controller_path = File.join(base_path, CONTROLLER_RB)
# puts "load_controller_file(#{path.inspect}) => #{controller_path}"
if File.exist?(controller_path)
klass = Class.new(@base)
# base_path is expected to be a string representing a filesystem path:
klass.const_set(:BASE_PATH, base_path.freeze)
# uri_path is expected to be an instance of Path:
klass.const_set(:URI_PATH, uri_path.dup.freeze)
klass.const_set(:CONTROLLER, self)
klass.class_eval(File.read(controller_path), controller_path)
# We lock down the controller class to prevent unsafe modifications:
klass.freeze
# Create an instance of the controller:
return klass.new
else
return nil
end
end
def invoke_controllers(request)
Invoke the controller layer for a given request. The request path may be rewritten.
Implementation
def invoke_controllers(request)
request_path = Path.from_string(request.path_info)
# The request path must be absolute. We could handle this internally but it is probably better for this to be an error:
raise ArgumentError.new("Invalid request path #{request_path}") unless request_path.absolute?
# The controller path contains the current complete path being evaluated:
controller_path = Path.new
# Controller instance variables which eventually get processed by the view:
variables = request.env[VARIABLES_KEY]
while request_path.components.any?
# We copy one path component from the relative path to the controller path at a time. The controller, when invoked, can modify the relative path (by assigning to relative_path.components). This allows for controller-relative rewrites, but only the remaining path postfix can be modified.
controller_path.components << request_path.components.shift
if controller = lookup_controller(controller_path)
# Don't modify the original controller:
controller = controller.clone
# Append the controller to the set of controller variables, updates the controller with all current instance variables.
variables << controller
if result = controller.process!(request, request_path)
return result
end
end
end
# Controllers can directly modify relative_path, which is copied into controller_path. The controllers may have rewriten the path so we update the path info:
request.env[Rack::PATH_INFO] = controller_path.to_s
# No controller gave a useful result:
return nil
end