mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
199 lines
7 KiB
Ruby
199 lines
7 KiB
Ruby
require 'active_support/ordered_options'
|
|
|
|
module AbstractController
|
|
class Error < StandardError; end
|
|
class ActionNotFound < StandardError; end
|
|
|
|
class Base
|
|
attr_internal :response_body
|
|
attr_internal :action_name
|
|
|
|
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)
|
|
::AbstractController::Base.descendants << klass.to_s
|
|
super
|
|
end
|
|
|
|
# A list of all descendents of AbstractController::Base. This is
|
|
# useful for initializers which need to add behavior to all controllers.
|
|
def descendants
|
|
@descendants ||= []
|
|
end
|
|
|
|
def config
|
|
@config ||= ActiveSupport::InheritableOptions.new(superclass < Base ? superclass.config : {})
|
|
end
|
|
|
|
def configure
|
|
yield config
|
|
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[String]:: 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[String]:: 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).map { |m| m.to_s }.to_set -
|
|
# Except for public instance methods of Base and its ancestors
|
|
internal_methods.map { |m| m.to_s } +
|
|
# Be sure to include shadowed public instance methods of this class
|
|
public_instance_methods(false).map { |m| m.to_s } -
|
|
# And always exclude explicitly hidden actions
|
|
hidden_actions
|
|
|
|
# Clear out AS callback method pollution
|
|
methods.reject { |method| method =~ /_one_time_conditions/ }
|
|
end
|
|
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
|
|
end
|
|
|
|
abstract!
|
|
|
|
def config
|
|
@config ||= ActiveSupport::InheritableOptions.new(self.class.config)
|
|
end
|
|
|
|
# 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"
|
|
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<String>:: 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<String>:: 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
|