See More

# Learn-Rails-by-Reading-Source-Code [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) ## Table of Contents * [Part 0: Before reading Rails 5 source code](#part-0-before-reading-rails-5-source-code) * [What will you learn from this tutorial?](#what-will-you-learn-from-this-tutorial) * [Part 1: Your app: an instance of YourProject::Application](#part-1-your-app-an-instance-of-yourprojectapplication) * [Part 2: config](#part-2-config) * [Part 3: Every request and response](#part-3-every-request-and-response) * [Puma](#puma) * [Rack apps](#rack-apps) * [The core app: ActionDispatch::Routing::RouteSet instance](#the-core-app-actiondispatchroutingrouteset-instance) * [Render view](#render-view) * [How can instance variables defined in Controller be accessed in view file?](#how-can-instance-variables-defined-in-controller-be-accessed-in-view-file) * [Part 4: What does `$ rails server` do?](#part-4-what-does--rails-server-do) * [Thor](#thor) * [Rails::Server#start](#railsserverstart) * [Starting Puma](#starting-puma) * [Conclusion](#conclusion) * [Exiting Puma](#exiting-puma) * [Process and Thread](#process-and-thread) * [Send `SIGTERM` to Puma](#send-sigterm-to-puma) ## Part 0: Before reading Rails 5 source code 1) I suggest you to learn Rack [http://rack.github.io/](http://rack.github.io/) first. In Rack, an object with `call` method is a Rack app. So what is the object with `call` method in Rails? I will answer this question in Part 1. 2) You need a good IDE which can help for debugging. I use [RubyMine](https://www.jetbrains.com/). ### What will you learn from this tutorial? * How does Rails start your application? * How does Rails process every request? * How does Rails combine ActionController, ActionView and Routes together? * How does Puma, Rack, Rails work together? * What's Puma's multiple threads? I should start with the command `$ rails server`, but I put this to Part 4. Because it's a little bit complex. ## Part 1: Your app: an instance of YourProject::Application Assume your Rails app's class name is `YourProject::Application` (defined in `./config/application.rb`). First, I will give you a piece of important code. ```ruby # ./gems/railties-5.2.2/lib/rails/commands/server/server_command.rb module Rails module Command class ServerCommand < Base def perform # ... Rails::Server.new(server_options).tap do |server| # APP_PATH is '/path/to/your_project/config/application'. # require APP_PATH will create the 'Rails.application' object. # Actually, 'Rails.application' is an instance of `YourProject::Application`. # Rack server will start 'Rails.application'. require APP_PATH Dir.chdir(Rails.application.root) server.start end end end end class Server < ::Rack::Server def start #... # 'wrapped_app' will get an well prepared app from `./config.ru` file. # 'wrapped_app' will return an instance of `YourProject::Application`. # But the instance of `YourProject::Application` returned is not created in 'wrapped_app'. # It has been created when `require APP_PATH` in previous code: # in Rails::Command::ServerCommand#perform wrapped_app super # Will invoke ::Rack::Server#start. end end end ``` A Rack server need to start with an `App` object. The `App` object should have a `call` method. `config.ru` is the conventional entry file for Rack app. So let's look at it. ```ruby # ./config.ru require_relative 'config/environment' run Rails.application # It seems that this is the app. ``` Let's test it by `Rails.application.respond_to?(:call)`, it returns `true`. Let's step into `Rails.application`. ```ruby # ./gems/railties-5.2.2/lib/rails.rb module Rails class << self @application = @app_class = nil attr_accessor :app_class # Oh, 'application' is a class method for module 'Rails'. It is not an object. # But it returns an object which is an instance of 'app_class'. # So it is important for us to know what class 'app_class' is. def application @application ||= (app_class.instance if app_class) end end end ``` Because `Rails.application.respond_to?(:call)` returns `true`, `app_class.instance` has a `call` method. When was `app_class` set? ```ruby module Rails class Application < Engine class << self def inherited(base) # This is a hooked method. Rails.app_class = base # This line set the 'app_class'. end end end end ``` `Rails::Application` is inherited by `YourProject`, ```ruby # ./config/application.rb module YourProject # The hooked method `inherited` will be invoked here. class Application < Rails::Application end end ``` So `YourProject::Application` is the `Rails.app_class` here. You may have a question: When does Rails execute the code in `./config/application.rb`? To answer this question, we need to look back to `config.ru`. ```ruby # ./config.ru require_relative 'config/environment' # Let's step into this line. run Rails.application # It seems that this is the app. ``` ```ruby # ./config/environment.rb # Load the Rails application. require_relative 'application' # Let's step into this line. # Initialize the Rails application. Rails.application.initialize! ``` ```ruby # ./config/application.rb require_relative 'boot' require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module YourProject # The hooked method `inherited` will be invoked here. class Application < Rails::Application config.load_defaults 5.2 config.i18n.default_locale = :zh end end ``` Because `YourProject::Application` is `Rails.app_class`, `app_class.instance` is `YourProject::Application.instance`. But where is the `call` method? `call` method should be a method of `YourProject::Application.instance`. The `call` method processes every request. Here it is. ```ruby # ./gems/railties/lib/rails/engine.rb module Rails class Engine < Railtie def call(env) # This method will process every request. It is invoked by Rack. So it is very important. req = build_request env app.call req.env # We will discuss the 'app' object later. end end end # ./gems/railties/lib/rails/application.rb module Rails class Application < Engine end end # ./config/application.rb module YourProject class Application < Rails::Application end end ``` Ancestor's chain is `YourProject::Application < Rails::Application < Rails::Engine < Rails::Railtie`. So `YourProject::Application.new.respond_to?(:call)` returns `true`. But what does `app_class.instance` really do? `instance` is just a method name. What we really expects is something like `app_class.new`. Let's look at the definition of `instance`. ```ruby # ./gems/railties/lib/rails/application.rb module Rails class Application < Engine def instance super.run_load_hooks! # This line confused me at the beginning. end end end ``` After a deep research, I realized that this code is equal to: ```ruby def instance return_value = super # Keyword 'super' means call the ancestor's same name method: 'instance'. return_value.run_load_hooks! end ``` ```ruby # ./gems/railties/lib/rails/railtie.rb module Rails class Railtie def instance # 'Rails::Railtie' is the top ancestor class. # Now 'app_class.instance' is 'YourProject::Application.new'. @instance ||= new end end end ``` ```ruby module Rails def application @application ||= (app_class.instance if app_class) end end ``` So `YourProject::Application.new` is `Rails.application`. Rack server will start `Rails.application` in the end. `Rails.application` is an important object in Rails. And you'll only have one `Rails.application` in one Puma process. Multiple threads in a Puma process shares the `Rails.application`. ## Part 2: config The first time we see the `config` is in `./config/application.rb`. ```ruby # ./config/application.rb #... module YourProject class Application < Rails::Application # Actually, `config` is a method of `YourProject::Application`. # It is defined in it's grandfather's father: `Rails::Railtie` config.load_defaults 5.2 # Let's step into this line to see what is config. config.i18n.default_locale = :zh end end ``` ```ruby module Rails class Railtie class << self # Method `:config` is defined here. # Actually, method `:config` is delegated to another object `:instance`. delegate :config, to: :instance # Call `YourProject::Application.config` will actually call `YourProject::Application.instance.config` def instance # return an instance of `YourProject::Application`. # Call `YourProject::Application.config` will actually call `YourProject::Application.new.config` @instance ||= new end end end class Engine < Railtie end class Application < Engine class << self def instance # 'super' will call `:instance` method in `Railtie`, # which will return an instance of `YourProject::Application`. return_value = super return_value.run_load_hooks! end end def run_load_hooks! return self if @ran_load_hooks @ran_load_hooks = true # ... self # `self` is an instance of `YourProject::Application`, and `self` is `Rails.application`. end # This is the method `config`. def config # It is an instance of class `Rails::Application::Configuration`. # Please notice that `Rails::Application` is superclass of `YourProject::Application` (self's class). @config ||= Application::Configuration.new(self.class.find_root(self.class.called_from)) end end end ``` In the end, `YourProject::Application.config === Rails.application.config` returns `true`. Invoke Class's `config` method become invoke the class's instance's `config` method. ```ruby module Rails class << self def configuration application.config end end end ``` So `Rails.configuration === Rails.application.config` returns `true`. FYI: ```ruby module Rails class Application class Configuration < ::Rails::Engine::Configuration end end class Engine class Configuration < ::Rails::Railtie::Configuration attr_accessor :middleware def initialize(root = nil) super() #... @middleware = Rails::Configuration::MiddlewareStackProxy.new end end end class Railtie class Configuration end end end ``` ## Part 3: Every request and response Imagine we have this route for the home page. ```ruby # ./config/routes.rb Rails.application.routes.draw do root 'home#index' # HomeController#index end ``` ### Puma When a request is made from client, Puma will process the request in `Puma::Server#process_client`. If you want to know how Puma enter the method `Puma::Server#process_client`, please read part 4 or just search 'process_client' in this document. ```ruby # ./gems/puma-3.12.0/lib/puma/server.rb require 'socket' module Puma # The HTTP Server itself. Serves out a single Rack app. # # This class is used by the `Puma::Single` and `Puma::Cluster` classes # to generate one or more `Puma::Server` instances capable of handling requests. # Each Puma process will contain one `Puma::Server` instacne. # # The `Puma::Server` instance pulls requests from the socket, adds them to a # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`. # # Each `Puma::Server` will have one reactor and one thread pool. class Server def initialize(app, events=Events.stdio, options={}) # app: #<:configuration::configmiddleware:0x00007fcf1612c338> # @config = #<:configuration:0x00007fcf169a6c98> # > @app = app #... end # Given a connection on +client+, handle the incoming requests. # # This method support HTTP Keep-Alive so it may, depending on if the client # indicates that it supports keep alive, wait for another request before # returning. # def process_client(client, buffer) begin # ... while true # Let's step into this line. case handle_request(client, buffer) # Will return true in this example. when true return unless @queue_requests buffer.reset ThreadPool.clean_thread_locals if clean_thread_locals unless client.reset(@status == :run) close_socket = false client.set_timeout @persistent_timeout @reactor.add client return end end end # ... ensure buffer.reset client.close if close_socket #... end end # Given the request +env+ from +client+ and a partial request body # in +body+, finish reading the body if there is one and invoke # the Rack app. Then construct the response and write it back to # +client+ # def handle_request(req, lines) env = req.env # ... # app: #<:configuration::configmiddleware:0x00007fcf1612c338> # @config = #<:configuration:0x00007fcf169a6c98> # > status, headers, res_body = @app.call(env) # Let's step into this line. # ... return keep_alive end end end ``` ```ruby # ./gems/puma-3.12.0/lib/puma/configuration.rb module Puma class Configuration class ConfigMiddleware def initialize(config, app) @config = config @app = app end def call(env) env[Const::PUMA_CONFIG] = @config # @app: #<:application:0x00007fb4b1b4bcf8> @app.call(env) end end end end ``` ### Rack apps As we see when Ruby enter `Puma::Configuration::ConfigMiddleware#call`, the `@app` is `YourProject::Application` instance. It is just the `Rails.application`. Rack need a `call` method to process request. Rails defined this `call` method in `Rails::Engine#call`, so that `YourProject::Application` instance will have a `call` method. ```ruby # ./gems/railties/lib/rails/engine.rb module Rails class Engine < Railtie def call(env) # This method will process every request. It is invoked by Rack. req = build_request env app.call req.env # The 'app' method is blow. end def app # FYI, # caller: [ # "../gems/railties-5.2.2/lib/rails/application/finisher.rb:47:in `block in '", # "../gems/railties-5.2.2/lib/rails/initializable.rb:32:in `instance_exec'", # "../gems/railties-5.2.2/lib/rails/initializable.rb:32:in `run'", # "../gems/railties-5.2.2/lib/rails/initializable.rb:63:in `block in run_initializers'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:228:in `block in tsort_each'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:431:in `each_strongly_connected_component_from'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:349:in `block in each_strongly_connected_component'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:347:in `each'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:347:in `call'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:347:in `each_strongly_connected_component'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:226:in `tsort_each'", # "../ruby-2.6.0/lib/ruby/2.6.0/tsort.rb:205:in `tsort_each'", # "../gems/railties-5.2.2/lib/rails/initializable.rb:61:in `run_initializers'", # "../gems/railties-5.2.2/lib/rails/application.rb:361:in `initialize!'", # "/Users/lanezhang/projects/mine/free-erp/config/environment.rb:5:in `'", # "config.ru:2:in `require_relative'", "config.ru:2:in `block in

