Class: Tap::Controller

Inherits:
Object
  • Object
show all
Extended by:
Lazydoc::Attributes
Includes:
Rack::Utils
Defined in:
lib/tap/controller.rb,
lib/tap/controller/utils.rb,
lib/tap/controller/extname.rb,
lib/tap/controller/rest_routes.rb

Overview

Declaring Actions

By default all public methods in subclasses are declared as actions. You can declare a private or protected method as an action by:

  • manually adding it directly to actions

  • defining it as a public method and then call private(:method) or protected(:method)

Similarly, public method can be made non-action by actions by:

  • manually deleting it from actions

  • define it private or protected then call public(:method)

Defined Under Namespace

Modules: Extname, RestRoutes, Utils

Constant Summary collapse

ServerError =
Tap::Server::ServerError

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeController

Initializes a new instance of self.



156
157
158
# File 'lib/tap/controller.rb', line 156

def initialize
  @request = @response = @server = nil
end

Class Attribute Details

.actionsObject (readonly)

An array of methods that can be called as actions. Actions must be stored as symbols. Actions are inherited.



40
41
42
# File 'lib/tap/controller.rb', line 40

def actions
  @actions
end

.default_actionObject (readonly)

The default action called for the request path ‘/’



43
44
45
# File 'lib/tap/controller.rb', line 43

def default_action
  @default_action
end

Instance Attribute Details

#requestObject

A Rack::Request wrapping env, set during call.



148
149
150
# File 'lib/tap/controller.rb', line 148

def request
  @request
end

#responseObject

A Rack::Response. If the action returns a string, it will be written to response and response will be returned by call. Otherwise, call returns the action result and response is ignored.



153
154
155
# File 'lib/tap/controller.rb', line 153

def response
  @response
end

#serverObject

References the Tap::Server running this controller.



145
146
147
# File 'lib/tap/controller.rb', line 145

def server
  @server
end

Class Method Details

.call(env) ⇒ Object

Instantiates self and performs call.



46
47
48
# File 'lib/tap/controller.rb', line 46

def call(env)
  new.call(env)
end

.get(variable) ⇒ Object

Gets the value of an instance variable set via set. Returns nil for variables that have not been set through set.



64
65
66
67
# File 'lib/tap/controller.rb', line 64

def get(variable)
  return nil unless set_variables.include?(variable)
  instance_variable_get("@#{variable}")
end

.inherited(child) ⇒ Object

Initialize instance variables on the child and inherit as necessary.



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/tap/controller.rb', line 26

def inherited(child) # :nodoc:
  super
  
  set_variables.each do |variable|
    value = get(variable)
    value = value.dup if Configurable::Config.duplicable_value?(value)
    child.set(variable, value)
  end
  
  child.set(:define_action, true)
end

.nest(key, controller, &block) ⇒ Object



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
# File 'lib/tap/controller.rb', line 74

def nest(key, controller, &block)
  
  # generate a subclass if anything gets overridden
  if block_given?
    controller = Class.new(controller)
    controller.class_eval(&block)
  end
  
  # this check prevents a warning in cases where the nesting 
  # class defines the nested class
  const_name = key.to_s.camelize
  unless const_defined?(const_name) && const_get(const_name) == subclass
    const_set(const_name, controller)
  end
  
  define_method(key) do |*args|
    instance = controller.new
    
    instance.server = server
    instance.controller_path = controller_path ? "#{controller_path}/#{key}" : key
    instance.request = request
    instance.response = response
    
    instance.dispatch(args)
  end
end

.set(variable, value) ⇒ Object

Sets an instance variable for self (ie the class), short for:

instance_variable_set(:@attribute, value)

Set variables inherited by subclasses. The value is duplicated on the subclass so the parent and child variable may be modified independently.



57
58
59
60
# File 'lib/tap/controller.rb', line 57

def set(variable, value)
  set_variables << variable
  instance_variable_set("@#{variable}", value)
end

.set_variablesObject

An array of variables set via set.



70
71
72
# File 'lib/tap/controller.rb', line 70

def set_variables
  @set_variables ||= []
end

Instance Method Details

#action?(action) ⇒ Boolean

Returns true if action is registered as an action for self.

Returns:

  • (Boolean)


161
162
163
# File 'lib/tap/controller.rb', line 161

def action?(action)
  self.class.actions.include?(action.to_sym)
end

#call(env) ⇒ Object

Routes the request to an action and returns the response. Routing is simple and fixed (see route):

