123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- require 'erubis'
- require 'active_support/configurable'
- require 'active_support/descendants_tracker'
- require 'active_support/core_ext/module/anonymous'
- module AbstractController
- class Error < StandardError; end
- class ActionNotFound < StandardError; end
- # <tt>AbstractController::Base</tt> 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.
- class Base
- attr_internal :response_body
- attr_internal :action_name
- attr_internal :formats
- include ActiveSupport::Configurable
- extend ActiveSupport::DescendantsTracker
- undef_method :not_implemented
- class << self
- attr_reader :abstract
- alias_method :abstract?, :abstract
- # Define a controller as abstract. See internal_methods for more
- # details.
- def abstract!
- @abstract = true
- end
- # A list of all internal methods for a controller. This finds the first
- # abstract superclass of a controller, and gets a list of all public
- # instance methods on that abstract class. Public instance methods of
- # a controller would normally be considered action methods, so methods
- # declared on abstract classes are being removed.
- # (ActionController::Metal and ActionController::Base are defined as abstract)
- def internal_methods
- controller = self
- controller = controller.superclass until controller.abstract?
- controller.public_instance_methods(true)
- end
- # The list of hidden actions to an empty array. 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 methods that are internal, but still exist on the class
- # itself. Finally, #hidden_actions are removed.
- #
- # ==== Returns
- # * <tt>array</tt> - A list of all methods that should be considered actions.
- def action_methods
- @action_methods ||= begin
- # All public instance methods of this class, including ancestors
- methods = (public_instance_methods(true) -
- # 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
- # Clear out AS callback method pollution
- methods.reject { |method| method =~ /_one_time_conditions/ }
- 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
- # 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_name.
- #
- # ==== Returns
- # * <tt>string</tt>
- def controller_path
- @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
- end
- def method_added(name)
- super
- clear_action_methods!
- end
- 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.
- #
- # ==== Returns
- # * <tt>self</tt>
- def process(action, *args)
- @_action_name = action_name = action.to_s
- unless action_name = method_for_action(action_name)
- raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
- end
- @_response_body = nil
- process_action(action_name, *args)
- end
- # Delegates to the class' #controller_path
- def controller_path
- self.class.controller_path
- end
- def action_methods
- self.class.action_methods
- end
- # Returns true if a method for the action is available and
- # can be dispatched, false otherwise.
- #
- # Notice that <tt>action_methods.include?("foo")</tt> may return
- # false and <tt>available_action?("foo")</tt> returns true because
- # available action consider actions that are also available
- # through other means, for example, implicit render ones.
- def available_action?(action_name)
- method_for_action(action_name).present?
- end
- private
- # Returns true if the name can be considered an action because
- # it has a method defined in the controller.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- #
- # :api: private
- def action_method?(name)
- self.class.action_methods.include?(name)
- end
- # Call the action. Override this in a subclass to modify the
- # behavior around processing an action. This, and not #process,
- # is the intended way to override action dispatching.
- #
- # Notice that the first argument is the method to be dispatched
- # which is *not* necessarily the same as the action name.
- def process_action(method_name, *args)
- send_action(method_name, *args)
- end
- # Actually call the method associated with the action. Override
- # this method if you wish to change how action methods are called,
- # not to add additional behavior around it. For example, you would
- # override #send_action if you want to inject arguments into the
- # method.
- alias send_action send
- # If the action name was not found, but a method called "action_missing"
- # was found, #method_for_action will return "_handle_action_missing".
- # This method calls #action_missing with the current action name.
- def _handle_action_missing(*args)
- action_missing(@_action_name, *args)
- 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
- # method and return "_handle_action_missing" if one is found.
- #
- # Subclasses may override this method to add additional conditions
- # that should be considered an action. For instance, an HTTP controller
- # with a template matching the action name is considered to exist.
- #
- # If you override this method to handle additional cases, you may
- # also provide a method (like _handle_method_missing) to handle
- # the case.
- #
- # If none of these conditions are true, and method_for_action
- # returns nil, an 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.
- def method_for_action(action_name)
- if action_method?(action_name) then action_name
- elsif respond_to?(:action_missing, true) then "_handle_action_missing"
- end
- end
- end
- end
|