'", # "../gems/rack-2.0.6/lib/rack/builder.rb:55:in `instance_eval'", # "../gems/rack-2.0.6/lib/rack/builder.rb:55:in `initialize'", # "config.ru:in `new'", "config.ru:in `
'", # "../gems/rack-2.0.6/lib/rack/builder.rb:49:in `eval'", # "../gems/rack-2.0.6/lib/rack/builder.rb:49:in `new_from_string'", # "../gems/rack-2.0.6/lib/rack/builder.rb:40:in `parse_file'", # "../gems/rack-2.0.6/lib/rack/server.rb:320:in `build_app_and_options_from_config'", # "../gems/rack-2.0.6/lib/rack/server.rb:219:in `app'", # "../gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:27:in `app'", # "../gems/rack-2.0.6/lib/rack/server.rb:357:in `wrapped_app'", # "../gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:92:in `log_to_stdout'", # "../gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:54:in `start'", # "../gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:149:in `block in perform'", # "../gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:144:in `tap'", # "../gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:144:in `perform'", # "../gems/thor-0.20.3/lib/thor/command.rb:27:in `run'", # "../gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'", # "../gems/thor-0.20.3/lib/thor.rb:391:in `dispatch'", # "../gems/railties-5.2.2/lib/rails/command/base.rb:65:in `perform'", # "../gems/railties-5.2.2/lib/rails/command.rb:46:in `invoke'", # "../gems/railties-5.2.2/lib/rails/commands.rb:18:in `'", # "../path/to/your_project/bin/rails:5:in `require'", # "../path/to/your_project/bin/rails:5:in `
'" # ] puts "caller: #{caller.inspect}" # You may want to know when is the @app first time initialized. # It is initialized when 'config.ru' is load by Rack server. # Please search `Rack::Server#build_app_and_options_from_config` in this document for more information. # When `Rails.application.initialize!` (in ./config/environment.rb) executed, @app is initialized. @app || @app_build_lock.synchronize { # '@app_build_lock = Mutex.new', so multiple threads share one '@app'. @app ||= begin # In the end, config.middleware will be an instance of ActionDispatch::MiddlewareStack with preset instance variable @middlewares (which is an Array). stack = default_middleware_stack # Let's step into this line # 'middleware' is a 'middleware_stack'! config.middleware = build_middleware.merge_into(stack) # FYI, this line is the last line and the result of this line is the return value for @app. config.middleware.build(endpoint) # look at this endpoint below. We will enter method `build` later. end } # @app: #<:sendfile:0x00007ff14d905f60 ...> # > # ... # > # # > # > @app end # Defaults to an ActionDispatch::Routing::RouteSet instance. def endpoint ActionDispatch::Routing::RouteSet.new_with_config(config) end end end ``` ```ruby # ./gems/railties/lib/rails/application... module Rails class Application < Engine def default_middleware_stack default_stack = DefaultMiddlewareStack.new(self, config, paths) default_stack.build_stack # Let's step into this line. end class DefaultMiddlewareStack attr_reader :config, :paths, :app def initialize(app, config, paths) @app = app @config = config @paths = paths end def build_stack ActionDispatch::MiddlewareStack.new do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options end middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.public_file_server.enabled headers = config.public_file_server.headers || {} middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end if config.allow_concurrency == false # User has explicitly opted out of concurrent request # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock end middleware.use ::ActionDispatch::Executor, app.executor middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies unless config.api_only if !config.api_only && config.session_store if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) config.session_options[:secure] = true end middleware.use config.session_store, config.session_options middleware.use ::ActionDispatch::Flash end unless config.api_only middleware.use ::ActionDispatch::ContentSecurityPolicy::Middleware end middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" middleware.use ::Rack::TempfileReaper unless config.api_only end end end end end ``` ```ruby # ./gems/actionpack-5.2.2/lib/action_dispatch/middleware/stack.rb module ActionDispatch class MiddlewareStack def use(klass, *args, &block) middlewares.push(build_middleware(klass, args, block)) end def build_middleware(klass, args, block) Middleware.new(klass, args, block) end def build(app = Proc.new) # See Enumerable#inject for more information. return_val = middlewares.freeze.reverse.inject(app) do |a, middleware| # a: app, and will be changed when iterating # middleware: #<:middlewarestack::middleware:0x00007f8a4fada6e8>, 'middleware' will be switched to another instance of ActionDispatch::MiddlewareStack::Middleware when iterating middleware.build(a) # Let's step into this line. end return_val end class Middleware def initialize(klass, args, block) @klass = klass @args = args @block = block end def build(app) # klass is Rack middleware like : Rack::TempfileReaper, Rack::ETag, Rack::ConditionalGet or Rack::Head, etc. # It's typical Rack app to use these middlewares. # See https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib for more information. klass.new(app, *args, &block) end end end end ``` ### The core app: ActionDispatch::Routing::RouteSet instance ```ruby # Paste again FYI. # @app: #<:sendfile:0x00007ff14d905f60 ...> # > # ... # > # # > # > ``` As we see in the Rack middleware stack, the last @app is `@app=#<:routing::routeset:0x00007fa1e594cbe8>` ```ruby # ./gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb module ActionDispatch module Routing class RouteSet def initialize(config = DEFAULT_CONFIG) @set = Journey::Routes.new @router = Journey::Router.new(@set) end def call(env) req = make_request(env) # return ActionDispatch::Request.new(env) req.path_info = Journey::Router::Utils.normalize_path(req.path_info) @router.serve(req) # Let's step into this line. end end end # ./gems/actionpack5.2.2/lib/action_dispatch/journey/router.rb module Journey class Router class RoutingError < ::StandardError end attr_accessor :routes def initialize(routes) @routes = routes end def serve(req) find_routes(req).each do |match, parameters, route| # Let's step into 'find_routes' set_params = req.path_parameters path_info = req.path_info script_name = req.script_name unless route.path.anchored req.script_name = (script_name.to_s + match.to_s).chomp("/") req.path_info = match.post_match req.path_info = "/" + req.path_info unless req.path_info.start_with? "/" end parameters = route.defaults.merge parameters.transform_values { |val| val.dup.force_encoding(::Encoding::UTF_8) } req.path_parameters = set_params.merge parameters # 'route' is an instance of ActionDispatch::Journey::Route. # 'route.app' is an instance of ActionDispatch::Routing::RouteSet::Dispatcher. status, headers, body = route.app.serve(req) # Let's step into method 'serve' if "pass" == headers["X-Cascade"] req.script_name = script_name req.path_info = path_info req.path_parameters = set_params next end return [status, headers, body] end [404, { "X-Cascade" => "pass" }, ["Not Found"]] end def find_routes(req) routes = filter_routes(req.path_info).concat custom_routes.find_all { |r| r.path.match(req.path_info) } routes = if req.head? match_head_routes(routes, req) else match_routes(routes, req) end routes.sort_by!(&:precedence) routes.map! { |r| match_data = r.path.match(req.path_info) path_parameters = {} match_data.names.zip(match_data.captures) { |name, val| path_parameters[name.to_sym] = Utils.unescape_uri(val) if val } [match_data, path_parameters, r] } end end end end # ./gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb module ActionDispatch module Routing class RouteSet class Dispatcher < Routing::Endpoint def serve(req) params = req.path_parameters # params: { action: 'index', controller: 'home' } controller = controller(req) # controller: HomeController # The definition of 'make_response!' is # ActionDispatch::Response.create.tap { |res| res.request = request; } res = controller.make_response!(req) dispatch(controller, params[:action], req, res) # Let's step into this line. rescue ActionController::RoutingError if @raise_on_name_error raise else return [404, { "X-Cascade" => "pass" }, []] end end private def controller(req) req.controller_class rescue NameError => e raise ActionController::RoutingError, e.message, e.backtrace end def dispatch(controller, action, req, res) controller.dispatch(action, req, res) # Let's step into this line. end end end end end # ./gems/actionpack-5.2.2/lib/action_controller/metal.rb module ActionController class Metal < AbstractController::Base abstract! def self.controller_name @controller_name ||= name.demodulize.sub(/Controller$/, "").underscore end def self.make_response!(request) ActionDispatch::Response.new.tap do |res| res.request = request end end class_attribute :middleware_stack, default: ActionController::MiddlewareStack.new def self.inherited(base) base.middleware_stack = middleware_stack.dup super end # Direct dispatch to the controller. Instantiates the controller, then # executes the action named +name+. def self.dispatch(name, req, res) if middleware_stack.any? middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env else # 'self' is HomeController, so for this line Rails will new a HomeController instance. # Invoke `HomeController.ancestors`, you can find many superclasses of HomeController. # These are some typical superclasses of HomeController. # HomeController # < ApplicationController # < ActionController::Base # < ActiveRecord::Railties::ControllerRuntime (module included) # < ActionController::Instrumentation (module included) # < ActionController::Rescue (module included) # < AbstractController::Callbacks (module included) # < ActionController::ImplicitRender (module included) # < ActionController::BasicImplicitRender (module included) # < ActionController::Renderers (module included) # < ActionController::Rendering (module included) # < ActionView::Layouts (module included) # < ActionView::Rendering (module included) # < ActionDispatch::Routing::UrlFor (module included) # < AbstractController::Rendering (module included) # < ActionController::Metal # < AbstractController::Base new.dispatch(name, req, res) # Let's step into this line. end end def dispatch(name, request, response) set_request!(request) set_response!(response) process(name) # Let's step into this line. request.commit_flash to_a end def to_a response.to_a end end end # .gems/actionpack-5.2.2/lib/abstract_controller/base.rb module AbstractController class Base def process(action, *args) @_action_name = action.to_s unless action_name = _find_action_name(@_action_name) raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}" end @_response_body = nil # action_name: 'index' process_action(action_name, *args) # Let's step into this line. end end end # .gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb module ActionController module Instrumentation def process_action(*args) raw_payload = { controller: self.class.name, action: action_name, params: request.filtered_parameters, headers: request.headers, format: request.format.ref, method: request.request_method, path: request.fullpath } ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| begin # self: #<0x00007fcd3c5dfd48><0x00007fcd3c5dfd48>&:ref<0x00007fcd3c5dfd48><0x00007fcd3c5dfd48>

<0x00007fa7e9c54278>&block&block<:template:0x00007f822759cbc0>&&&block<:template:0x00007f89bab1efb8 class="container" ...><0x00007ff10d6c9d18>&block<:template::handlers::erb:0x00007ff10e1be188>

<0x00007f83ecfed310>&&&&<:command::servercommand:0x00007fdcc49791b0><:command::servercommand:0x00007fdcc49791b0><:command::servercommand:0x00007fa5f319bf40>&&&blk<:application:0x00007f7fe5523f98>&blk<:builder:0x00007f8c861ec278>&blk&blk&block<:client:0x00007ff114ece6b0>&&&block
&&