route                  calls
/                      default_action (ie 'index')
/action/*args          action(*args)

If the action returns a string, it will be written to response. Otherwise, call returns the result of action. This allows actions like:

class ActionsController < Tap::Controller
  def simple
    "html body"
  end

  def standard
    response["Content-Type"] = "text/plain"
    response << "text"
    response.finish
  end

  def custom
    [200, {"Content-Type" => "text/plain"}, ["text"]]
  end
end


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/tap/controller.rb', line 239

def call(env)
  @server = env['tap.server']
  @request = Rack::Request.new(env)
  @response = Rack::Response.new
  
  case result = dispatch(route)
  when String
    response.write result
    response.finish
  when nil
    response.finish
  else 
    result
  end
end

#dispatch(route) ⇒ Object

Inputs a route like [action, *args] and dispatches it to the action.



271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/tap/controller.rb', line 271

def dispatch(route)
  action, *args = route
  
  if action == nil || action == ""
    action = self.class.default_action 
  end
  
  unless action?(action)
    not_found
  end
  
  send(action, *args)
end

#error(msg, status = 500) ⇒ Object

Raises:



388
389
390
# File 'lib/tap/controller.rb', line 388

def error(msg, status=500)
  raise ServerError.new(msg, status)
end

#module_render(path, obj, options = {}) ⇒ Object



365
366
367
368
369
370
371
372
# File 'lib/tap/controller.rb', line 365

def module_render(path, obj, options={})
  options[:file] = server.module_path(path, obj.class)
  
  locals = options[:locals] ||= {}
  locals[:obj] ||= obj
  
  render options
end

#not_foundObject



384
385
386
# File 'lib/tap/controller.rb', line 384

def not_found
  error("404 Error: page not found", 404)
end

#redirect(uri, status = 302, headers = {}, body = "") ⇒ Object

Redirects to the specified uri.



375
376
377
378
379
380
381
382
# File 'lib/tap/controller.rb', line 375

def redirect(uri, status=302, headers={}, body="")
  response.status = status
  response.headers.merge!(headers)
  response.body = body

  response['Location'] = [uri]
  response.finish
end

#render(path, options = {}) ⇒ Object

Renders the class_file at path with the specified options. Path can be omitted if options specifies an alternate path to render. Options:

template:: renders the template relative to the template directory
file:: renders the specified file 
layout:: renders with the specified layout, or default_layout if true
locals:: a hash of local variables used in the template


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/tap/controller.rb', line 293

def render(path, options={})
  options, path = path, nil if path.kind_of?(Hash)

  # lookup template
  template_path = case
  when options[:file]
    options[:file]
  when options[:template]
    server.template_path(options[:template])
  else
    server.module_path(path, self.class)
  end

  unless template_path
    raise "could not find template: (path: #{path.inspect}, file: #{options[:file].inspect}, template: #{options[:template].inspect})"
  end
  
  # render template
  template = File.read(template_path)
  content = render_erb(template, options, template_path)
  
  # render layout
  render_layout(options[:layout], content)
end

#render_erb(template, options = {}, filename = nil) ⇒ Object

Renders the specified template as ERB using the options. Options:

locals:: a hash of local variables used in the template

The filename used to identify errors in an erb template to a specific file and is completely options (but handy).



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/tap/controller.rb', line 349

def render_erb(template, options={}, filename=nil)
  # assign locals to the render binding
  # this almost surely may be optimized...
  locals = options[:locals]
  binding = render_erb_binding

  locals.each_pair do |key, value|
    @assignment_value = value
    eval("#{key} = remove_instance_variable(:@assignment_value)", binding)
  end if locals

  erb = ERB.new(template, nil, "<>")
  erb.filename = filename
  erb.result(binding)
end

#render_layout(layout, content) ⇒ Object

Renders the specified layout with content as a local variable. If layout is true, the class default_layout will be rendered. Returns content if no layout is specified.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/tap/controller.rb', line 321

def render_layout(layout, content)
  return content unless layout
  
  if layout == true
    layout = self.class.get(:default_layout)
  end
  
  if layout.kind_of?(Hash)
    locals = layout[:locals] ||= {}
    
    if locals.has_key?(:content)
      raise "layout already has local content assigned: #{layout.inspect}"
    end
    
    locals[:content] = content
  else
    layout = {:template => layout, :locals => {:content => content}}
  end
  
  render(layout)
end

#routeObject

Returns the action, args, and extname for the request.path_info. Routing is simple and fixed:

route             returns
/                 [:index, []]
/action/*args     [:action, args]

The action and args are unescaped by route. An alternate default action may be specified using set. Override this method in subclasses for fancier routes.



265
266
267
268
# File 'lib/tap/controller.rb', line 265

def route
  blank, *route = request.path_info.split("/").collect {|arg| unescape(arg) }
  route
end

#uri(action = nil, params = nil, options = nil) ⇒ Object

Returns a uri to the specified action on self. The parameters will be built into a query string, if specified. By default the uri will not specify a protocol or host. Specifying an option hash will add these to the uri.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/tap/controller.rb', line 169

def uri(action=nil, params=nil, options=nil)
  if action.kind_of?(Hash)
    unless params.nil? && options.nil?
      raise "extra arguments specified for uri hash syntax"
    end
    
    options = action
    params = options[:params]
    action = options[:action]
  end
  
  uri = []
  
  if request
    uri << request.env['SCRIPT_NAME']
  end
  
  if action
    uri << '/'
    uri << action
  end
  
  unless params.nil? || params.empty?
    uri << '?'
    uri << build_query(params)
  end
  
  if options
    scheme = (options[:scheme] || request.scheme)
    port = (options[:port] || request.port)
    
    if scheme == "https" && port != 443 ||
        scheme == "http" && port != 80
      uri.unshift ":#{port}"
    end
    
    uri.unshift(options[:host] || request.host)
    uri.unshift("://")
    uri.unshift(scheme)
  end
  
  uri.join
end