Class: Utopia::Controller::Middleware

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

Constant Summary collapse

CONTROLLER_RB =

The controller filename.

"controller.rb".freeze

Instance Attribute Summary collapse

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.



26
27
28
29
30
31
32
33
# File 'lib/utopia/controller/middleware.rb', line 26

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

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



35
36
37
# File 'lib/utopia/controller/middleware.rb', line 35

def app
  @app
end

Instance Method Details

#call(env) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/utopia/controller/middleware.rb', line 120

def call(env)
  env[VARIABLES_KEY] ||= Variables.new
  
  request = Rack::Request.new(env)
  
  if result = invoke_controllers(request)
    return result
  end
  
  return @app.call(env)
end

#freezeObject



37
38
39
40
41
42
43
44
# File 'lib/utopia/controller/middleware.rb', line 37

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)


84
85
86
87
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
# File 'lib/utopia/controller/middleware.rb', line 84

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

#load_controller_file(uri_path) ⇒ Object

Loads the controller file for the given relative url_path.



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

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.



47
48
49
50
51
# File 'lib/utopia/controller/middleware.rb', line 47

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