diff options
Diffstat (limited to 'actionpack/lib/abstract_controller')
-rw-r--r-- | actionpack/lib/abstract_controller/base.rb | 88 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/callbacks.rb | 122 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/collector.rb | 12 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/helpers.rb | 17 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/railties/routes_helpers.rb | 6 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/rendering.rb | 85 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/url_for.rb | 2 |
7 files changed, 209 insertions, 123 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index a84ed17bd4..c95b9a4097 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -8,10 +8,11 @@ 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 + # AbstractController::Base is a low-level API. Nobody should be # using it directly, and subclasses (like ActionController::Base) are # expected to provide their own +render+ method, since rendering means # different things depending on the context. @@ -56,21 +57,11 @@ module AbstractController controller.public_instance_methods(true) end - # The list of hidden actions. Defaults to an empty array. - # This can be modified by other modules or subclasses - # to specify particular actions as hidden. - # - # ==== Returns - # * <tt>Array</tt> - An array of method names that should not be considered actions. - def hidden_actions - [] - end - # A list of method names that should be considered actions. This # includes all public instance methods on a controller, less - # any internal methods (see #internal_methods), adding back in + # any internal methods (see internal_methods), adding back in # any methods that are internal, but still exist on the class - # itself. Finally, #hidden_actions are removed. + # itself. # # ==== Returns # * <tt>Set</tt> - A set of all methods that should be considered actions. @@ -81,25 +72,26 @@ 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 } - - # And always exclude explicitly hidden actions - hidden_actions.to_a + public_instance_methods(false)).uniq.map(&:to_s) - # Clear out AS callback method pollution - Set.new(methods.reject { |method| method =~ /_one_time_conditions/ }) + methods.to_set end end # action_methods are cached and there is sometimes need to refresh - # them. clear_action_methods! allows you to do that, so next time + # them. ::clear_action_methods! allows you to do that, so next time # you run action_methods, they will be recalculated def clear_action_methods! @action_methods = nil end # Returns the full controller name, underscored, without the ending Controller. - # For instance, MyApp::MyPostsController would return "my_app/my_posts" for - # controller_path. + # + # class MyApp::MyPostsController < AbstractController::Base + # end + # end + # + # MyApp::MyPostsController.controller_path # => "my_app/my_posts" # # ==== Returns # * <tt>String</tt> @@ -114,25 +106,20 @@ module AbstractController end end - # Define some internal variables that should not be propagated to the view. - def self.default_protected_instance_vars - [] - end - abstract! # Calls the action going through the entire action dispatch stack. # # 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 @@ -141,12 +128,12 @@ module AbstractController process_action(action_name, *args) end - # Delegates to the class' #controller_path + # Delegates to the class' ::controller_path def controller_path self.class.controller_path end - # Delegates to the class' #action_methods + # Delegates to the class' ::action_methods def action_methods self.class.action_methods end @@ -165,7 +152,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 @@ -209,6 +204,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 @@ -223,14 +236,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 @@ -238,5 +251,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 d6c941832f..69f490b327 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' + module AbstractController module Callbacks extend ActiveSupport::Concern @@ -9,7 +11,7 @@ module AbstractController included do define_callbacks :process_action, - terminator: ->(controller,_) { controller.response_body }, + terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.response_body }, skip_after_callbacks_if_terminated: true end @@ -22,10 +24,21 @@ module AbstractController end module ClassMethods - # If :only or :except are used, convert the options into the - # :unless and :if options of ActiveSupport::Callbacks. - # The basic idea is that :only => :index gets converted to - # :if => proc {|c| c.action_name == "index" }. + # If +:only+ or +:except+ are used, convert the options into the + # +:if+ and +:unless+ options of ActiveSupport::Callbacks. + # + # The basic idea is that <tt>:only => :index</tt> gets converted to + # <tt>:if => proc {|c| c.action_name == "index" }</tt>. + # + # Note that <tt>:only</tt> has priority over <tt>:if</tt> in case they + # are used together. + # + # only: :index, if: -> { true } # the :if option will be ignored. + # + # Note that <tt>:if</tt> has priority over <tt>:except</tt> in case they + # are used together. + # + # except: :index, if: -> { true } # the :except option will be ignored. # # ==== Options # * <tt>only</tt> - The callback should be run only for this action @@ -42,21 +55,23 @@ module AbstractController 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 + def skip_filter(*names) + ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.") + skip_action_callback(*names) + end # Take callback names and an optional callback proc, normalize them, # then call the block with each callback. This allows us to abstract @@ -85,7 +100,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 @@ -93,7 +107,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 @@ -101,7 +114,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 @@ -109,7 +121,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 @@ -117,7 +128,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 @@ -125,7 +135,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 @@ -133,7 +142,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 @@ -141,7 +149,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 @@ -149,7 +156,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 @@ -157,7 +163,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 @@ -165,7 +170,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 @@ -173,46 +177,52 @@ 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 + + define_method "#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.") + send("#{callback}_action", *names, &blk) + end + + 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 + + define_method "prepend_#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use prepend_#{callback}_action instead.") + send("prepend_#{callback}_action", *names, &blk) + end + + # 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 + + define_method "skip_#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use skip_#{callback}_action instead.") + send("skip_#{callback}_action", *names, &blk) + end + + # *_action is the same as append_*_action + alias_method :"append_#{callback}_action", :"#{callback}_action" + + define_method "append_#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.") + send("append_#{callback}_action", *names, &blk) + end 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 e77e4e01e9..109eff10eb 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -27,9 +27,6 @@ module AbstractController end module ClassMethods - MissingHelperError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('AbstractController::Helpers::ClassMethods::MissingHelperError', - 'AbstractController::Helpers::MissingHelperError') - # 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. @@ -153,7 +150,17 @@ module AbstractController rescue LoadError => e raise AbstractController::Helpers::MissingHelperError.new(e, file_name) end - file_name.camelize.constantize + + 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 when Module arg else @@ -177,7 +184,7 @@ module AbstractController module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path - rescue MissingSourceFile => e + rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb index 6684f46f64..14b574e322 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.include(namespace.railtie_routes_url_helpers(include_path_helpers)) else - klass.send(:include, routes.url_helpers) + klass.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 41f19fba78..5514213ad8 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,8 @@ 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 @@ -12,57 +15,74 @@ module AbstractController module Rendering extend ActiveSupport::Concern + include ActionView::ViewPaths - def self.default_protected_instance_vars - super.concat [:@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config] + # Normalizes arguments, options and then delegates render_to_body and + # sticks the result in <tt>self.response_body</tt>. + # :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. # # It is similar to render, except that it does not - # set the response_body and it should be guaranteed + # 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 + # If a component extends the semantics of +response_body+ + # (as ActionController 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. - # :api: plugin - def render_to_body(options = {}) - end - - # Normalize arguments, options and then delegates render_to_body and - # sticks the result in self.response_body. + # Performs the actual template rendering. # :api: public - def render(*args, &block) + def render_to_body(options = {}) end - # Return Content-Type of rendered content + # Returns Content-Type of rendered content # :api: public def rendered_format + Mime::TEXT end + 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 = {} - (instance_variables - self.class.default_protected_instance_vars).each do |name| - hash[name[1..-1]] = instance_variable_get(name) - end - hash + protected_vars = _protected_ivars + variables = instance_variables + + 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". + # Normalize args by converting <tt>render "foo"</tt> to + # <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to + # <tt>render :file => "foo/bar"</tt>. # :api: plugin def _normalize_args(action=nil, options={}) - options + if action.is_a? Hash + action + else + options + end end # Normalize options. @@ -76,5 +96,26 @@ module AbstractController 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/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 |