base.rb 5.8 KB
Newer Older
1
module AbstractController
2 3
  class Error < StandardError; end
  class ActionNotFound < StandardError; end
J
Joshua Peek 已提交
4

5 6
  class Base
    attr_internal :response_body
7
    attr_internal :action_name
8 9

    class << self
J
Joshua Peek 已提交
10
      attr_reader :abstract
11
      alias_method :abstract?, :abstract
J
Joshua Peek 已提交
12

13 14
      # Define a controller as abstract. See internal_methods for more
      # details.
15 16 17
      def abstract!
        @abstract = true
      end
J
Joshua Peek 已提交
18

19
      def inherited(klass)
20
        ::AbstractController::Base.descendants << klass.to_s
21 22 23
        super
      end

24 25 26 27
      # A list of all descendents of AbstractController::Base. This is
      # useful for initializers which need to add behavior to all controllers.
      def descendants
        @descendants ||= []
28
      end
J
Joshua Peek 已提交
29

30 31 32 33 34
      # 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
35
      # (ActionController::Metal and ActionController::Base are defined
36
      # as abstract)
37 38 39 40 41
      def internal_methods
        controller = self
        controller = controller.superclass until controller.abstract?
        controller.public_instance_methods(true)
      end
J
Joshua Peek 已提交
42

43 44 45 46 47 48 49
      # 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.
50 51 52
      def hidden_actions
        []
      end
J
Joshua Peek 已提交
53

54 55 56 57 58 59 60 61 62
      # 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.
63 64 65
      def action_methods
        @action_methods ||=
          # All public instance methods of this class, including ancestors
66
          public_instance_methods(true).map { |m| m.to_s }.to_set -
67
          # Except for public instance methods of Base and its ancestors
68
          internal_methods.map { |m| m.to_s } +
69
          # Be sure to include shadowed public instance methods of this class
70
          public_instance_methods(false).map { |m| m.to_s } -
71 72 73
          # And always exclude explicitly hidden actions
          hidden_actions
      end
74
    end
J
Joshua Peek 已提交
75

76
    abstract!
J
Joshua Peek 已提交
77

78
    # Calls the action going through the entire action dispatch stack.
79
    #
80 81 82 83 84 85
    # 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
86 87
    def process(action)
      @_action_name = action_name = action.to_s
88

89
      unless action_name = method_for_action(action_name)
90
        raise ActionNotFound, "The action '#{action}' could not be found"
91
      end
92 93

      process_action(action_name)
94
    end
J
Joshua Peek 已提交
95

96
  private
97 98 99 100 101 102 103 104 105 106
    # 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)
107
      self.class.action_methods.include?(name)
108
    end
J
Joshua Peek 已提交
109

110 111 112
    # 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.
113
    def process_action(method_name)
114
      send_action(method_name)
115
    end
116

117 118 119 120 121
    # 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.
122 123
    alias send_action send

124 125 126
    # 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.
127 128 129 130
    def _handle_action_missing
      action_missing(@_action_name)
    end

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    # 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.
154 155 156 157
    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
Y
Yehuda Katz 已提交
158
    end
159
  end
J
Joshua Peek 已提交
160
end