aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
authorEmilio Tagua <miloops@gmail.com>2009-06-10 19:54:27 -0300
committerEmilio Tagua <miloops@gmail.com>2009-06-10 19:54:27 -0300
commit247ac954132222fdd399cddc264bc1d4029f750c (patch)
tree680a07197aa207279a3455327c62843b256fe88a /actionpack/lib/action_controller
parentd7b98b3ca855418f88a9e979d32c1710040b3038 (diff)
parent47ff57f6d14fe161900bf85e2d2cf6d7e21a1eb8 (diff)
downloadrails-247ac954132222fdd399cddc264bc1d4029f750c.tar.gz
rails-247ac954132222fdd399cddc264bc1d4029f750c.tar.bz2
rails-247ac954132222fdd399cddc264bc1d4029f750c.zip
Merge commit 'rails/master'
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/abstract/base.rb7
-rw-r--r--actionpack/lib/action_controller/abstract/helpers.rb52
-rw-r--r--actionpack/lib/action_controller/abstract/layouts.rb114
-rw-r--r--actionpack/lib/action_controller/abstract/logger.rb16
-rw-r--r--actionpack/lib/action_controller/abstract/renderer.rb3
-rw-r--r--actionpack/lib/action_controller/new_base/compatibility.rb5
-rw-r--r--actionpack/lib/action_controller/new_base/helpers.rb159
-rw-r--r--actionpack/lib/action_controller/new_base/hide_actions.rb44
-rw-r--r--actionpack/lib/action_controller/new_base/http.rb51
-rw-r--r--actionpack/lib/action_controller/new_base/layouts.rb171
10 files changed, 455 insertions, 167 deletions
diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/action_controller/abstract/base.rb
index c3daef8759..a19a236ef7 100644
--- a/actionpack/lib/action_controller/abstract/base.rb
+++ b/actionpack/lib/action_controller/abstract/base.rb
@@ -95,11 +95,6 @@ module AbstractController
end
private
- # See AbstractController::Base.action_methods
- def action_methods
- self.class.action_methods
- end
-
# Returns true if the name can be considered an action. This can
# be overridden in subclasses to modify the semantics of what
# can be considered an action.
@@ -110,7 +105,7 @@ module AbstractController
# ==== Returns
# TrueClass, FalseClass
def action_method?(name)
- action_methods.include?(name)
+ self.class.action_methods.include?(name)
end
# Call the action. Override this in a subclass to modify the
diff --git a/actionpack/lib/action_controller/abstract/helpers.rb b/actionpack/lib/action_controller/abstract/helpers.rb
index 0a2776de9c..6b73f887c1 100644
--- a/actionpack/lib/action_controller/abstract/helpers.rb
+++ b/actionpack/lib/action_controller/abstract/helpers.rb
@@ -5,33 +5,26 @@ module AbstractController
include Renderer
included do
- extlib_inheritable_accessor :master_helper_module
- self.master_helper_module = Module.new
+ extlib_inheritable_accessor(:_helpers) { Module.new }
end
+ # Override AbstractController::Renderer's _action_view to include the
+ # helper module for this class into its helpers module.
def _action_view
- @_action_view ||= begin
- av = super
- av.helpers.send(:include, master_helper_module)
- av
- end
+ @_action_view ||= super.tap { |av| av.helpers.include(_helpers) }
end
module ClassMethods
+ # When a class is inherited, wrap its helper module in a new module.
+ # This ensures that the parent class's module can be changed
+ # independently of the child class's.
def inherited(klass)
- klass.master_helper_module = Module.new
- klass.master_helper_module.__send__ :include, master_helper_module
+ helpers = _helpers
+ klass._helpers = Module.new { include helpers }
super
end
- # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
- # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
- # available to the templates.
- def add_template_helper(mod)
- master_helper_module.module_eval { include mod }
- end
-
# Declare a controller method as a helper. For example, the following
# makes the +current_user+ controller method available to the view:
# class ApplicationController < ActionController::Base
@@ -48,9 +41,13 @@ module AbstractController
#
# In a view:
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
+ #
+ # ==== Parameters
+ # meths<Array[#to_s]>:: The name of a method on the controller
+ # to be made available on the view.
def helper_method(*meths)
meths.flatten.each do |meth|
- master_helper_module.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
+ _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def #{meth}(*args, &blk)
controller.send(%(#{meth}), *args, &blk)
end
@@ -58,6 +55,14 @@ module AbstractController
end
end
+ # Make a number of helper modules part of this class' default
+ # helpers.
+ #
+ # ==== Parameters
+ # *args<Array[Module]>:: Modules to be included
+ # block<Block>:: Evalulate the block in the context
+ # of the helper module. Any methods defined in the block
+ # will be helpers.
def helper(*args, &block)
args.flatten.each do |arg|
case arg
@@ -65,7 +70,18 @@ module AbstractController
add_template_helper(arg)
end
end
- master_helper_module.module_eval(&block) if block_given?
+ _helpers.module_eval(&block) if block_given?
+ end
+
+ private
+ # Makes all the (instance) methods in the helper module available to templates
+ # rendered through this controller.
+ #
+ # ==== Parameters
+ # mod<Module>:: The module to include into the current helper module
+ # for the class
+ def add_template_helper(mod)
+ _helpers.module_eval { include mod }
end
end
end
diff --git a/actionpack/lib/action_controller/abstract/layouts.rb b/actionpack/lib/action_controller/abstract/layouts.rb
index 273063f74b..9ff8e9beb1 100644
--- a/actionpack/lib/action_controller/abstract/layouts.rb
+++ b/actionpack/lib/action_controller/abstract/layouts.rb
@@ -5,16 +5,26 @@ module AbstractController
include Renderer
included do
- extlib_inheritable_accessor :_layout_conditions
- self._layout_conditions = {}
+ extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
end
module ClassMethods
+ # Specify the layout to use for this class.
+ #
+ # If the specified layout is a:
+ # String:: the String is the template name
+ # Symbol:: call the method specified by the symbol, which will return
+ # the template name
+ # false:: There is no layout
+ # true:: raise an ArgumentError
+ #
+ # ==== Parameters
+ # layout<String, Symbol, false)>:: The layout to use.
+ #
+ # ==== Options (conditions)
+ # :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to.
+ # :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one
def layout(layout, conditions = {})
- unless [String, Symbol, FalseClass, NilClass].include?(layout.class)
- raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
- end
-
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions
@@ -22,6 +32,11 @@ module AbstractController
_write_layout_method
end
+ # If no layout is supplied, look for a template named the return
+ # value of this method.
+ #
+ # ==== Returns
+ # String:: A template name
def _implied_layout_name
name.underscore
end
@@ -29,23 +44,31 @@ module AbstractController
# Takes the specified layout and creates a _layout method to be called
# by _default_layout
#
- # If the specified layout is a:
- # String:: return the string
- # Symbol:: call the method specified by the symbol
- # false:: return nil
- # none:: If a layout is found in the view paths with the controller's
- # name, return that string. Otherwise, use the superclass'
- # layout (which might also be implied)
+ # If there is no explicit layout specified:
+ # If a layout is found in the view paths with the controller's
+ # name, return that string. Otherwise, use the superclass'
+ # layout (which might also be implied)
def _write_layout_method
case @_layout
when String
self.class_eval %{def _layout(details) #{@_layout.inspect} end}
when Symbol
- self.class_eval %{def _layout(details) #{@_layout} end}
+ self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
+ def _layout(details)
+ #{@_layout}.tap do |layout|
+ unless layout.is_a?(String) || !layout
+ raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \
+ "should have returned a String, false, or nil"
+ end
+ end
+ end
+ ruby_eval
when false
self.class_eval %{def _layout(details) end}
- else
- self.class_eval %{
+ when true
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
+ when nil
+ self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def _layout(details)
if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts")
"#{_implied_layout_name}"
@@ -53,33 +76,54 @@ module AbstractController
super
end
end
- }
+ ruby_eval
end
end
end
private
- # This will be overwritten
- def _layout(details)
- end
+ # This will be overwritten by _write_layout_method
+ def _layout(details) end
- # :api: plugin
- # ====
- # Override this to mutate the inbound layout name
+ # Determine the layout for a given name and details.
+ #
+ # ==== Parameters
+ # name<String>:: The name of the template
+ # details<Hash{Symbol => Object}>:: A list of details to restrict
+ # the lookup to. By default, layout lookup is limited to the
+ # formats specified for the current request.
def _layout_for_name(name, details = {:formats => formats})
- unless [String, FalseClass, NilClass].include?(name.class)
- raise ArgumentError, "String, false, or nil expected; you passed #{name.inspect}"
- end
-
- name && view_paths.find_by_parts(name, details, _layout_prefix(name))
+ name && _find_by_parts(name, details)
end
- # TODO: Decide if this is the best hook point for the feature
- def _layout_prefix(name)
- "layouts"
+ # Take in the name and details and find a Template.
+ #
+ # ==== Parameters
+ # name<String>:: The name of the template to retrieve
+ # details<Hash>:: A list of details to restrict the search by. This
+ # might include details like the format or locale of the template.
+ #
+ # ==== Returns
+ # Template:: A template object matching the name and details
+ def _find_by_parts(name, details)
+ # TODO: Make prefix actually part of details in ViewPath#find_by_parts
+ prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts"
+ view_paths.find_by_parts(name, details, prefix)
end
- def _default_layout(require_layout = false, details = {:formats => formats})
+ # Returns the default layout for this controller and a given set of details.
+ # Optionally raises an exception if the layout could not be found.
+ #
+ # ==== Parameters
+ # details<Hash>:: A list of details to restrict the search by. This
+ # might include details like the format or locale of the template.
+ # require_layout<Boolean>:: If this is true, raise an ArgumentError
+ # with details about the fact that the exception could not be
+ # found (defaults to false)
+ #
+ # ==== Returns
+ # Template:: The template object for the default layout (or nil)
+ def _default_layout(details, require_layout = false)
if require_layout && _action_has_layout? && !_layout(details)
raise ArgumentError,
"There was no default layout for #{self.class} in #{view_paths.inspect}"
@@ -93,6 +137,12 @@ module AbstractController
end
end
+ # Determines whether the current action has a layout by checking the
+ # action name against the :only and :except conditions set on the
+ # layout.
+ #
+ # ==== Returns
+ # Boolean:: True if the action has a layout, false otherwise.
def _action_has_layout?
conditions = _layout_conditions
if only = conditions[:only]
diff --git a/actionpack/lib/action_controller/abstract/logger.rb b/actionpack/lib/action_controller/abstract/logger.rb
index d6fa843485..b960e152e3 100644
--- a/actionpack/lib/action_controller/abstract/logger.rb
+++ b/actionpack/lib/action_controller/abstract/logger.rb
@@ -5,6 +5,13 @@ module AbstractController
module Logger
extend ActiveSupport::Concern
+ # A class that allows you to defer expensive processing
+ # until the logger actually tries to log. Otherwise, you are
+ # forced to do the processing in advance, and send the
+ # entire processed String to the logger, which might
+ # just discard the String if the log level is too low.
+ #
+ # TODO: Require that Rails loggers accept a block.
class DelayedLog
def initialize(&blk)
@blk = blk
@@ -20,8 +27,10 @@ module AbstractController
cattr_accessor :logger
end
- def process(action)
- ret = super
+ # Override process_action in the AbstractController::Base
+ # to log details about the method.
+ def process_action(action)
+ super
if logger
log = DelayedLog.new do
@@ -32,10 +41,9 @@ module AbstractController
logger.info(log)
end
-
- ret
end
+ private
def request_origin
# this *needs* to be cached!
# otherwise you'd get different results if calling it more than once
diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb
index 28bdda4a75..611d3a16ce 100644
--- a/actionpack/lib/action_controller/abstract/renderer.rb
+++ b/actionpack/lib/action_controller/abstract/renderer.rb
@@ -29,6 +29,9 @@ module AbstractController
# partial<Boolean>:: Whether or not the template to render is a partial
# _partial:: If a partial, rather than a template, was rendered, return
# the partial.
+ # helpers:: A module containing the helpers to be used in the view. This
+ # module should respond_to include.
+ # controller:: The controller that initialized the ActionView
#
# Override this method in a to change the default behavior.
def _action_view
diff --git a/actionpack/lib/action_controller/new_base/compatibility.rb b/actionpack/lib/action_controller/new_base/compatibility.rb
index f278c2da14..29ba43a879 100644
--- a/actionpack/lib/action_controller/new_base/compatibility.rb
+++ b/actionpack/lib/action_controller/new_base/compatibility.rb
@@ -114,8 +114,9 @@ module ActionController
super || (respond_to?(:method_missing) && "_handle_method_missing")
end
- def _layout_prefix(name)
- super unless name =~ /\blayouts/
+ def _find_by_parts(name, details)
+ details[:prefix] = nil if name =~ /\blayouts/
+ super
end
def performed?
diff --git a/actionpack/lib/action_controller/new_base/helpers.rb b/actionpack/lib/action_controller/new_base/helpers.rb
index e8000be87b..2fa5ea6519 100644
--- a/actionpack/lib/action_controller/new_base/helpers.rb
+++ b/actionpack/lib/action_controller/new_base/helpers.rb
@@ -3,6 +3,51 @@ require 'active_support/core_ext/name_error'
require 'active_support/dependencies'
module ActionController
+ # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
+ # +numbers+ and model objects, to name a few. These helpers are available to all templates
+ # by default.
+ #
+ # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
+ # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
+ # include <tt>MyHelper</tt>.
+ #
+ # Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any
+ # controller which inherits from it.
+ #
+ # ==== Examples
+ # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if
+ # the Time object is blank:
+ #
+ # module FormattedTimeHelper
+ # def format_time(time, format=:long, blank_message="&nbsp;")
+ # time.blank? ? blank_message : time.to_s(format)
+ # end
+ # end
+ #
+ # FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
+ #
+ # class EventsController < ActionController::Base
+ # helper FormattedTimeHelper
+ # def index
+ # @events = Event.find(:all)
+ # end
+ # end
+ #
+ # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
+ #
+ # <% @events.each do |event| -%>
+ # <p>
+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
+ # </p>
+ # <% end -%>
+ #
+ # Finally, assuming we have two event instances, one which has a time and one which does not,
+ # the output might look like this:
+ #
+ # 23 Aug 11:30 | Carolina Railhawks Soccer Match
+ # N/A | Carolina Railhaws Training Workshop
+ #
module Helpers
extend ActiveSupport::Concern
@@ -10,20 +55,22 @@ module ActionController
included do
# Set the default directory for helpers
- class_inheritable_accessor :helpers_dir
- self.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
+ extlib_inheritable_accessor(:helpers_dir) do
+ defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers"
+ end
end
module ClassMethods
def inherited(klass)
- klass.__send__ :default_helper_module!
+ klass.class_eval { default_helper_module! unless name.blank? }
super
end
# The +helper+ class method can take a series of helper module names, a block, or both.
#
- # * <tt>*args</tt>: One or more modules, strings or symbols, or the special symbol <tt>:all</tt>.
- # * <tt>&block</tt>: A block defining helper methods.
+ # ==== Parameters
+ # *args<Array[Module, Symbol, String, :all]>
+ # block<Block>:: A block defining helper methods
#
# ==== Examples
# When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
@@ -56,34 +103,7 @@ module ActionController
# helper(:three, BlindHelper) { def mice() 'mice' end }
#
def helper(*args, &block)
- args.flatten.each do |arg|
- case arg
- when :all
- helper all_application_helpers
- when String, Symbol
- file_name = arg.to_s.underscore + '_helper'
- class_name = file_name.camelize
-
- begin
- require_dependency(file_name)
- rescue LoadError => load_error
- requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
- if requiree == file_name
- msg = "Missing helper file helpers/#{file_name}.rb"
- raise LoadError.new(msg).copy_blame!(load_error)
- else
- raise
- end
- end
-
- super class_name.constantize
- else
- super args
- end
- end
-
- # Evaluate block in template class if given.
- master_helper_module.module_eval(&block) if block_given?
+ super(*_modules_for_helpers(args), &block)
end
# Declares helper accessors for controller attributes. For example, the
@@ -91,39 +111,68 @@ module ActionController
# controller and makes them available to the view:
# helper_attr :name
# attr_accessor :name
+ #
+ # ==== Parameters
+ # *attrs<Array[String, Symbol]>:: Names of attributes to be converted
+ # into helpers.
def helper_attr(*attrs)
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
end
# Provides a proxy to access helpers methods from outside the view.
def helpers
- unless @helper_proxy
- @helper_proxy = ActionView::Base.new
- @helper_proxy.extend master_helper_module
- else
- @helper_proxy
- end
+ @helper_proxy ||= ActionView::Base.new.extend(_helpers)
end
- private
- def default_helper_module!
- unless name.blank?
- module_name = name.sub(/Controller$|$/, 'Helper')
- module_path = module_name.split('::').map { |m| m.underscore }.join('/')
- require_dependency module_path
- helper module_name.constantize
+ private
+ # Returns a list of modules, normalized from the acceptable kinds of
+ # helpers with the following behavior:
+ # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
+ # and "foo_bar_helper.rb" is loaded using require_dependency.
+ # :all:: Loads all modules in the #helpers_dir
+ # Module:: No further processing
+ #
+ # After loading the appropriate files, the corresponding modules
+ # are returned.
+ #
+ # ==== Parameters
+ # args<Array[String, Symbol, Module, all]>:: A list of helpers
+ #
+ # ==== Returns
+ # Array[Module]:: A normalized list of modules for the list of
+ # helpers provided.
+ def _modules_for_helpers(args)
+ args.flatten.map! do |arg|
+ case arg
+ when :all
+ _modules_for_helpers all_application_helpers
+ when String, Symbol
+ file_name = "#{arg.to_s.underscore}_helper"
+ require_dependency(file_name, "Missing helper file helpers/%s.rb")
+ file_name.camelize.constantize
+ when Module
+ arg
+ else
+ raise ArgumentError, "helper must be a String, Symbol, or Module"
end
- rescue MissingSourceFile => e
- raise unless e.is_missing? module_path
- rescue NameError => e
- raise unless e.missing_name? module_name
end
+ end
- # Extract helper names from files in app/helpers/**/*.rb
- def all_application_helpers
- extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
- Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
- end
+ def default_helper_module!
+ module_name = name.sub(/Controller$/, '')
+ module_path = module_name.underscore
+ helper module_path
+ rescue MissingSourceFile => e
+ raise e unless e.is_missing? "#{module_path}_helper"
+ rescue NameError => e
+ raise e unless e.missing_name? "#{module_name}Helper"
+ end
+
+ # Extract helper names from files in app/helpers/**/*.rb
+ def all_application_helpers
+ extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
+ Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/new_base/hide_actions.rb b/actionpack/lib/action_controller/new_base/hide_actions.rb
index b45e520bee..af68c772b1 100644
--- a/actionpack/lib/action_controller/new_base/hide_actions.rb
+++ b/actionpack/lib/action_controller/new_base/hide_actions.rb
@@ -1,39 +1,35 @@
module ActionController
+ # ActionController::HideActions adds the ability to prevent public methods on a controller
+ # to be called as actions.
module HideActions
extend ActiveSupport::Concern
included do
- extlib_inheritable_accessor :hidden_actions
- self.hidden_actions ||= Set.new
+ extlib_inheritable_accessor(:hidden_actions) { Set.new }
end
- def action_methods
- self.class.action_names
- end
+ private
- def action_names
- action_methods
+ # Overrides AbstractController::Base#action_method? to return false if the
+ # action name is in the list of hidden actions.
+ def action_method?(action_name)
+ !hidden_actions.include?(action_name) && super
end
- private
- def action_method?(action_name)
- !hidden_actions.include?(action_name) && super
+ module ClassMethods
+ # Sets all of the actions passed in as hidden actions.
+ #
+ # ==== Parameters
+ # *args<#to_s>:: A list of actions
+ def hide_action(*args)
+ hidden_actions.merge(args.map! {|a| a.to_s })
end
- module ClassMethods
- def hide_action(*args)
- args.each do |arg|
- self.hidden_actions << arg.to_s
- end
- end
-
- def action_methods
- @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)})
- end
-
- def self.action_names
- action_methods
- end
+ # Overrides AbstractController::Base#action_methods to remove any methods
+ # that are listed as hidden methods.
+ def action_methods
+ @action_methods ||= Set.new(super.reject {|name| hidden_actions.include?(name)})
end
+ end
end
end
diff --git a/actionpack/lib/action_controller/new_base/http.rb b/actionpack/lib/action_controller/new_base/http.rb
index c96aaaa865..2e73561f93 100644
--- a/actionpack/lib/action_controller/new_base/http.rb
+++ b/actionpack/lib/action_controller/new_base/http.rb
@@ -2,48 +2,48 @@ require 'action_controller/abstract'
require 'active_support/core_ext/module/delegation'
module ActionController
+ # ActionController::Http provides a way to get a valid Rack application from a controller.
+ #
+ # In AbstractController, dispatching is triggered directly by calling #process on a new controller.
+ # ActionController::Http provides an #action method that returns a valid Rack application for a
+ # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router,
+ # can dispatch directly to the action returned by FooController.action(:index).
class Http < AbstractController::Base
abstract!
# :api: public
attr_internal :params, :env
- # :api: public
+ # Returns the last part of the controller's name, underscored, without the ending
+ # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for
+ # controller_name
+ #
+ # ==== Returns
+ # String
def self.controller_name
@controller_name ||= controller_path.split("/").last
end
- # :api: public
+ # Delegates to the class' #controller_name
def controller_name
self.class.controller_name
end
- # :api: public
+ # Returns the full controller name, underscored, without the ending Controller.
+ # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
+ # controller_name.
+ #
+ # ==== Returns
+ # String
def self.controller_path
@controller_path ||= self.name.sub(/Controller$/, '').underscore
end
- # :api: public
+ # Delegates to the class' #controller_path
def controller_path
self.class.controller_path
end
- # :api: private
- def self.action_names
- action_methods
- end
-
- # :api: private
- def action_names
- action_methods
- end
-
- # :api: plugin
- def self.call(env)
- controller = new
- controller.call(env).to_rack
- end
-
# The details below can be overridden to support a specific
# Request and Response object. The default ActionController::Base
# implementation includes RackConvenience, which makes a request
@@ -57,7 +57,7 @@ module ActionController
super
end
- # Basic implements for content_type=, location=, and headers are
+ # Basic implementations for content_type=, location=, and headers are
# provided to reduce the dependency on the RackConvenience module
# in Renderer and Redirector.
@@ -81,6 +81,15 @@ module ActionController
[status, headers, response_body]
end
+ # Return a rack endpoint for the given action. Memoize the endpoint, so
+ # multiple calls into MyController.action will return the same object
+ # for the same action.
+ #
+ # ==== Parameters
+ # action<#to_s>:: An action name
+ #
+ # ==== Returns
+ # Proc:: A rack application
def self.action(name)
@actions ||= {}
@actions[name.to_s] ||= proc do |env|
diff --git a/actionpack/lib/action_controller/new_base/layouts.rb b/actionpack/lib/action_controller/new_base/layouts.rb
index 0ff71587d6..ace4b148c9 100644
--- a/actionpack/lib/action_controller/new_base/layouts.rb
+++ b/actionpack/lib/action_controller/new_base/layouts.rb
@@ -1,4 +1,163 @@
module ActionController
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
+ # repeated setups. The inclusion pattern has pages that look like this:
+ #
+ # <%= render "shared/header" %>
+ # Hello World
+ # <%= render "shared/footer" %>
+ #
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
+ #
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
+ # that the header and footer are only mentioned in one place, like this:
+ #
+ # // The header part of this layout
+ # <%= yield %>
+ # // The footer part of this layout
+ #
+ # And then you have content pages that look like this:
+ #
+ # hello world
+ #
+ # At rendering time, the content page is computed and then inserted in the layout, like this:
+ #
+ # // The header part of this layout
+ # hello world
+ # // The footer part of this layout
+ #
+ # NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
+ # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
+ #
+ # == Accessing shared variables
+ #
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
+ # references that won't materialize before rendering time:
+ #
+ # <h1><%= @page_title %></h1>
+ # <%= yield %>
+ #
+ # ...and content pages that fulfill these references _at_ rendering time:
+ #
+ # <% @page_title = "Welcome" %>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # The result after rendering is:
+ #
+ # <h1>Welcome</h1>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # == Layout assignment
+ #
+ # You can either specify a layout declaratively (using the #layout class method) or give
+ # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
+ # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
+ #
+ # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
+ # that template will be used for all actions in PostsController and controllers inheriting
+ # from PostsController.
+ #
+ # If you use a module, for instance Weblog::PostsController, you will need a template named
+ # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
+ #
+ # Since all your controllers inherit from ApplicationController, they will use
+ # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
+ # or provided.
+ #
+ # == Inheritance Examples
+ #
+ # class BankController < ActionController::Base
+ # layout "bank_standard"
+ #
+ # class InformationController < BankController
+ #
+ # class TellerController < BankController
+ # # teller.html.erb exists
+ #
+ # class TillController < TellerController
+ #
+ # class VaultController < BankController
+ # layout :access_level_layout
+ #
+ # class EmployeeController < BankController
+ # layout nil
+ #
+ # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
+ #
+ # The TellerController uses +teller.html.erb+, and TillController inherits that layout and
+ # uses it as well.
+ #
+ # == Types of layouts
+ #
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
+ #
+ # The method reference is the preferred approach to variable layouts and is used like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout :writers_and_readers
+ #
+ # def index
+ # # fetching posts
+ # end
+ #
+ # private
+ # def writers_and_readers
+ # logged_in? ? "writer_layout" : "reader_layout"
+ # end
+ #
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
+ # is logged in or not.
+ #
+ # If you want to use an inline method, such as a proc, do something like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ #
+ # Of course, the most common way of specifying a layout is still just as a plain template name:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
+ # Otherwise, it will be looked up relative to the template root.
+ #
+ # == Conditional layouts
+ #
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
+ # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
+ # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard", :except => :rss
+ #
+ # # ...
+ #
+ # end
+ #
+ # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
+ # around the rendered view.
+ #
+ # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
+ # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
+ #
+ # == Using a different layout in the action render call
+ #
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
+ # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
+ # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # def help
+ # render :action => "help", :layout => "help"
+ # end
+ # end
+ #
+ # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
module Layouts
extend ActiveSupport::Concern
@@ -6,6 +165,7 @@ module ActionController
include AbstractController::Layouts
module ClassMethods
+ # If no layout is provided, look for a layout with this name.
def _implied_layout_name
controller_path
end
@@ -14,16 +174,17 @@ module ActionController
private
def _determine_template(options)
super
- if (!options.key?(:text) && !options.key?(:inline) && !options.key?(:partial)) || options.key?(:layout)
- options[:_layout] = _layout_for_option(options.key?(:layout) ? options[:layout] : :none, options[:_template].details)
- end
+
+ return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout)
+ layout = options.key?(:layout) ? options[:layout] : :none
+ options[:_layout] = _layout_for_option(layout, options[:_template].details)
end
def _layout_for_option(name, details)
case name
when String then _layout_for_name(name, details)
- when true then _default_layout(true, details)
- when :none then _default_layout(false, details)
+ when true then _default_layout(details, true)
+ when :none then _default_layout(details, false)
when false, nil then nil
else
raise ArgumentError,