Class: Ramaze::Controller

Inherits:
Object show all
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

Controller, MainController

Constant Summary collapse

FILTER =

TODO:

* fix caching, see todos below
  FILTER = [ :cached, :default ] unless defined?(FILTER)
[ :default ]

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helper::Methods

extend_object, #helper, included

Class Method Details

.action_methodsObject

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_modulesObject

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_methodsObject

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(message, *paths)
  paths.each do |path|
    Log.warn(message % path) unless File.directory?(path)
  end
end

.currentObject

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_orderObject

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

.mappingObject

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 options = {}
  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 bar
    '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
    options, *ignored = argv
    controller = options[:controller] || options['controller']
    action = options[:action] || options['action']
    file = options[:file] || options['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_pathsObject

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

#errorObject

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.message

  unless Action.current.template
    response['Content-Type'] = 'text/plain'
    respond %(
      #{error.message}
        #{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