aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/abstract/base.rb
blob: 306f3d2ccbb8ad6e3da2edfe899610f05c45d9d7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
require 'active_support/core_ext/module/attr_internal'

module AbstractController
  class Error < StandardError; end

  class DoubleRenderError < Error
    DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."

    def initialize(message = nil)
      super(message || DEFAULT_MESSAGE)
    end
  end

  class Base
    attr_internal :response_body
    attr_internal :action_name

    class << self
      attr_reader :abstract

      def abstract!
        @abstract = true
      end

      alias_method :abstract?, :abstract

      def inherited(klass)
        ::AbstractController::Base.subclasses << klass.to_s
        super
      end

      def subclasses
        @subclasses ||= []
      end

      def internal_methods
        controller = self
        controller = controller.superclass until controller.abstract?
        controller.public_instance_methods(true)
      end

      def process(action)
        new.process(action.to_s)
      end

      def hidden_actions
        []
      end

      def action_methods
        @action_methods ||=
          # All public instance methods of this class, including ancestors
          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
      end
    end

    abstract!

    def process(action)
      @_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

      process_action(action_name)
      self
    end

  private
    def action_methods
      self.class.action_methods
    end

    def action_method?(action)
      action_methods.include?(action)
    end

    # It is possible for respond_to?(action_name) to be false and
    # respond_to?(:action_missing) to be false if respond_to_action?
    # is overridden in a subclass. For instance, ActionController::Base
    # overrides it to include the case where a template matching the
    # action_name is found.
    def process_action(method_name)
      send_action(method_name)
    end

    alias send_action send

    def _handle_action_missing
      action_missing(@_action_name)
    end

    # Override this to change the conditions that will raise an
    # ActionNotFound error. If you accept a difference case,
    # you must handle it by also overriding process_action and
    # handling the case.
    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