Class: Ramaze::Controller
- Includes:
- Helper::Methods
- Defined in:
- lib/ramaze/controller.rb,
lib/ramaze/controller/error.rb,
lib/ramaze/controller/resolve.rb
Overview
The Controller is responsible for combining and rendering actions.
Direct Known Subclasses
Constant Summary collapse
- FILTER =
TODO:
* fix caching, see todos below FILTER = [ :cached, :default ] unless defined?(FILTER)
[ :default ]
Class Method Summary collapse
-
.action_methods ⇒ Object
methodnames that may be used for current controller.
-
.action_modules ⇒ Object
Array of all modules (so including Ramaze helpers) that are included in this controller and where the module is also in the Helper::LOOKUP set.
-
.at(mapping) ⇒ Object
Returns the Controller at a mapped path.
-
.cached(path) ⇒ Object
Default element of FILTER.
-
.cached_action_methods ⇒ Object
List or create a list of action methods to be cached.
-
.check_path(message, *paths) ⇒ Object
checks paths for existance and logs a warning if it doesn’t exist yet.
-
.current ⇒ Object
Return Controller of current Action.
-
.default(path) ⇒ Object
Default element of FILTER.
-
.deny_layout(*actions) ⇒ Object
Deny layout for passed names of actions.
-
.engine(name) ⇒ Object
This is a method to specify the templating engine for your controller.
-
.extension_order ⇒ Object
Uses custom defined engines and all available engines and throws it against the extensions for the template to find the most likely templating-engine to use ordered by priority and likelyhood.
-
.handle(path) ⇒ Object
Entering point for Dispatcher, first Controller::resolve(path) and then renders the resulting Action.
-
.inherited(controller) ⇒ Object
When Controller is subclassed the resulting class is placed in Global.controllers and a new trait :actions_cached is set on it.
-
.layout(*meth_or_hash) ⇒ Object
Define a layout for all actions on this controller.
-
.map(*syms) ⇒ Object
Map Controller to the given syms or strings.
-
.mapping ⇒ Object
if trait is set and controller is not in Global.mapping yet this will build a new default mapping-point, MainController is put at ‘/’ by default.
-
.patterns_for(path) ⇒ Object
Iterator that yields potential ways in which a given path could be mapped to controller, action and params.
-
.raise_no_action(controller, path) ⇒ Object
Raises Ramaze::Error::NoAction.
-
.raise_no_controller(path) ⇒ Object
Raises Ramaze::Error::NoController.
-
.raise_no_filter(path) ⇒ Object
Raises Ramaze::Error::NoFilter TODO: * is this called at all for anybody? I think everybody has filters.
-
.relevant_ancestors(parent = Ramaze::Controller) ⇒ Object
By default, returns all ancestors of current Controller that have Ramaze::Controller as their ancestor.
-
.resolve(path, routed = false) ⇒ Object
Resolve an absolute path in the application by passing it to each element of Ramaze::Controller::FILTER.
-
.resolve_action(path, *parameter) ⇒ Object
Try to produce an Action from the given path and paremters with the appropiate template if one exists.
-
.resolve_method(name, *params) ⇒ Object
Based on methodname and arity, tries to find the right method on current controller.
-
.resolve_template(path) ⇒ Object
Search the #template_paths for a fitting template for path.
-
.startup(options = {}) ⇒ Object
called from Ramaze.startup, adds Cache.actions and Cache.patterns, walks all controllers subclassed so far and adds them to the Global.mapping if they are not assigned yet.
-
.template(this, *argv) ⇒ Object
This is used for template rerouting, takes action, optionally a controller and action to route to.
-
.template_paths ⇒ Object
Composes an array with the template-paths to look up in the right order.
-
.view_root(*args) ⇒ Object
Define a view_root for Controller, returns the current view_root if no argument is given.
Instance Method Summary collapse
-
#error ⇒ Object
The default error-page handler.
Methods included from Helper::Methods
extend_object, #helper, included
Class Method Details
.action_methods ⇒ Object
methodnames that may be used for current controller.
194 195 196 197 198 199 200 201 |
# File 'lib/ramaze/controller/resolve.rb', line 194 def action_methods ancs = relevant_ancestors + action_modules ancs.reverse.inject [] do |meths, anc| meths + anc.public_instance_methods(false).map{|im| im.to_s } - anc.private_instance_methods(false).map{|im| im.to_s } end end |
.action_modules ⇒ Object
Array of all modules (so including Ramaze helpers) that are included in this controller and where the module is also in the Helper::LOOKUP set. Hence this is the included modules whose public methods may be exposed as actions of this controller.
207 208 209 |
# File 'lib/ramaze/controller/resolve.rb', line 207 def action_modules Helper::LOOKUP.find_all {|mod| self.include?(mod)} end |
.at(mapping) ⇒ Object
Returns the Controller at a mapped path.
99 100 101 |
# File 'lib/ramaze/controller.rb', line 99 def at(mapping) Global.mapping[mapping.to_s] end |
.cached(path) ⇒ Object
Default element of FILTER. Looks up the path in Cache.resolved and returns it if found.
36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/ramaze/controller/resolve.rb', line 36 def cached(path) if found = Cache.resolved[path] if found.respond_to?(:relaxed_hash) return found.dup else Log.warn("Found faulty `#{path}' in Cache.resolved, deleting it for sanity.") Cache.resolved.delete path end end nil end |
.cached_action_methods ⇒ Object
List or create a list of action methods to be cached
189 190 191 |
# File 'lib/ramaze/controller/resolve.rb', line 189 def cached_action_methods Cache.action_methods[self] ||= action_methods end |
.check_path(message, *paths) ⇒ Object
checks paths for existance and logs a warning if it doesn’t exist yet.
62 63 64 65 66 |
# File 'lib/ramaze/controller.rb', line 62 def check_path(, *paths) paths.each do |path| Log.warn( % path) unless File.directory?(path) end end |
.current ⇒ Object
Return Controller of current Action
255 256 257 258 |
# File 'lib/ramaze/controller.rb', line 255 def current action = Action.current action.instance || action.controller end |
.default(path) ⇒ Object
Default element of FILTER. The default handler that tries to find the best match for the given path in terms of Controller/method/template and given arguments. If a match is found it will be cached for further use.
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/ramaze/controller/resolve.rb', line 54 def default(path) mapping = Global.mapping controllers = Global.controllers raise_no_controller(path) if controllers.empty? or mapping.empty? # TODO: # * following code is dangerous (DoS): # patterns = Cache.patterns[path] ||= pattern_for(path) first_controller = nil # Look through the possible ways of interpreting the path until we find # one that matches an existing controller action. patterns_for(path) do |controller, method, params| if controller = mapping[controller] first_controller ||= controller action = controller.resolve_action(method, *params) next unless action template = action.template valid_action = if Action.stack.size > 0 || Global.actionless_templates action.method or (params.empty? && template) else action.method end if valid_action # TODO: # * dangerous as well # Cache.resolved[path] = action return action end end end if !Thread.current[:routed] and new_path = Route.resolve(path) Log.dev("Routing from `#{path}' to `#{new_path}'") return resolve(new_path, true) end raise_no_action(first_controller, path) if first_controller raise_no_controller(path) end |
.deny_layout(*actions) ⇒ Object
Deny layout for passed names of actions. Name should be name of the method or template without extension, as String or Symbol
Usage:
class MainController < Ramaze::Controller
deny_layout :atom
def atom
"I won't have layout"
end
end
146 147 148 149 150 |
# File 'lib/ramaze/controller.rb', line 146 def deny_layout(*actions) actions.each do |action| layout[:deny] << action.to_s end end |
.engine(name) ⇒ Object
This is a method to specify the templating engine for your controller. It basically just is sugar for:
trait :engine => Haml
Usage:
class MainController < Ramaze::Controller
engine :Haml
end
244 245 246 247 248 249 250 251 |
# File 'lib/ramaze/controller.rb', line 244 def engine(name) name = Template.const_get(name) rescue NameError => ex Log.warn ex Log.warn "Try to use passed engine directly" ensure trait :engine => name end |
.extension_order ⇒ Object
Uses custom defined engines and all available engines and throws it against the extensions for the template to find the most likely templating-engine to use ordered by priority and likelyhood.
243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/ramaze/controller/resolve.rb', line 243 def extension_order t_extensions = Template::ENGINES all_extensions = t_extensions.values.flatten if engine = ancestral_trait[:engine] c_extensions = t_extensions.select{|k,v| k == engine}.map{|k,v| v}.flatten return (c_extensions + all_extensions).uniq end all_extensions end |
.handle(path) ⇒ Object
Entering point for Dispatcher, first Controller::resolve(path) and then renders the resulting Action.
263 264 265 266 267 |
# File 'lib/ramaze/controller.rb', line 263 def handle path action = resolve(path) STATE[:controller] = action.controller action.render end |
.inherited(controller) ⇒ Object
When Controller is subclassed the resulting class is placed in Global.controllers and a new trait :actions_cached is set on it.
36 37 38 39 40 41 42 43 |
# File 'lib/ramaze/controller.rb', line 36 def inherited controller controller.trait :actions_cached => {} Global.controllers << controller if map = controller.mapping Log.dev("mapping #{map} => #{controller}") Global.mapping[map] ||= controller end end |
.layout(*meth_or_hash) ⇒ Object
Define a layout for all actions on this controller
Example:
class Foo < Ramaze::Controller
layout :foo
end
This defines the action :foo to be layout of the controller and will
render the layout after any other action has been rendered, assigns
@content to the result of the action and then goes on rendering
the layout-action where @content may or may not be used, returning
whatever the layout returns.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/ramaze/controller.rb', line 116 def layout(*meth_or_hash) if meth_or_hash.empty? trait[:layout] ||= ( ancestral_trait[:layout] || {:all => nil, :deny => Set.new} ).dup else meth_or_hash = meth_or_hash.first if meth_or_hash.respond_to?(:to_hash) meth_or_hash.each do |layout_name, *actions| actions.flatten.each do |action| layout[action.to_s] = layout_name end end else layout[:all] = meth_or_hash end end end |
.map(*syms) ⇒ Object
Map Controller to the given syms or strings. Replaces old mappings. If you want to add a mapping, just modify Global.mapping.
89 90 91 92 93 94 95 |
# File 'lib/ramaze/controller.rb', line 89 def map(*syms) Global.mapping.delete_if{|k,v| v == self} syms.compact.each do |sym| Global.mapping[sym.to_s] = self end end |
.mapping ⇒ Object
if trait is set and controller is not in Global.mapping yet this will build a new default mapping-point, MainController is put at ‘/’ by default. For other Class names, String#snake_case is called, e.g. FooBarController is mapped at ‘/foo_bar’.
73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/ramaze/controller.rb', line 73 def mapping global_mapping = Global.mapping.invert[self] return global_mapping if global_mapping if ancestral_trait[:automap] && self.to_s !~ /#<Class:/ name = self.to_s.gsub('Controller', '').gsub('::', '/').clone return if name.empty? name == 'Main' ? '/' : "/#{name.snake_case}" end end |
.patterns_for(path) ⇒ Object
Iterator that yields potential ways in which a given path could be mapped to controller, action and params. It produces them in strict order, with longest controller path favoured, then longest action path.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/ramaze/controller/resolve.rb', line 215 def patterns_for path # Split into fragments, and remove empty ones (which split may have output). # The to_s is vital as sometimes we are passed an array. fragments = path.to_s.split '/' fragments.delete '' # Work through all the possible splits of controller and 'the rest' (action # + params) starting with longest possible controller. fragments.length.downto(0) do |ca_split| controller = '/' + fragments[0...ca_split].join('/') # Work on the remaining portion, generating all the action/params splits. fragments.length.downto(ca_split) do |ap_split| action = fragments[ca_split...ap_split].join '__' params = fragments[ap_split..-1] if action.empty? yield controller, 'index', params else yield controller, "#{action}__index", params yield controller, action, params end end end end |
.raise_no_action(controller, path) ⇒ Object
Raises Ramaze::Error::NoAction
272 273 274 275 |
# File 'lib/ramaze/controller/resolve.rb', line 272 def raise_no_action(controller, path) STATE[:controller] = controller raise Ramaze::Error::NoAction, "No Action found for `#{path}' on #{controller}" end |
.raise_no_controller(path) ⇒ Object
Raises Ramaze::Error::NoController
266 267 268 |
# File 'lib/ramaze/controller/resolve.rb', line 266 def raise_no_controller(path) raise Ramaze::Error::NoController, "No Controller found for `#{path}'" end |
.raise_no_filter(path) ⇒ Object
Raises Ramaze::Error::NoFilter TODO:
* is this called at all for anybody?
I think everybody has filters.
260 261 262 |
# File 'lib/ramaze/controller/resolve.rb', line 260 def raise_no_filter(path) raise Ramaze::Error::NoFilter, "No Filter found for `#{path}'" end |
.relevant_ancestors(parent = Ramaze::Controller) ⇒ Object
By default, returns all ancestors of current Controller that have Ramaze::Controller as their ancestor. Optional argument parent can be used return ancestors that have parent as an ancestor.
273 274 275 276 277 |
# File 'lib/ramaze/controller.rb', line 273 def relevant_ancestors(parent = Ramaze::Controller) ancestors.select do |anc| anc.ancestors.include?(parent) end end |
.resolve(path, routed = false) ⇒ Object
Resolve an absolute path in the application by passing it to each element of Ramaze::Controller::FILTER. If an element does not respond to call it will be sent to self instead, in either case with path as argument.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/ramaze/controller/resolve.rb', line 18 def resolve(path, routed = false) Thread.current[:routed] = routed FILTER.each do |filter| answer = if filter.respond_to?(:call) filter.call(path) else send(filter.to_s, path) end return answer if answer end raise_no_filter(path) end |
.resolve_action(path, *parameter) ⇒ Object
Try to produce an Action from the given path and paremters with the appropiate template if one exists.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/ramaze/controller/resolve.rb', line 105 def resolve_action(path, *parameter) path, parameter = path.to_s, parameter.map{|e| e.to_s} # Use ancestral_trait so if template is set in superclass, it is still found. if info = ancestral_trait["#{path}_template"] template = info[:file] unless template controller, action = info.values_at :controller, :action # Controller may not have been explicitly set, in which case use self. controller ||= self template = controller.resolve_template(action) end end method, params = resolve_method(path, *parameter) if method or parameter.empty? template ||= resolve_template(path) end action = Action.create :path => path, :method => method, :params => params, :template => template, :controller => self return false unless action.valid_rest? action end |
.resolve_method(name, *params) ⇒ Object
Based on methodname and arity, tries to find the right method on current controller.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/ramaze/controller/resolve.rb', line 167 def resolve_method(name, *params) cam = cached_action_methods if cam.include?(name) method = name else name = name.gsub(/__/, '/') method = name if cam.include?(name) end if method arity = instance_method(method).arity if arity < 0 or params.size == arity return method, params end end return nil, [] end |
.resolve_template(path) ⇒ Object
Search the #template_paths for a fitting template for path. Only the first found possibility for the generated glob is returned.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/ramaze/controller/resolve.rb', line 137 def resolve_template(path) path = path.to_s path_converted = path.split('__').inject{|s,v| File.join(s, v) } possible_paths = [path, path_converted].compact paths = template_paths.map{|pa| possible_paths.map{|a| File.join(pa, a) } }.flatten.uniq glob = "{#{paths.join(',')}}.{#{extension_order.join(',')}}" Dir[glob].first end |
.startup(options = {}) ⇒ Object
called from Ramaze.startup, adds Cache.actions and Cache.patterns, walks all controllers subclassed so far and adds them to the Global.mapping if they are not assigned yet.
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/ramaze/controller.rb', line 49 def startup = {} Log.dev("found Controllers: #{Global.controllers.inspect}") check_path("Public root: '%s' doesn't exist", Global.public_root) check_path("View root: '%s' doesn't exist", Global.view_root) require 'ramaze/controller/main' if Global.mapping.empty? Log.debug("mapped Controllers: #{Global.mapping.inspect}") end |
.template(this, *argv) ⇒ Object
This is used for template rerouting, takes action, optionally a controller and action to route to.
Usage:
class MainController
template :index, OtherController, :list
template :foo, :bar
template :bar, :file => '/absolute/path'
template :baz, :file => 'relative/path'
template :abc, :controller => OtherController
template :xyz, :controller => OtherController, :action => 'list'
def index
'will use template from OtherController#list'
end
def foo
'will use template from self#bar'
end
def
'will use template from /absolute/path'
end
def baz
'will use template from relative/path'
end
def abc
'will use template from OtherController#index'
end
def xyz
'will use template from OtherController#list'
end
end
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/ramaze/controller.rb', line 200 def template(this, *argv) case argv.first when Hash , *ignored = argv controller = [:controller] || ['controller'] action = [:action] || ['action'] file = [:file] || ['file'] info = {} if file file = file.to_s unless Pathname(file).absolute? root = [view_root || Global.view_root].flatten.first file = File.join(root, file) end info[:file] = file else controller ||= self action = (action || 'index').to_s info[:controller] = controller info[:action] = action end trait "#{this}_template" => info else # Only explicitly set the controller to use, if it was explicitly given. # This helps ensure that template mappings still work in subclasses # of this controller. first, second, *ignored = argv if second trait "#{this}_template" => {:controller => first, :action => second} else trait "#{this}_template" => {:action => first} end end end |
.template_paths ⇒ Object
Composes an array with the template-paths to look up in the right order. Usually this is composed of Global.view_root and the mapping of the controller.
157 158 159 160 161 162 163 |
# File 'lib/ramaze/controller/resolve.rb', line 157 def template_paths if paths = view_root paths else view_root(File.join(Global.view_root, Global.mapping.invert[self])) end end |
.view_root(*args) ⇒ Object
Define a view_root for Controller, returns the current view_root if no argument is given. Runs every given path through Controller::check_path
156 157 158 159 160 161 |
# File 'lib/ramaze/controller.rb', line 156 def view_root *args return @view_root if args.empty? check_path("#{self}.view_root: '%s' doesn't exist", *args) @view_root = args.flatten end |
Instance Method Details
#error ⇒ Object
The default error-page handler. you can overwrite this method in your controller and create your own error-template for use.
Error-pages can be in whatever the templating-engine of your controller is set to.
Ramaze::Dispatcher::Error.current
holds the exception thrown.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/ramaze/controller/error.rb', line 14 def error error = Dispatcher::Error.current title = error. unless Action.current.template response['Content-Type'] = 'text/plain' respond %( #{error.} #{error.backtrace.join("\n ")} #{PP.pp request, '', 200} ).ui end backtrace_size = Global.backtrace_size @backtrace = error.backtrace[0..20].map{|line| file, lineno, meth = *Ramaze.parse_backtrace(line) lines = Ramaze.caller_lines(file, lineno, backtrace_size) [ lines, lines.object_id.abs, file, lineno, meth ] } # for backwards-compat with old error.zmr @colors = [255] * @backtrace.size @title = h(title) @editor = ENV['EDITOR'] || 'vim' title rescue Object => ex Log.error(ex) end |