base.rb 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. require 'erubis'
  2. require 'active_support/configurable'
  3. require 'active_support/descendants_tracker'
  4. require 'active_support/core_ext/module/anonymous'
  5. module AbstractController
  6. class Error < StandardError; end
  7. class ActionNotFound < StandardError; end
  8. # <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
  9. # using it directly, and subclasses (like ActionController::Base) are
  10. # expected to provide their own +render+ method, since rendering means
  11. # different things depending on the context.
  12. class Base
  13. attr_internal :response_body
  14. attr_internal :action_name
  15. attr_internal :formats
  16. include ActiveSupport::Configurable
  17. extend ActiveSupport::DescendantsTracker
  18. undef_method :not_implemented
  19. class << self
  20. attr_reader :abstract
  21. alias_method :abstract?, :abstract
  22. # Define a controller as abstract. See internal_methods for more
  23. # details.
  24. def abstract!
  25. @abstract = true
  26. end
  27. # A list of all internal methods for a controller. This finds the first
  28. # abstract superclass of a controller, and gets a list of all public
  29. # instance methods on that abstract class. Public instance methods of
  30. # a controller would normally be considered action methods, so methods
  31. # declared on abstract classes are being removed.
  32. # (ActionController::Metal and ActionController::Base are defined as abstract)
  33. def internal_methods
  34. controller = self
  35. controller = controller.superclass until controller.abstract?
  36. controller.public_instance_methods(true)
  37. end
  38. # The list of hidden actions to an empty array. Defaults to an
  39. # empty array. This can be modified by other modules or subclasses
  40. # to specify particular actions as hidden.
  41. #
  42. # ==== Returns
  43. # * <tt>array</tt> - An array of method names that should not be considered actions.
  44. def hidden_actions
  45. []
  46. end
  47. # A list of method names that should be considered actions. This
  48. # includes all public instance methods on a controller, less
  49. # any internal methods (see #internal_methods), adding back in
  50. # any methods that are internal, but still exist on the class
  51. # itself. Finally, #hidden_actions are removed.
  52. #
  53. # ==== Returns
  54. # * <tt>array</tt> - A list of all methods that should be considered actions.
  55. def action_methods
  56. @action_methods ||= begin
  57. # All public instance methods of this class, including ancestors
  58. methods = (public_instance_methods(true) -
  59. # Except for public instance methods of Base and its ancestors
  60. internal_methods +
  61. # Be sure to include shadowed public instance methods of this class
  62. public_instance_methods(false)).uniq.map { |x| x.to_s } -
  63. # And always exclude explicitly hidden actions
  64. hidden_actions.to_a
  65. # Clear out AS callback method pollution
  66. methods.reject { |method| method =~ /_one_time_conditions/ }
  67. end
  68. end
  69. # action_methods are cached and there is sometimes need to refresh
  70. # them. clear_action_methods! allows you to do that, so next time
  71. # you run action_methods, they will be recalculated
  72. def clear_action_methods!
  73. @action_methods = nil
  74. end
  75. # Returns the full controller name, underscored, without the ending Controller.
  76. # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
  77. # controller_name.
  78. #
  79. # ==== Returns
  80. # * <tt>string</tt>
  81. def controller_path
  82. @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
  83. end
  84. def method_added(name)
  85. super
  86. clear_action_methods!
  87. end
  88. end
  89. abstract!
  90. # Calls the action going through the entire action dispatch stack.
  91. #
  92. # The actual method that is called is determined by calling
  93. # #method_for_action. If no method can handle the action, then an
  94. # ActionNotFound error is raised.
  95. #
  96. # ==== Returns
  97. # * <tt>self</tt>
  98. def process(action, *args)
  99. @_action_name = action_name = action.to_s
  100. unless action_name = method_for_action(action_name)
  101. raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
  102. end
  103. @_response_body = nil
  104. process_action(action_name, *args)
  105. end
  106. # Delegates to the class' #controller_path
  107. def controller_path
  108. self.class.controller_path
  109. end
  110. def action_methods
  111. self.class.action_methods
  112. end
  113. # Returns true if a method for the action is available and
  114. # can be dispatched, false otherwise.
  115. #
  116. # Notice that <tt>action_methods.include?("foo")</tt> may return
  117. # false and <tt>available_action?("foo")</tt> returns true because
  118. # available action consider actions that are also available
  119. # through other means, for example, implicit render ones.
  120. def available_action?(action_name)
  121. method_for_action(action_name).present?
  122. end
  123. private
  124. # Returns true if the name can be considered an action because
  125. # it has a method defined in the controller.
  126. #
  127. # ==== Parameters
  128. # * <tt>name</tt> - The name of an action to be tested
  129. #
  130. # ==== Returns
  131. # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
  132. #
  133. # :api: private
  134. def action_method?(name)
  135. self.class.action_methods.include?(name)
  136. end
  137. # Call the action. Override this in a subclass to modify the
  138. # behavior around processing an action. This, and not #process,
  139. # is the intended way to override action dispatching.
  140. #
  141. # Notice that the first argument is the method to be dispatched
  142. # which is *not* necessarily the same as the action name.
  143. def process_action(method_name, *args)
  144. send_action(method_name, *args)
  145. end
  146. # Actually call the method associated with the action. Override
  147. # this method if you wish to change how action methods are called,
  148. # not to add additional behavior around it. For example, you would
  149. # override #send_action if you want to inject arguments into the
  150. # method.
  151. alias send_action send
  152. # If the action name was not found, but a method called "action_missing"
  153. # was found, #method_for_action will return "_handle_action_missing".
  154. # This method calls #action_missing with the current action name.
  155. def _handle_action_missing(*args)
  156. action_missing(@_action_name, *args)
  157. end
  158. # Takes an action name and returns the name of the method that will
  159. # handle the action. In normal cases, this method returns the same
  160. # name as it receives. By default, if #method_for_action receives
  161. # a name that is not an action, it will look for an #action_missing
  162. # method and return "_handle_action_missing" if one is found.
  163. #
  164. # Subclasses may override this method to add additional conditions
  165. # that should be considered an action. For instance, an HTTP controller
  166. # with a template matching the action name is considered to exist.
  167. #
  168. # If you override this method to handle additional cases, you may
  169. # also provide a method (like _handle_method_missing) to handle
  170. # the case.
  171. #
  172. # If none of these conditions are true, and method_for_action
  173. # returns nil, an ActionNotFound exception will be raised.
  174. #
  175. # ==== Parameters
  176. # * <tt>action_name</tt> - An action name to find a method name for
  177. #
  178. # ==== Returns
  179. # * <tt>string</tt> - The name of the method that handles the action
  180. # * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.
  181. def method_for_action(action_name)
  182. if action_method?(action_name) then action_name
  183. elsif respond_to?(:action_missing, true) then "_handle_action_missing"
  184. end
  185. end
  186. end
  187. end