base.rb 9.4 KB
Newer Older
1
require 'erubis'
2
require 'set'
3
require 'active_support/configurable'
4
require 'active_support/descendants_tracker'
5
require 'active_support/core_ext/module/anonymous'
6

7
module AbstractController
V
Vijay Dev 已提交
8 9 10
  class Error < StandardError #:nodoc:
  end

11 12
  # Raised when a non-existing controller action is triggered.
  class ActionNotFound < StandardError
V
Vijay Dev 已提交
13
  end
J
Joshua Peek 已提交
14

J
Joost Baaij 已提交
15 16
  # <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
  # using it directly, and subclasses (like ActionController::Base) are
17
  # expected to provide their own +render+ method, since rendering means
18
  # different things depending on the context.
19 20
  class Base
    attr_internal :response_body
21
    attr_internal :action_name
22
    attr_internal :formats
23

24
    include ActiveSupport::Configurable
25
    extend ActiveSupport::DescendantsTracker
26

27
    undef_method :not_implemented
28
    class << self
J
Joshua Peek 已提交
29
      attr_reader :abstract
30
      alias_method :abstract?, :abstract
J
Joshua Peek 已提交
31

32 33
      # Define a controller as abstract. See internal_methods for more
      # details.
34 35 36
      def abstract!
        @abstract = true
      end
J
Joshua Peek 已提交
37

A
Aaron Patterson 已提交
38
      def inherited(klass) # :nodoc:
39
        # Define the abstract ivar on subclasses so that we don't get
A
Aaron Patterson 已提交
40 41 42 43 44 45 46
        # uninitialized ivar warnings
        unless klass.instance_variable_defined?(:@abstract)
          klass.instance_variable_set(:@abstract, false)
        end
        super
      end

47 48 49
      # 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
N
Neeraj Singh 已提交
50 51 52
      # 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)
53 54
      def internal_methods
        controller = self
A
Aaron Patterson 已提交
55

56 57 58
        controller = controller.superclass until controller.abstract?
        controller.public_instance_methods(true)
      end
J
Joshua Peek 已提交
59

60 61
      # The list of hidden actions. Defaults to an empty array.
      # This can be modified by other modules or subclasses
62 63 64
      # to specify particular actions as hidden.
      #
      # ==== Returns
65
      # * <tt>Array</tt> - An array of method names that should not be considered actions.
66 67 68
      def hidden_actions
        []
      end
J
Joshua Peek 已提交
69

70 71 72 73 74 75 76
      # 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
77
      # * <tt>Set</tt> - A set of all methods that should be considered actions.
78
      def action_methods
79
        @action_methods ||= begin
80
          # All public instance methods of this class, including ancestors
A
Aaron Patterson 已提交
81
          methods = (public_instance_methods(true) -
82
            # Except for public instance methods of Base and its ancestors
A
Aaron Patterson 已提交
83
            internal_methods +
84
            # Be sure to include shadowed public instance methods of this class
A
Aaron Patterson 已提交
85
            public_instance_methods(false)).uniq.map { |x| x.to_s } -
86
            # And always exclude explicitly hidden actions
A
Aaron Patterson 已提交
87
            hidden_actions.to_a
88 89

          # Clear out AS callback method pollution
90
          Set.new(methods.reject { |method| method =~ /_one_time_conditions/ })
91
        end
92
      end
93

94 95 96 97 98 99 100
      # 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

101 102
      # Returns the full controller name, underscored, without the ending Controller.
      # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
103
      # controller_path.
104 105
      #
      # ==== Returns
106
      # * <tt>String</tt>
107
      def controller_path
108
        @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
109
      end
110

111
      # Refresh the cached action_methods when a new action_method is added.
112 113 114 115
      def method_added(name)
        super
        clear_action_methods!
      end
116
    end
J
Joshua Peek 已提交
117

118
    abstract!
J
Joshua Peek 已提交
119

120
    # Calls the action going through the entire action dispatch stack.
121
    #
122 123
    # The actual method that is called is determined by calling
    # #method_for_action. If no method can handle the action, then an
124
    # AbstractController::ActionNotFound error is raised.
125 126
    #
    # ==== Returns
127
    # * <tt>self</tt>
128
    def process(action, *args)
129
      @_action_name = action.to_s
130

131
      unless action_name = _find_action_name(@_action_name)
132
        raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
133
      end
134

J
Joshua Peek 已提交
135 136
      @_response_body = nil

137
      process_action(action_name, *args)
138
    end
J
Joshua Peek 已提交
139

140 141 142 143 144
    # Delegates to the class' #controller_path
    def controller_path
      self.class.controller_path
    end

145
    # Delegates to the class' #action_methods
146 147
    def action_methods
      self.class.action_methods
148
    end
J
Joshua Peek 已提交
149

150 151
    # Returns true if a method for the action is available and
    # can be dispatched, false otherwise.
152
    #
153 154
    # Notice that <tt>action_methods.include?("foo")</tt> may return
    # false and <tt>available_action?("foo")</tt> returns true because
V
Vijay Dev 已提交
155
    # this method considers actions that are also available
156
    # through other means, for example, implicit render ones.
157 158 159 160 161 162
    #
    # ==== Parameters
    # * <tt>action_name</tt> - The name of an action to be tested
    #
    # ==== Returns
    # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
163
    def available_action?(action_name)
164
      _find_action_name(action_name).present?
165
    end
166

167
    private
168

169 170 171 172 173 174 175 176 177 178 179 180 181 182
      # 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

183 184 185
      # 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.
186 187 188
      #
      # Notice that the first argument is the method to be dispatched
      # which is *not* necessarily the same as the action name.
189 190 191
      def process_action(method_name, *args)
        send_action(method_name, *args)
      end
192

193 194 195 196 197 198 199 200 201 202
      # 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.
203 204
      def _handle_action_missing(*args)
        action_missing(@_action_name, *args)
205 206
      end

207 208 209 210 211 212 213 214 215 216 217 218
      # 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
219 220
      # * false           - No valid method name could be found.
      # Raise AbstractController::ActionNotFound.
221 222 223 224
      def _find_action_name(action_name)
        _valid_action_name?(action_name) && method_for_action(action_name)
      end

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
      # 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
240
      # returns nil, an AbstractController::ActionNotFound exception will be raised.
241 242
      #
      # ==== Parameters
243
      # * <tt>action_name</tt> - An action name to find a method name for
244 245
      #
      # ==== Returns
J
Joost Baaij 已提交
246
      # * <tt>string</tt> - The name of the method that handles the action
247
      # * <tt>nil</tt>    - No method name could be found.
248
      def method_for_action(action_name)
249 250 251 252
        if action_method?(action_name)
          action_name
        elsif respond_to?(:action_missing, true)
          "_handle_action_missing"
253
        end
254
      end
255 256 257

      # Checks if the action name is valid and returns false otherwise.
      def _valid_action_name?(action_name)
258
        action_name !~ Regexp.new(File::SEPARATOR)
259
      end
260
  end
J
Joshua Peek 已提交
261
end