require 'action_controller/request'
require 'action_controller/response'
require 'action_controller/routing'
require 'action_controller/code_generation'
require 'action_controller/url_rewriter'
require 'drb'
module ActionController #:nodoc:
class ActionControllerError < StandardError #:nodoc:
end
class SessionRestoreError < ActionControllerError #:nodoc:
end
class MissingTemplate < ActionControllerError #:nodoc:
end
class RoutingError < ActionControllerError #:nodoc:
attr_reader :failures
def initialize(message, failures=[])
super(message)
@failures = failures
end
end
class UnknownController < ActionControllerError #:nodoc:
end
class UnknownAction < ActionControllerError #:nodoc:
end
class MissingFile < ActionControllerError #:nodoc:
end
class DoubleRenderError < ActionControllerError #:nodoc:
end
# Action Controllers are made up of one or more actions that performs its purpose and then either renders a template or
# redirects to another action. An action is defined as a public method on the controller, which will automatically be
# made accessible to the web-server through a mod_rewrite mapping. A sample controller could look like this:
#
# class GuestBookController < ActionController::Base
# def index
# @entries = Entry.find_all
# end
#
# def sign
# Entry.create(@params["entry"])
# redirect_to :action => "index"
# end
# end
#
# GuestBookController.template_root = "templates/"
# GuestBookController.process_cgi
#
# All actions assume that you want to render a template matching the name of the action at the end of the performance
# unless you tell it otherwise. The index action complies with this assumption, so after populating the @entries instance
# variable, the GuestBookController will render "templates/guestbook/index.rhtml".
#
# Unlike index, the sign action isn't interested in rendering a template. So after performing its main purpose (creating a
# new entry in the guest book), it sheds the rendering assumption and initiates a redirect instead. This redirect works by
# returning an external "302 Moved" HTTP response that takes the user to the index action.
#
# The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
# Most actions are variations of these themes.
#
# Also note that it's the final call to process_cgi that actually initiates the action performance. It will extract
# request and response objects from the CGI
#
# When Action Pack is used inside of Rails, the template_root is automatically configured and you don't need to call process_cgi
# yourself.
#
# == Requests
#
# Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters.
# This value should hold the name of the action to be performed. Once the action has been identified, the remaining
# request parameters, the session (if one is available), and the full request with all the http headers are made available to
# the action through instance variables. Then the action is performed.
#
# The full request object is available with the request accessor and is primarily used to query for http headers. These queries
# are made by accessing the environment hash, like this:
#
# def hello_ip
# location = request.env["REMOTE_IP"]
# render_text "Hello stranger from #{location}"
# end
#
# == Parameters
#
# All request parameters whether they come from a GET or POST request, or from the URL, are available through the params hash.
# So an action that was performed through /weblog/list?category=All&limit=5 will include { "category" => "All", "limit" => 5 }
# in params.
#
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
#
#
#
#
# A request stemming from a form holding these inputs will include { "post" => { "name" => "david", "address" => "hyacintvej" } }.
# If the address input had been named "post[address][street]", the params would have included
# { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting.
#
# == Sessions
#
# Sessions allows you to store objects in memory between requests. This is useful for objects that are not yet ready to be persisted,
# such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
# as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
# they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
#
# You can place objects in the session by using the session hash accessor:
#
# session[:person] = Person.authenticate(user_name, password)
#
# And retrieved again through the same hash:
#
# Hello #{session[:person]}
#
# Any object can be placed in the session (as long as it can be Marshalled). But remember that 1000 active sessions each storing a
# 50kb object could lead to a 50MB memory overhead. In other words, think carefully about size and caching before resorting to the use
# of the session.
#
# For removing objects from the session, you can either assign a single key to nil, like @session[:person] = nil, or you can
# remove the entire session with reset_session.
#
# == Responses
#
# Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
# object is generated automatically through the use of renders and redirects, so it's normally nothing you'll need to be concerned about.
#
# == Renders
#
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
# of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
# The controller passes objects to the view by assigning instance variables:
#
# def show
# @post = Post.find(params[:id])
# end
#
# Which are then automatically available to the view:
#
# Title: <%= @post.title %>
#
# You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use
# the manual rendering methods:
#
# def search
# @results = Search.find(params[:query])
# case @results
# when 0 then render :action=> "no_results"
# when 1 then render :action=> "show"
# when 2..10 then render :action=> "show_many"
# end
# end
#
# Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html.
#
# == Redirects
#
# Redirecting is what actions that update the model do when they're done. The save_post method shouldn't be responsible for also
# showing the post once it's saved -- that's the job for show_post. So once save_post has completed its business, it'll
# redirect to show_post. All redirects are external, which means that when the user refreshes his browser, it's not going to save
# the post again, but rather just show it one more time.
#
# This sounds fairly simple, but the redirection is complicated by the quest for a phenomenon known as "pretty urls". Instead of accepting
# the dreadful beings that is "weblog_controller?action=show&post_id=5", Action Controller goes out of its way to represent the former as
# "/weblog/show/5". And this is even the simple case. As an example of a more advanced pretty url consider
# "/library/books/ISBN/0743536703/show", which can be mapped to books_controller?action=show&type=ISBN&id=0743536703.
#
# Redirects work by rewriting the URL of the current action. So if the show action was called by "/library/books/ISBN/0743536703/show",
# we can redirect to an edit action simply by doing redirect_to(:action => "edit"), which could throw the user to
# "/library/books/ISBN/0743536703/edit". Naturally, you'll need to setup the routes configuration file to point to the proper controller
# and action in the first place, but once you have, it can be rewritten with ease.
#
# Let's consider a bunch of examples on how to go from "/clients/37signals/basecamp/project/dash" to somewhere else:
#
# redirect_to(:action => "edit") =>
# /clients/37signals/basecamp/project/dash
#
# redirect_to(:client_name => "nextangle", :project_name => "rails") =>
# /clients/nextangle/rails/project/dash
#
# Those redirects happen under the configuration of:
#
# map.connect 'clients/:client_name/:project_name/:controller/:action'
#
# == Calling multiple redirects or renders
#
# The rule for handling calls of multiple redirects and renders is that the first call wins. So in the following example:
#
# def do_something
# redirect_to :action => "elsewhere"
# render :action => "overthere"
# end
#
# Only the redirect happens. The rendering call is simply ignored.
#
# == Environments
#
# Action Controller works out of the box with CGI, FastCGI, and mod_ruby. CGI and mod_ruby controllers are triggered just the same using:
#
# WeblogController.process_cgi
#
# FastCGI controllers are triggered using:
#
# FCGI.each_cgi{ |cgi| WeblogController.process_cgi(cgi) }
class Base
include ClassInheritableAttributes
DEFAULT_RENDER_STATUS_CODE = "200 OK"
# Determines whether the view has access to controller internals @request, @response, @session, and @template.
# By default, it does.
@@view_controller_internals = true
cattr_accessor :view_controller_internals
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
# and images to a dedicated asset server away from the main web server. Example:
# ActionController::Base.asset_host = "http://assets.example.com"
@@asset_host = ""
cattr_accessor :asset_host
# All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
# When the application is ready to go public, this should be set to false, and the protected method local_request?
# should instead be implemented in the controller to determine when debugging screens should be shown.
@@consider_all_requests_local = true
cattr_accessor :consider_all_requests_local
# Enable or disable the collection of failure information for RoutingErrors.
# This information can be extremely useful when tweaking custom routes, but is
# pointless once routes have been tested and verified.
@@debug_routes = true
cattr_accessor :debug_routes
# Controls whether the application is thread-safe, so multi-threaded servers like WEBrick knows whether to apply a mutex
# around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications
# may not be. Turned off by default.
@@allow_concurrency = false
cattr_accessor :allow_concurrency
# Template root determines the base from which template references will be made. So a call to render("test/template")
# will be converted to "#{template_root}/test/template.rhtml".
class_inheritable_accessor :template_root
# The logger is used for generating information on the action run-time (including benchmarking) if available.
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
cattr_accessor :logger
# Determines which template class should be used by ActionController.
cattr_accessor :template_class
# Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
cattr_accessor :ignore_missing_templates
# Holds the request object that's primarily used to get environment variables through access like
# request.env["REQUEST_URI"].
attr_accessor :request
# Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like @params["post_id"]
# to get the post_id. No type casts are made, so all values are returned as strings.
attr_accessor :params
# Holds the response object that's primarily used to set additional HTTP headers through access like
# response.headers["Cache-Control"] = "no-cache". Can also be used to access the final body HTML after a template
# has been rendered through response.body -- useful for after_filters that wants to manipulate the output,
# such as a OutputCompressionFilter.
attr_accessor :response
# Holds a hash of objects in the session. Accessed like session[:person] to get the object tied to the "person"
# key. The session will hold any type of object as values, but the key should be a string.
attr_accessor :session
# Holds a hash of header names and values. Accessed like headers["Cache-Control"] to get the value of the Cache-Control
# directive. Values should always be specified as strings.
attr_accessor :headers
# Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
# is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
attr_accessor :assigns
# Returns the name of the action this controller is processing.
attr_accessor :action_name
class << self
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
new.process(request, response)
end
# Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
def controller_class_name
@controller_class_name ||= name.demodulize
end
# Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
def controller_name
@controller_name ||= controller_class_name.sub(/Controller$/, '').underscore
end
# Convert the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
def controller_path
unless @controller_path
components = self.name.to_s.split('::')
components[-1] = $1 if /^(.*)Controller$/ =~ components.last
# Accomodate the root Controllers module.
components.shift if components.first == 'Controllers'
@controller_path = components.map { |name| name.underscore }.join('/')
end
@controller_path
end
# Return an array containing the names of public methods that have been marked hidden from the action processor.
# By default, all methods defined in ActionController::Base and included modules are hidden.
# More methods can be hidden using +hide_actions+.
def hidden_actions
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods) unless read_inheritable_attribute(:hidden_actions)
read_inheritable_attribute(:hidden_actions)
end
# Hide each of the given methods from being callable as actions.
def hide_action(*names)
write_inheritable_attribute(:hidden_actions, hidden_actions | names.collect {|n| n.to_s})
end
# Set the template root to be one directory behind the root dir of the controller. Examples:
# /code/weblog/components/admin/users_controller.rb with Admin::UsersController
# will use /code/weblog/components as template root
# and find templates in /code/weblog/components/admin/users/
#
# /code/weblog/components/admin/parties/users_controller.rb with Admin::Parties::UsersController
# will also use /code/weblog/components as template root
# and find templates in /code/weblog/components/admin/parties/users/
def uses_component_template_root
path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/).first)
path_of_controller_root = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode)
self.template_root = path_of_controller_root
end
# Temporary method for enabling upload progress until it's ready to leave experimental mode
def enable_upload_progress # :nodoc:
require 'action_controller/upload_progress'
include ActionController::UploadProgress
end
end
public
# Extracts the action_name from the request parameters and performs that action.
def process(request, response, method = :perform_action, *arguments) #:nodoc:
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
@action_name = params[:action] || 'index'
log_processing unless logger.nil?
send(method, *arguments)
close_session
return @response
end
# Returns a URL that has been rewritten according to the options hash and the defined Routes.
# (For doing a complete redirect, use redirect_to).
#
# url_for is used to:
#
# All keys given to url_for are forwarded to the Route module save for the following:
# * :anchor -- specifies the anchor name to be appended to the path. For example,
# url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'
# will produce "/posts/show/10#comments".
# * :only_path -- if true, returns the absolute URL (omitting the protocol, host name, and port)
# * :trailing_slash -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
# is currently not recommended since it breaks caching.
# * :host -- overrides the default (current) host if provided
# * :protocol -- overrides the default (current) protocol if provided
#
# The URL is generated from the remaining keys in the hash. A URL contains two key parts: the
Good seeing you!
" using Builder # render :inline => "xml.p { 'Good seeing you!' }", :type => :rxml # # # Renders "hello david" # render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" } # # _Deprecation_ _notice_: This used to have the signature render_template(template, status = 200, type = :rhtml) # # === Rendering nothing # # Rendering nothing is often convenient in combination with Ajax calls that perform their effect client-side or # when you just want to communicate a status code. # # # Renders an empty response with status code 200 # render :nothing => true # # # Renders an empty response with status code 401 (access denied) # render :nothing => true, :status => 401 def render(options = {}, deprecated_status = nil) #:doc: # puts "Rendering: #{options.inspect}" raise DoubleRenderError, "Can only render or redirect once per action" if performed? # Backwards compatibility return render({ :template => options || default_template_name, :status => deprecated_status }) if !options.is_a?(Hash) add_variables_to_assigns options[:status] = (options[:status] || DEFAULT_RENDER_STATUS_CODE).to_s if options[:text] @response.headers["Status"] = options[:status] @response.body = options[:text] @performed_render = true return options[:text] elsif options[:file] assert_existance_of_template_file(options[:file]) if options[:use_full_path] logger.info("Rendering #{options[:file]} (#{options[:status]})") unless logger.nil? render(options.merge({ :text => @template.render_file(options[:file], options[:use_full_path])})) elsif options[:template] render(options.merge({ :file => options[:template], :use_full_path => true })) elsif options[:inline] render(options.merge({ :text => @template.render_template( options[:type] || :rhtml, options[:inline], options[:locals] || {} ) })) elsif options[:action] render(options.merge({ :template => default_template_name(options[:action]) })) elsif options[:partial] && options[:collection] render(options.merge({ :text => ( @template.render_partial_collection( options[:partial] == true ? default_template_name : options[:partial], options[:collection], options[:spacer_template], options[:locals] || {} ) || '' ) })) elsif options[:partial] render(options.merge({ :text => @template.render_partial( options[:partial] == true ? default_template_name : options[:partial], options[:object], options[:locals] || {} ) })) elsif options[:nothing] render(options.merge({ :text => "" })) else render(options.merge({ :action => action_name })) end end # Renders according to the same rules as render, but returns the result in a string instead # of sending it as the response body to the browser. def render_to_string(options = {}) #:doc: result = render(options) erase_render_results return result end # Clears the rendered results, allowing for another render to be performed. def erase_render_results #:nodoc: @response.body = nil @performed_render = false end # Clears the redirected results from the headers, resetting the status to 200 and returns # the URL that was used to redirect or nil if there was no redirected URL # Note that +redirect_to+ will change the body of the response to indicate a redirection. # The response body is not reset here, see +erase_render_results+ def erase_redirect_results #:nodoc: @performed_redirect = false response.redirected_to = nil response.redirected_to_method_params = nil response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE response.headers.delete('location') end def rewrite_options(options) if defaults = default_url_options(options) defaults.merge(options) else options end end # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in # the form of a hash, just like the one you would use for url_for directly. Example: # # def default_url_options(options) # { :project => @project.active? ? @project.url_name : "unknown" } # end # # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set # by this method. def default_url_options(options) #:doc: end # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: # # * Hash: The URL will be generated by calling url_for with the +options+. # * String starting with protocol:// (like http://): Is passed straight through as the target for redirection. # * String not containing a protocol: The current current protocol and host is prepended to the string. # # Examples: # redirect_to :action => "show", :id => 5 # redirect_to "http://www.rubyonrails.org" # redirect_to "/images/screenshot.jpg" # # The redirection happens as a "302 Moved" header. def redirect_to(options = {}, *parameters_for_method_reference) #:doc: case options when %r{^\w+://.*} raise DoubleRenderError, "Can only render or redirect once per action" if performed? logger.info("Redirected to #{options}") unless logger.nil? response.redirect(options) response.redirected_to = options @performed_redirect = true when String redirect_to(request.protocol + request.host_with_port + options) else if parameters_for_method_reference.empty? redirect_to(url_for(options)) response.redirected_to = options else redirect_to(url_for(options, *parameters_for_method_reference)) response.redirected_to, response.redirected_to_method_params = options, parameters_for_method_reference end end end # Resets the session by clearing out all the objects stored within and initializing a new session object. def reset_session #:doc: @request.reset_session @session = @request.session @response.session = @session end private def initialize_template_class(response) begin response.template = template_class.new(template_root, {}, self) rescue raise "You must assign a template class through ActionController.template_class= before processing a request" end @performed_render = @performed_redirect = false end def assign_shortcuts(request, response) @request, @params, @cookies = request, request.parameters, request.cookies @response = response @response.session = request.session @session = @response.session @template = @response.template @assigns = @response.template.assigns @headers = @response.headers end def initialize_current_url @url = UrlRewriter.new(@request, @params.clone()) end def log_processing logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin})" logger.info " Parameters: #{@params.inspect}" end def perform_action if action_methods.include?(action_name) || action_methods.include?('method_missing') send(action_name) render unless performed? elsif template_exists? && template_public? render else raise UnknownAction, "No action responded to #{action_name}", caller end end def performed? @performed_render || @performed_redirect end def action_methods @action_methods ||= (self.class.public_instance_methods - self.class.hidden_actions) end def add_variables_to_assigns add_instance_variables_to_assigns add_class_variables_to_assigns if view_controller_internals end def add_instance_variables_to_assigns @@protected_variables_cache = protected_instance_variables.inject({}) { |h, k| h[k] = true; h } instance_variables.each do |var| next if @@protected_variables_cache.include?(var) @assigns[var[1..-1]] = instance_variable_get(var) end end def add_class_variables_to_assigns %w( template_root logger template_class ignore_missing_templates ).each do |cvar| @assigns[cvar] = self.send(cvar) end end def protected_instance_variables if view_controller_internals [ "@assigns", "@performed_redirect", "@performed_render" ] else [ "@assigns", "@performed_redirect", "@performed_render", "@request", "@response", "@session", "@cookies", "@template" ] end end def request_origin "#{@request.remote_ip} at #{Time.now.to_s}" end def complete_request_uri request.protocol + request.host + request.request_uri end def close_session @session.close unless @session.nil? || Hash === @session end def template_exists?(template_name = default_template_name) @template.file_exists?(template_name) end def template_public?(template_name = default_template_name) @template.file_public?(template_name) end def assert_existance_of_template_file(template_name) unless template_exists?(template_name) || ignore_missing_templates full_template_path = @template.send(:full_template_path, template_name, 'rhtml') template_type = (template_name =~ /layouts/i) ? 'layout' : 'template' raise(MissingTemplate, "Missing #{template_type} #{full_template_path}") end end def default_template_name(default_action_name = action_name) "#{self.class.controller_path}/#{default_action_name}" end end end