aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/abstract_controller/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/abstract_controller/base.rb')
-rw-r--r--actionpack/lib/abstract_controller/base.rb269
1 files changed, 269 insertions, 0 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
new file mode 100644
index 0000000000..4026dab2ce
--- /dev/null
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -0,0 +1,269 @@
+require 'erubis'
+require 'set'
+require 'active_support/configurable'
+require 'active_support/descendants_tracker'
+require 'active_support/core_ext/module/anonymous'
+
+module AbstractController
+ class Error < StandardError #:nodoc:
+ end
+
+ # 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
+ # 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
+
+ def inherited(klass) # :nodoc:
+ # 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)
+ end
+ super
+ 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. 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>Set</tt> - A set 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
+ Set.new(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_path.
+ #
+ # ==== Returns
+ # * <tt>String</tt>
+ def controller_path
+ @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
+ end
+
+ # Refresh the cached action_methods when a new action_method is added.
+ 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
+ # AbstractController::ActionNotFound error is raised.
+ #
+ # ==== Returns
+ # * <tt>self</tt>
+ def process(action, *args)
+ @_action_name = action.to_s
+
+ unless action_name = _find_action_name(@_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
+
+ # Delegates to the class' #action_methods
+ 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
+ # this method considers actions that are also available
+ # through other means, for example, implicit render ones.
+ #
+ # ==== Parameters
+ # * <tt>action_name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
+ def available_action?(action_name)
+ _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
+
+ # 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.
+ #
+ # 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
+ # 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 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.
+ def method_for_action(action_name)
+ if action_method?(action_name)
+ action_name
+ elsif respond_to?(:action_missing, true)
+ "_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