diff options
Diffstat (limited to 'actionpack/lib/abstract_controller')
-rw-r--r-- | actionpack/lib/abstract_controller/base.rb | 50 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/callbacks.rb | 87 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/collector.rb | 12 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/helpers.rb | 41 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/layouts.rb | 418 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/railties/routes_helpers.rb | 6 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/rendering.rb | 190 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/translation.rb | 11 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/url_for.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/view_paths.rb | 96 |
10 files changed, 188 insertions, 725 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 56dc9ab7a1..51c661f735 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -8,7 +8,8 @@ module AbstractController class Error < StandardError #:nodoc: end - class ActionNotFound < StandardError #:nodoc: + # Raised when a non-existing controller action is triggered. + class ActionNotFound < StandardError end # <tt>AbstractController::Base</tt> is a low-level API. Nobody should be @@ -35,7 +36,7 @@ module AbstractController end def inherited(klass) # :nodoc: - # define the abstract ivar on subclasses so that we don't get + # Define the abstract ivar on subclasses so that we don't get # uninitialized ivar warnings unless klass.instance_variable_defined?(:@abstract) klass.instance_variable_set(:@abstract, false) @@ -81,7 +82,7 @@ module AbstractController # Except for public instance methods of Base and its ancestors internal_methods + # Be sure to include shadowed public instance methods of this class - public_instance_methods(false)).uniq.map { |x| x.to_s } - + public_instance_methods(false)).uniq.map(&:to_s) - # And always exclude explicitly hidden actions hidden_actions.to_a @@ -120,14 +121,14 @@ module AbstractController # # The actual method that is called is determined by calling # #method_for_action. If no method can handle the action, then an - # ActionNotFound error is raised. + # AbstractController::ActionNotFound error is raised. # # ==== Returns # * <tt>self</tt> def process(action, *args) - @_action_name = action_name = action.to_s + @_action_name = action.to_s - unless action_name = method_for_action(action_name) + unless action_name = _find_action_name(@_action_name) raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}" end @@ -160,7 +161,15 @@ module AbstractController # ==== Returns # * <tt>TrueClass</tt>, <tt>FalseClass</tt> def available_action?(action_name) - method_for_action(action_name).present? + _find_action_name(action_name).present? + end + + # Returns true if the given controller is capable of rendering + # a path. A subclass of +AbstractController::Base+ + # may return false. An Email controller for example does not + # support paths, only full URLs. + def self.supports_path? + true end private @@ -204,6 +213,24 @@ module AbstractController end # Takes an action name and returns the name of the method that will + # handle the action. + # + # It checks if the action name is valid and returns false otherwise. + # + # See method_for_action for more information. + # + # ==== Parameters + # * <tt>action_name</tt> - An action name to find a method name for + # + # ==== Returns + # * <tt>string</tt> - The name of the method that handles the action + # * false - No valid method name could be found. + # Raise AbstractController::ActionNotFound. + def _find_action_name(action_name) + _valid_action_name?(action_name) && method_for_action(action_name) + end + + # Takes an action name and returns the name of the method that will # handle the action. In normal cases, this method returns the same # name as it receives. By default, if #method_for_action receives # a name that is not an action, it will look for an #action_missing @@ -218,14 +245,14 @@ module AbstractController # the case. # # If none of these conditions are true, and method_for_action - # returns nil, an ActionNotFound exception will be raised. + # returns nil, an AbstractController::ActionNotFound exception will be raised. # # ==== Parameters # * <tt>action_name</tt> - An action name to find a method name for # # ==== Returns # * <tt>string</tt> - The name of the method that handles the action - # * <tt>nil</tt> - No method name could be found. Raise ActionNotFound. + # * <tt>nil</tt> - No method name could be found. def method_for_action(action_name) if action_method?(action_name) action_name @@ -233,5 +260,10 @@ module AbstractController "_handle_action_missing" end end + + # Checks if the action name is valid and returns false otherwise. + def _valid_action_name?(action_name) + !action_name.to_s.include? File::SEPARATOR + end end end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 599fff81c2..ca5c80cd71 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -8,7 +8,9 @@ module AbstractController include ActiveSupport::Callbacks included do - define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true + define_callbacks :process_action, + terminator: ->(controller,_) { controller.response_body }, + skip_after_callbacks_if_terminated: true end # Override AbstractController::Base's process_action to run the @@ -36,24 +38,22 @@ module AbstractController def _normalize_callback_option(options, from, to) # :nodoc: if from = options[from] from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ") - options[to] = Array(options[to]) << from + options[to] = Array(options[to]).unshift(from) end end - # Skip before, after, and around action callbacks matching any of the names - # Aliased as skip_filter. + # Skip before, after, and around action callbacks matching any of the names. # # ==== Parameters # * <tt>names</tt> - A list of valid names that could be used for # callbacks. Note that skipping uses Ruby equality, so it's # impossible to skip a callback defined using an anonymous proc - # using #skip_filter + # using #skip_action_callback def skip_action_callback(*names) skip_before_action(*names) skip_after_action(*names) skip_around_action(*names) end - alias_method :skip_filter, :skip_action_callback # Take callback names and an optional callback proc, normalize them, @@ -69,7 +69,7 @@ module AbstractController # * <tt>name</tt> - The callback to be added # * <tt>options</tt> - A hash of options to be used when adding the callback def _insert_callbacks(callbacks, block = nil) - options = callbacks.last.is_a?(Hash) ? callbacks.pop : {} + options = callbacks.extract_options! _normalize_callback_options(options) callbacks.push(block) if block callbacks.each do |callback| @@ -83,7 +83,6 @@ module AbstractController # :call-seq: before_action(names, block) # # Append a callback before actions. See _insert_callbacks for parameter details. - # Aliased as before_filter. ## # :method: prepend_before_action @@ -91,7 +90,6 @@ module AbstractController # :call-seq: prepend_before_action(names, block) # # Prepend a callback before actions. See _insert_callbacks for parameter details. - # Aliased as prepend_before_filter. ## # :method: skip_before_action @@ -99,7 +97,6 @@ module AbstractController # :call-seq: skip_before_action(names) # # Skip a callback before actions. See _insert_callbacks for parameter details. - # Aliased as skip_before_filter. ## # :method: append_before_action @@ -107,7 +104,6 @@ module AbstractController # :call-seq: append_before_action(names, block) # # Append a callback before actions. See _insert_callbacks for parameter details. - # Aliased as append_before_filter. ## # :method: after_action @@ -115,7 +111,6 @@ module AbstractController # :call-seq: after_action(names, block) # # Append a callback after actions. See _insert_callbacks for parameter details. - # Aliased as after_filter. ## # :method: prepend_after_action @@ -123,7 +118,6 @@ module AbstractController # :call-seq: prepend_after_action(names, block) # # Prepend a callback after actions. See _insert_callbacks for parameter details. - # Aliased as prepend_after_filter. ## # :method: skip_after_action @@ -131,7 +125,6 @@ module AbstractController # :call-seq: skip_after_action(names) # # Skip a callback after actions. See _insert_callbacks for parameter details. - # Aliased as skip_after_filter. ## # :method: append_after_action @@ -139,7 +132,6 @@ module AbstractController # :call-seq: append_after_action(names, block) # # Append a callback after actions. See _insert_callbacks for parameter details. - # Aliased as append_after_filter. ## # :method: around_action @@ -147,7 +139,6 @@ module AbstractController # :call-seq: around_action(names, block) # # Append a callback around actions. See _insert_callbacks for parameter details. - # Aliased as around_filter. ## # :method: prepend_around_action @@ -155,7 +146,6 @@ module AbstractController # :call-seq: prepend_around_action(names, block) # # Prepend a callback around actions. See _insert_callbacks for parameter details. - # Aliased as prepend_around_filter. ## # :method: skip_around_action @@ -163,7 +153,6 @@ module AbstractController # :call-seq: skip_around_action(names) # # Skip a callback around actions. See _insert_callbacks for parameter details. - # Aliased as skip_around_filter. ## # :method: append_around_action @@ -171,46 +160,36 @@ module AbstractController # :call-seq: append_around_action(names, block) # # Append a callback around actions. See _insert_callbacks for parameter details. - # Aliased as append_around_filter. # set up before_action, prepend_before_action, skip_before_action, etc. # for each of before, after, and around. [:before, :after, :around].each do |callback| - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - # Append a before, after or around callback. See _insert_callbacks - # for details on the allowed parameters. - def #{callback}_action(*names, &blk) # def before_action(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options) - end # end - end # end - - alias_method :#{callback}_filter, :#{callback}_action - - # Prepend a before, after or around callback. See _insert_callbacks - # for details on the allowed parameters. - def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) - end # end - end # end - - alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action - - # Skip a before, after or around callback. See _insert_callbacks - # for details on the allowed parameters. - def skip_#{callback}_action(*names) # def skip_before_action(*names) - _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options| - skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options) - end # end - end # end - - alias_method :skip_#{callback}_filter, :skip_#{callback}_action - - # *_action is the same as append_*_action - alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action - alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action - RUBY_EVAL + define_method "#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options) + end + end + alias_method :"#{callback}_filter", :"#{callback}_action" + + define_method "prepend_#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options.merge(:prepend => true)) + end + end + alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action" + + # Skip a before, after or around callback. See _insert_callbacks + # for details on the allowed parameters. + define_method "skip_#{callback}_action" do |*names| + _insert_callbacks(names) do |name, options| + skip_callback(:process_action, callback, name, options) + end + end + alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action" + + # *_action is the same as append_*_action + alias_method :"append_#{callback}_action", :"#{callback}_action" + alias_method :"append_#{callback}_filter", :"#{callback}_action" end end end diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 09b9e7ddf0..ddd56b354a 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -23,7 +23,17 @@ module AbstractController protected def method_missing(symbol, &block) - mime_constant = Mime.const_get(symbol.upcase) + const_name = symbol.upcase + + unless Mime.const_defined?(const_name) + raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \ + "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \ + "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \ + "be sure to nest your variant response within a format response: " \ + "format.html { |html| html.tablet { ... } }" + end + + mime_constant = Mime.const_get(const_name) if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 55e9fd9c8d..109eff10eb 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -12,6 +12,20 @@ module AbstractController self._helper_methods = Array.new end + class MissingHelperError < LoadError + def initialize(error, path) + @error = error + @path = "helpers/#{path}.rb" + set_backtrace error.backtrace + + if error.path =~ /^#{path}(\.rb)?$/ + super("Missing helper file helpers/%s.rb" % path) + else + raise error + end + end + 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 @@ -29,7 +43,7 @@ module AbstractController # helper_method :current_user, :logged_in? # # def current_user - # @current_user ||= User.find_by_id(session[:user]) + # @current_user ||= User.find_by(id: session[:user]) # end # # def logged_in? @@ -59,7 +73,7 @@ module AbstractController # The +helper+ class method can take a series of helper module names, a block, or both. # # ==== Options - # * <tt>*args</tt> - Module, Symbol, String, :all + # * <tt>*args</tt> - Module, Symbol, String # * <tt>block</tt> - A block defining helper methods # # When the argument is a module it will be included directly in the template class. @@ -134,9 +148,19 @@ module AbstractController begin require_dependency(file_name) rescue LoadError => e - raise MissingHelperError.new(e, file_name) + raise AbstractController::Helpers::MissingHelperError.new(e, file_name) + end + + mod_name = file_name.camelize + begin + mod_name.constantize + rescue LoadError + # dependencies.rb gives a similar error message but its wording is + # not as clear because it mentions autoloading. To the user all it + # matters is that a helper module couldn't be loaded, autoloading + # is an internal mechanism that should not leak. + raise NameError, "Couldn't find #{mod_name}, expected it to be defined in helpers/#{file_name}.rb" end - file_name.camelize.constantize when Module arg else @@ -145,15 +169,6 @@ module AbstractController end end - class MissingHelperError < LoadError - def initialize(error, path) - @error = error - @path = "helpers/#{path}.rb" - set_backtrace error.backtrace - super("Missing helper file helpers/%s.rb" % path) - end - end - private # Makes all the (instance) methods in the helper module available to templates # rendered through this controller. diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb deleted file mode 100644 index 73799e8085..0000000000 --- a/actionpack/lib/abstract_controller/layouts.rb +++ /dev/null @@ -1,418 +0,0 @@ -require "active_support/core_ext/module/remove_method" - -module AbstractController - # 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 - # - # == 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 - # # bank.html.erb exists - # - # class ExchangeController < BankController - # # exchange.html.erb exists - # - # class CurrencyController < BankController - # - # class InformationController < BankController - # layout "information" - # - # class TellerController < InformationController - # # teller.html.erb exists - # - # class EmployeeController < InformationController - # # employee.html.erb exists - # layout nil - # - # class VaultController < BankController - # layout :access_level_layout - # - # class TillController < BankController - # layout false - # - # In these examples, we have three implicit lookup scenarios: - # * The BankController uses the "bank" layout. - # * The ExchangeController uses the "exchange" layout. - # * The CurrencyController inherits the layout from BankController. - # - # However, when a layout is explicitly set, the explicitly set layout wins: - # * The InformationController uses the "information" layout, explicitly set. - # * The TellerController also uses the "information" layout, because the parent explicitly set it. - # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration. - # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method. - # * The TillController does not use a layout at all. - # - # == 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 - # 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" } - # end - # - # If an argument isn't given to the proc, it's evaluated in the context of - # the current controller anyway. - # - # class WeblogController < ActionController::Base - # layout proc { logged_in? ? "writer_layout" : "reader_layout" } - # end - # - # 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" - # end - # - # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point - # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>. - # - # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists. - # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent: - # - # class ApplicationController < ActionController::Base - # layout "application" - # end - # - # class PostsController < ApplicationController - # # Will use "application" layout - # end - # - # class CommentsController < ApplicationController - # # Will search for "comments" layout and fallback "application" layout - # layout nil - # end - # - # == 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 for all actions except for the +rss+ action, which will - # be rendered directly, without wrapping 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 override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. - module Layouts - extend ActiveSupport::Concern - - include Rendering - - included do - class_attribute :_layout, :_layout_conditions, :instance_accessor => false - self._layout = nil - self._layout_conditions = {} - _write_layout_method - end - - delegate :_layout_conditions, :to => "self.class" - - module ClassMethods - def inherited(klass) # :nodoc: - super - klass._write_layout_method - end - - # This module is mixed in if layout conditions are provided. This means - # that if no layout conditions are used, this method is not used - module LayoutConditions # :nodoc: - private - - # Determines whether the current action has a layout definition by - # checking the action name against the :only and :except conditions - # set by the <tt>layout</tt> method. - # - # ==== Returns - # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise. - def _conditional_layout? - return unless super - - conditions = _layout_conditions - - if only = conditions[:only] - only.include?(action_name) - elsif except = conditions[:except] - !except.include?(action_name) - else - true - end - end - end - - # 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 - # nil:: Force default layout behavior with inheritance - # - # ==== Parameters - # * <tt>layout</tt> - The layout to use. - # - # ==== Options (conditions) - # * :only - A list of actions to apply this layout to. - # * :except - Apply this layout to all actions but this one. - def layout(layout, conditions = {}) - include LayoutConditions unless conditions.empty? - - conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } - self._layout_conditions = conditions - - self._layout = layout - _write_layout_method - end - - # If no layout is supplied, look for a template named the return - # value of this method. - # - # ==== Returns - # * <tt>String</tt> - A template name - def _implied_layout_name # :nodoc: - controller_path - end - - # Creates a _layout method to be called by _default_layout . - # - # If a layout is not explicitly mentioned then look for a layout with the controller's name. - # if nothing is found then try same procedure to find super class's layout. - def _write_layout_method # :nodoc: - remove_possible_method(:_layout) - - prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"] - name_clause = if name - <<-RUBY - lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super - RUBY - else - <<-RUBY - super - RUBY - end - - layout_definition = case _layout - when String - _layout.inspect - when Symbol - <<-RUBY - #{_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 - RUBY - when Proc - define_method :_layout_from_proc, &_layout - _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)" - when false - nil - when true - raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil" - when nil - name_clause - end - - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout - if _conditional_layout? - #{layout_definition} - else - #{name_clause} - end - end - private :_layout - RUBY - end - end - - def _normalize_options(options) # :nodoc: - super - - if _include_layout?(options) - layout = options.delete(:layout) { :default } - options[:layout] = _layout_for_option(layout) - end - end - - attr_internal_writer :action_has_layout - - def initialize(*) # :nodoc: - @_action_has_layout = true - super - end - - # Controls whether an action should be rendered using a layout. - # If you want to disable any <tt>layout</tt> settings for the - # current action so that it is rendered without a layout then - # either override this method in your controller to return false - # for that action or set the <tt>action_has_layout</tt> attribute - # to false before rendering. - def action_has_layout? - @_action_has_layout - end - - private - - def _conditional_layout? - true - end - - # This will be overwritten by _write_layout_method - def _layout; end - - # Determine the layout for a given name, taking into account the name type. - # - # ==== Parameters - # * <tt>name</tt> - The name of the template - def _layout_for_option(name) - case name - when String then _normalize_layout(name) - when Proc then name - when true then Proc.new { _default_layout(true) } - when :default then Proc.new { _default_layout(false) } - when false, nil then nil - else - raise ArgumentError, - "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" - end - end - - def _normalize_layout(value) - value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value - end - - # Returns the default layout for this controller. - # Optionally raises an exception if the layout could not be found. - # - # ==== Parameters - # * <tt>require_layout</tt> - If set to true and layout is not found, - # an ArgumentError exception is raised (defaults to false) - # - # ==== Returns - # * <tt>template</tt> - The template object for the default layout (or nil) - def _default_layout(require_layout = false) - begin - value = _layout if action_has_layout? - rescue NameError => e - raise e, "Could not render layout: #{e.message}" - end - - if require_layout && action_has_layout? && !value - raise ArgumentError, - "There was no default layout for #{self.class} in #{view_paths.inspect}" - end - - _normalize_layout(value) - end - - def _include_layout?(options) - (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) - end - end -end diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb index 6684f46f64..568c47e43a 100644 --- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb +++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb @@ -1,14 +1,14 @@ module AbstractController module Railties module RoutesHelpers - def self.with(routes) + def self.with(routes, include_path_helpers = true) Module.new do define_method(:inherited) do |klass| super(klass) if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) } - klass.send(:include, namespace.railtie_routes_url_helpers) + klass.send(:include, namespace.railtie_routes_url_helpers(include_path_helpers)) else - klass.send(:include, routes.url_helpers) + klass.send(:include, routes.url_helpers(include_path_helpers)) end end end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 4b984d0558..9d10140ed2 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,8 @@ -require "abstract_controller/base" -require "action_view" +require 'active_support/concern' +require 'active_support/core_ext/class/attribute' +require 'action_view' +require 'action_view/view_paths' +require 'set' module AbstractController class DoubleRenderError < Error @@ -10,179 +13,108 @@ module AbstractController end end - # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, - # it will trigger the lookup_context and consequently expire the cache. - class I18nProxy < ::I18n::Config #:nodoc: - attr_reader :original_config, :lookup_context - - def initialize(original_config, lookup_context) - original_config = original_config.original_config if original_config.respond_to?(:original_config) - @original_config, @lookup_context = original_config, lookup_context - end - - def locale - @original_config.locale - end - - def locale=(value) - @lookup_context.locale = value - end - end - module Rendering extend ActiveSupport::Concern - include AbstractController::ViewPaths - - included do - class_attribute :protected_instance_variables - self.protected_instance_variables = [] - end - - # Overwrite process to setup I18n proxy. - def process(*) #:nodoc: - old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) - super - ensure - I18n.config = old_config - end - - module ClassMethods - def view_context_class - @view_context_class ||= begin - routes = respond_to?(:_routes) && _routes - helpers = respond_to?(:_helpers) && _helpers - - Class.new(ActionView::Base) do - if routes - include routes.url_helpers - include routes.mounted_helpers - end - - if helpers - include helpers - end - end - end - end - end - - attr_internal_writer :view_context_class - - def view_context_class - @_view_context_class ||= self.class.view_context_class - end - - # An instance of a view class. The default view class is ActionView::Base - # - # The view class must have the following methods: - # View.new[lookup_context, assigns, controller] - # Create a new ActionView instance for a controller - # View#render[options] - # Returns String with the rendered template - # - # Override this method in a module to change the default behavior. - def view_context - view_context_class.new(view_renderer, view_assigns, self) - end - - # Returns an object that is able to render templates. - def view_renderer - @_view_renderer ||= ActionView::Renderer.new(lookup_context) - end + include ActionView::ViewPaths # Normalize arguments, options and then delegates render_to_body and # sticks the result in self.response_body. + # :api: public def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) + _process_format(rendered_format, options) if rendered_format + self.response_body end - # Raw rendering of a template to a string. Just convert the results of - # render_response into a String. + # Raw rendering of a template to a string. + # + # It is similar to render, except that it does not + # set the response_body and it should be guaranteed + # to always return a string. + # + # If a component extends the semantics of response_body + # (as Action Controller extends it to be anything that + # responds to the method each), this method needs to be + # overridden in order to still return a string. # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) end - # Raw rendering of a template to a Rack-compatible body. - # :api: plugin + # Performs the actual template rendering. + # :api: public def render_to_body(options = {}) - _process_options(options) - _render_template(options) end - # Find and renders a template based on the options given. - # :api: private - def _render_template(options) #:nodoc: - lookup_context.rendered_format = nil if options[:formats] - view_renderer.render(view_context, options) + # Returns Content-Type of rendered content + # :api: public + def rendered_format + Mime::TEXT end - DEFAULT_PROTECTED_INSTANCE_VARIABLES = [ - :@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config, - :@_view_context_class, :@_view_renderer, :@_lookup_context - ] + DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %w( + @_action_name @_response_body @_formats @_prefixes @_config + @_view_context_class @_view_renderer @_lookup_context + @_routes @_db_runtime + ).map(&:to_sym) # This method should return a hash with assigns. # You can overwrite this configuration per controller. # :api: public def view_assigns - hash = {} - variables = instance_variables - variables -= protected_instance_variables - variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES - variables.each { |name| hash[name[1..-1]] = instance_variable_get(name) } - hash - end + protected_vars = _protected_ivars + variables = instance_variables - private - - # Normalize args and options. - # :api: private - def _normalize_render(*args, &block) - options = _normalize_args(*args, &block) - _normalize_options(options) - options + variables.reject! { |s| protected_vars.include? s } + variables.each_with_object({}) { |name, hash| + hash[name.slice(1, name.length)] = instance_variable_get(name) + } end # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :file => "foo/bar". # :api: plugin def _normalize_args(action=nil, options={}) - case action - when NilClass - when Hash - options = action - when String, Symbol - action = action.to_s - key = action.include?(?/) ? :file : :action - options[key] = action + if action.is_a? Hash + action else - options[:partial] = action + options end - - options end # Normalize options. # :api: plugin def _normalize_options(options) - if options[:partial] == true - options[:partial] = action_name - end - - if (options.keys & [:partial, :file, :template]).empty? - options[:prefixes] ||= _prefixes - end - - options[:template] ||= (options[:action] || action_name).to_s options end # Process extra options. # :api: plugin def _process_options(options) + options + end + + # Process the rendered format. + # :api: private + def _process_format(format, options = {}) + end + + # Normalize args and options. + # :api: private + def _normalize_render(*args, &block) + options = _normalize_args(*args, &block) + #TODO: remove defined? when we restore AP <=> AV dependency + if defined?(request) && request && request.variant.present? + options[:variant] = request.variant + end + _normalize_options(options) + options + end + + def _protected_ivars # :nodoc: + DEFAULT_PROTECTED_INSTANCE_VARIABLES end end end diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index b6c484d188..02028d8e05 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -1,9 +1,17 @@ module AbstractController module Translation + # Delegates to <tt>I18n.translate</tt>. Also aliased as <tt>t</tt>. + # + # When the given key starts with a period, it will be scoped by the current + # controller and action. So if you call <tt>translate(".foo")</tt> from + # <tt>PeopleController#index</tt>, it will convert the call to + # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive + # to translate many keys within the same controller / action and gives you a + # simple framework for scoping them consistently. def translate(*args) key = args.first if key.is_a?(String) && (key[0] == '.') - key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }" + key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }" args[0] = key end @@ -11,6 +19,7 @@ module AbstractController end alias :t :translate + # Delegates to <tt>I18n.localize</tt>. Also aliased as <tt>l</tt>. def localize(*args) I18n.localize(*args) end diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb index 4a95e1f276..72d07b0927 100644 --- a/actionpack/lib/abstract_controller/url_for.rb +++ b/actionpack/lib/abstract_controller/url_for.rb @@ -11,7 +11,7 @@ module AbstractController def _routes raise "In order to use #url_for, you must include routing helpers explicitly. " \ - "For instance, `include Rails.application.routes.url_helpers" + "For instance, `include Rails.application.routes.url_helpers`." end module ClassMethods diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb deleted file mode 100644 index c08b3a0e2a..0000000000 --- a/actionpack/lib/abstract_controller/view_paths.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'action_view/base' - -module AbstractController - module ViewPaths - extend ActiveSupport::Concern - - included do - class_attribute :_view_paths - self._view_paths = ActionView::PathSet.new - self._view_paths.freeze - end - - delegate :template_exists?, :view_paths, :formats, :formats=, - :locale, :locale=, :to => :lookup_context - - module ClassMethods - def parent_prefixes - @parent_prefixes ||= begin - parent_controller = superclass - prefixes = [] - - until parent_controller.abstract? - prefixes << parent_controller.controller_path - parent_controller = parent_controller.superclass - end - - prefixes - end - end - end - - # The prefixes used in render "foo" shortcuts. - def _prefixes - @_prefixes ||= begin - parent_prefixes = self.class.parent_prefixes - parent_prefixes.dup.unshift(controller_path) - end - end - - # LookupContext is the object responsible to hold all information required to lookup - # templates, i.e. view paths and details. Check ActionView::LookupContext for more - # information. - def lookup_context - @_lookup_context ||= - ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes) - end - - def details_for_lookup - { } - end - - def append_view_path(path) - lookup_context.view_paths.push(*path) - end - - def prepend_view_path(path) - lookup_context.view_paths.unshift(*path) - end - - module ClassMethods - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # * <tt>path</tt> - If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::PathSet for more information) - def append_view_path(path) - self._view_paths = view_paths + Array(path) - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # * <tt>path</tt> - If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::PathSet for more information) - def prepend_view_path(path) - self._view_paths = ActionView::PathSet.new(Array(path) + view_paths) - end - - # A list of all of the default view paths for this controller. - def view_paths - _view_paths - end - - # Set the view paths. - # - # ==== Parameters - # * <tt>paths</tt> - If a PathSet is provided, use that; - # otherwise, process the parameter into a PathSet. - def view_paths=(paths) - self._view_paths = ActionView::PathSet.new(Array(paths)) - end - end - end -end |