aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/metal.rb
blob: 8433be2320a4dd7e47d7fb61b72b0ca003a74799 (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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require 'active_support/core_ext/class/inheritable_attributes'

module ActionController
  # ActionController::Metal provides a way to get a valid Rack application from a controller.
  #
  # In AbstractController, dispatching is triggered directly by calling #process on a new controller.
  # ActionController::Metal provides an #action method that returns a valid Rack application for a
  # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router,
  # can dispatch directly to the action returned by FooController.action(:index).
  class Metal < AbstractController::Base
    abstract!

    # :api: public
    attr_internal :params, :env

    # Returns the last part of the controller's name, underscored, without the ending
    # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for
    # controller_name
    #
    # ==== Returns
    # String
    def self.controller_name
      @controller_name ||= controller_path.split("/").last
    end

    # Delegates to the class' #controller_name
    def controller_name
      self.class.controller_name
    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 self.controller_path
      @controller_path ||= name && name.sub(/Controller$/, '').underscore
    end

    # Delegates to the class' #controller_path
    def controller_path
      self.class.controller_path
    end

    # The details below can be overridden to support a specific
    # Request and Response object. The default ActionController::Base
    # implementation includes RackDelegation, which makes a request
    # and response object available. You might wish to control the
    # environment and response manually for performance reasons.

    attr_internal :status, :headers, :content_type, :response

    def initialize(*)
      @_headers = {}
      super
    end

    # Basic implementations for content_type=, location=, and headers are
    # provided to reduce the dependency on the RackDelegation module
    # in Renderer and Redirector.

    def content_type=(type)
      headers["Content-Type"] = type.to_s
    end

    def location=(url)
      headers["Location"] = url
    end

    # :api: private
    def dispatch(name, env)
      @_env = env
      process(name)
      to_a
    end

    # :api: private
    def to_a
      response ? response.to_a : [status, headers, response_body]
    end

    class ActionEndpoint
      @@endpoints = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }

      def self.for(controller, action, stack)
        @@endpoints[controller][action][stack] ||= begin
          endpoint = new(controller, action)
          stack.build(endpoint)
        end
      end

      def initialize(controller, action)
        @controller, @action = controller, action
      end

      def call(env)
        @controller.new.dispatch(@action, env)
      end
    end

    extlib_inheritable_accessor(:middleware_stack) { ActionDispatch::MiddlewareStack.new }

    def self.use(*args)
      middleware_stack.use(*args)
    end

    def self.middleware
      middleware_stack
    end

    def self.call(env)
      action(env['action_dispatch.request.path_parameters'][:action]).call(env)
    end

    # Return a rack endpoint for the given action. Memoize the endpoint, so
    # multiple calls into MyController.action will return the same object
    # for the same action.
    #
    # ==== Parameters
    # action<#to_s>:: An action name
    #
    # ==== Returns
    # Proc:: A rack application
    def self.action(name)
      ActionEndpoint.for(self, name, middleware_stack)
    end
  end
end