diff options
author | Emilio Tagua <miloops@gmail.com> | 2009-06-10 19:54:27 -0300 |
---|---|---|
committer | Emilio Tagua <miloops@gmail.com> | 2009-06-10 19:54:27 -0300 |
commit | 247ac954132222fdd399cddc264bc1d4029f750c (patch) | |
tree | 680a07197aa207279a3455327c62843b256fe88a /actionpack/lib/action_controller | |
parent | d7b98b3ca855418f88a9e979d32c1710040b3038 (diff) | |
parent | 47ff57f6d14fe161900bf85e2d2cf6d7e21a1eb8 (diff) | |
download | rails-247ac954132222fdd399cddc264bc1d4029f750c.tar.gz rails-247ac954132222fdd399cddc264bc1d4029f750c.tar.bz2 rails-247ac954132222fdd399cddc264bc1d4029f750c.zip |
Merge commit 'rails/master'
Diffstat (limited to 'actionpack/lib/action_controller')
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=" ") + # 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, |