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/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: 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.



163
164
165
# File 'lib/tap/controller.rb', line 163

def initialize
  @request = @response = @server = @controller_path = 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.



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

def actions
  @actions
end

.default_actionObject (readonly)

The default action called for the request path ‘/’



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

def default_action
  @default_action
end

Instance Attribute Details

#controller_pathObject

Returns the value of attribute controller_path.



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

def controller_path
  @controller_path
end

#requestObject

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



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

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.



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

def response
  @response
end

#serverObject

Returns the value of attribute server.



150
151
152
# File 'lib/tap/controller.rb', line 150

def server
  @server
end

Class Method Details

.call(env) ⇒ Object

Instantiates self and performs call.



49
50
51
# File 'lib/tap/controller.rb', line 49

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.



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

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.



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

def inherited(child) # :nodoc:
  super
  
  unless child.instance_variable_defined?(:@source_file)
    caller[0] =~ Lazydoc::CALLER_REGEXP
    child.instance_variable_set(:@source_file, File.expand_path($1)) 
  end
  
  set_variables.each do |variable|
    child.set(variable, get(variable))
  end
  
  child.set(:actions, actions.dup)
  child.set(:define_action, true)
end

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



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/tap/controller.rb', line 80

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, input) ⇒ Object

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

instance_variable_set(:@attribute, input)

These variables are meaningful to a default Tap::Controller and will be inherited by subclasses:

actions:: sets actions
default_action:: the default action (:index)


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

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

.set_variablesObject

An array of variables set via set. set_variables are inherited.



76
77
78
# File 'lib/tap/controller.rb', line 76

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)


168
169
170
# File 'lib/tap/controller.rb', line 168

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


228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/tap/controller.rb', line 228

def call(env)
  @server = env['tap.server']
  @controller_path = env['tap.controller_path']
  
  @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



261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/tap/controller.rb', line 261

def dispatch(route)
  action, *args = route
  
  if action == nil || action == ""
    action = self.class.default_action 
  end
  
  unless action?(action)
    raise ServerError.new("404 Error: page not found", 404)
  end
  
  send(action, *args)
end

#module_path(path, klass = self.class) ⇒ Object



198
199
200
# File 'lib/tap/controller.rb', line 198

def module_path(path, klass=self.class)
  server.env.module_path(:views, klass.ancestors, path) {|file| File.file?(file) }
end

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



355
356
357
358
359
360
361
362
363
# File 'lib/tap/controller.rb', line 355

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

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

Redirects to the specified uri.



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

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


283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/tap/controller.rb', line 283

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]
    self.template_path(options[:template])
  else
    self.module_path(path)
  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).



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/tap/controller.rb', line 339

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.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/tap/controller.rb', line 311

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.



256
257
258
259
# File 'lib/tap/controller.rb', line 256

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

#template_path(path) ⇒ Object



194
195
196
# File 'lib/tap/controller.rb', line 194

def template_path(path)
  server.env.path(:views, path) {|file| File.file?(file) }
end

#uri(action = nil, params = {}) ⇒ Object

Returns a uri to the specified action on self.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/tap/controller.rb', line 173

def uri(action=nil, params={})
  uri = []
  
  if controller_path
    uri << '/'
    uri << controller_path
  end
  
  if action
    uri << '/'
    uri << action
  end
  
  unless params.empty?
    uri << '?'
    uri << build_query(params)
  end
  
  uri.join
end