Class: Utopia::Controller::Middleware

Inherits:
Protocol::HTTP::Middleware
  • Object
show all
Defined in:
lib/utopia/controller/middleware.rb

Overview

Dispatches requests to filesystem-backed controller classes.

Constant Summary collapse

CONTROLLER_RB =

The controller filename.

"controller.rb".freeze

Instance Method Summary collapse

Constructor Details

#initialize(app, root: Utopia::default_root, base: Controller::Base) ⇒ Middleware

Returns a new instance of Middleware.

Parameters:

  • root (String) (defaults to: Utopia::default_root)

    The content root where controllers will be loaded from.

  • base (Class) (defaults to: Controller::Base)

    The base class for controllers.



29
30
31
32
33
34
35
36
37
# File 'lib/utopia/controller/middleware.rb', line 29

def initialize(app, root: Utopia::default_root, base: Controller::Base)
	super(app)
	
	@root = root
	
	@controller_cache = Concurrent::Map.new
	
	@base = base
end

Instance Method Details

#call(request) ⇒ Object

Attach controller variables while processing the request.



127
128
129
130
131
132
133
134
135
# File 'lib/utopia/controller/middleware.rb', line 127

def call(request)
	request.variables ||= Variables.new
	
	if result = invoke_controllers(request)
		return Utopia::Response.wrap(result)
	end
	
	return @delegate.call(request)
end

#freezeObject

Freeze this object and its internal state.



41
42
43
44
45
46
47
48
# File 'lib/utopia/controller/middleware.rb', line 41

def freeze
	return self if frozen?
	
	@root.freeze
	@base.freeze
	
	super
end

#invoke_controllers(request) ⇒ Object

Invoke the controller layer for a given request. The request path may be rewritten.

Raises:

  • (ArgumentError)


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/utopia/controller/middleware.rb', line 88

def invoke_controllers(request)
	request_path = request.path
	
	# 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.variables
	
	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 the remaining path. Preserve those rewrites at the request boundary:
	request.path = controller_path
	
	# No controller gave a useful result:
	return nil
end

#load_controller_file(uri_path) ⇒ Object

Loads the controller file for the given relative url_path.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/utopia/controller/middleware.rb', line 58

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

#lookup_controller(path) ⇒ Object

Fetch the controller for the given relative path. May be cached.



51
52
53
54
55
# File 'lib/utopia/controller/middleware.rb', line 51

def lookup_controller(path)
	@controller_cache.fetch_or_store(path.to_s) do
		load_controller_file(path)
	end
end