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 # 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. class Base attr_internal :response_body attr_internal :action_name include ActiveSupport::Configurable extend ActiveSupport::DescendantsTracker 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 we # are removing those methods on classes declared as abstract # (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 # * array - 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 # * array - 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 # * string 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 # * self 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 private # 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. # # ==== Parameters # * name - The name of an action to be tested # # ==== Returns # * TrueClass, FalseClass 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. 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 action_missing(@_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 ActionNotFound exception will be raised. # # ==== Parameters # * action_name - An action name to find a method name for # # ==== Returns # * string - The name of the method that handles the action # * nil - 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