Más en rubyonrails.org: Más Ruby on Rails

NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS SE PUBLICAN EN https://railsguides.es/

The Rails Initialization Process

Esta guía explica los aspectos internos del proceso de inicialización en Rails. Es una guía muy detallada y recomendada para desarrolladores avanzados de Rails.

Después de leer esta guía, sabrá:

Esta guía pasa por todas las llamadas a métodos que se necesario para iniciar la pila de Ruby on Rails para un Rails predeterminado aplicación, explicando cada parte en detalle a lo largo del camino. Para esto guía, nos centraremos en lo que sucede cuando ejecuta bin / rails server para iniciar su aplicación.

Las rutas en esta guía son relativas a Rails o una aplicación Rails a menos que se especifique lo contrario.

SUGERENCIA: Si desea seguir la lectura mientras navega por Rails fuente código, le recomendamos que utilice el t enlace de teclas para abrir el buscador de archivos dentro de GitHub y buscar archivos con rapidez.

1 Launch!

Comencemos a arrancar e inicializar la aplicación. Una aplicación Rails suele comenzó ejecutando bin/rails console o bin/rails server.

1.1 bin/rails

Este archivo es el siguiente:

#!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative "../config/boot"
require "rails/commands"

La constante APP_PATH se utilizará más adelante enrails / commands. El archivo config / boot al que se hace referencia aquí es el archivoconfig/boot.rb en nuestra aplicación que es responsable de cargar Bundler y configurarlo.

1.2 config/boot.rb

config/boot.rb contiene:

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require "bundler/setup" # Set up gems listed in the Gemfile.

En una aplicación estándar de Rails, hay un Gemfile que declara todos dependencias de la aplicación. Conjuntos de config/boot.rb ENV['BUNDLE_GEMFILE'] a la ubicación de este archivo. Si el Gemfile existe, entonces se requiere bundler/setup. Bundler utiliza el require para configure la ruta de carga para las dependencias de su Gemfile.

Una aplicación estándar de Rails depende de varias gemas, específicamente:

  • actioncable
  • actionmailer
  • actionpack
  • actionview
  • activejob
  • activemodel
  • activerecord
  • activestorage
  • activesupport
  • actionmailbox
  • actiontext
  • arel
  • builder
  • bundler
  • erubi
  • i18n
  • mail
  • mime-types
  • rack
  • rack-test
  • rails
  • railties
  • rake
  • sqlite3
  • thor
  • tzinfo

1.3 rails/commands.rb

Una vez que config/boot.rb haya terminado, el siguiente archivo que se requiere es rails/commands, que ayuda a expandir los alias. En el caso actual, el La matriz ARGV simplemente contiene el server que se pasará:

require "rails/command"

aliases = {
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner",
  "t"  => "test"
}

command = ARGV.shift
command = aliases[command] || command

Rails::Command.invoke command, ARGV

Si hubiéramos usado s en lugar deservidor, Rails habría usado los alias definido aquí para encontrar el comando correspondiente.

1.4 rails/command.rb

Cuando uno escribe un comando Rails, invoke intenta buscar un comando para el dado espacio de nombres y ejecuta el comando si lo encuentra.

Si Rails no reconoce el comando, le entrega las riendas a Rake. para ejecutar una tarea con el mismo nombre.

Como se muestra, Rails::Command muestra la salida de ayuda automáticamente si el namespace esta vacio.

module Rails
  module Command
    class << self
      def invoke(full_namespace, args = [], **config)
        namespace = full_namespace = full_namespace.to_s

        if char = namespace =~ /:(\w+)$/
          command_name, namespace = $1, namespace.slice(0, char)
        else
          command_name = namespace
        end

        command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name)
        command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name)

        command = find_by_namespace(namespace, command_name)
        if command && command.all_commands[command_name]
          command.perform(command_name, args, config)
        else
          find_by_namespace("rake").perform(full_namespace, args, config)
        end
      end
    end
  end
end

Con el comando server, Rails ejecutará el siguiente código:

module Rails
  module Command
    class ServerCommand < Base # :nodoc:
      def perform
        extract_environment_option_from_argument
        set_application_directory!
        prepare_restart

        Rails::Server.new(server_options).tap do |server|
          # Require application after server sets environment to propagate
          # the --environment option.
          require APP_PATH
          Dir.chdir(Rails.application.root)

          if server.serveable?
            print_boot_information(server.server, server.served_url)
            after_stop_callback = -> { say "Exiting" unless options[:daemon] }
            server.start(after_stop_callback)
          else
            say rack_server_suggestion(using)
          end
        end
      end
    end
  end
end

Este archivo cambiará al directorio raíz de Rails (una ruta dos directorios arriba de APP_PATH que apunta aconfig / application.rb), pero solo si el No se encuentra el archivo config.ru. Esto luego inicia la clase Rails :: Server.

1.5 actionpack/lib/action_dispatch.rb

Action Dispatch es el componente de enrutamiento del marco de Rails. Agrega funcionalidad como enrutamiento, sesión y middlewares comunes.

1.6 rails/commands/server/server_command.rb

La clase Rails::Server se define en este archivo heredando de Rack::Server. Cuando se llama a Rails::Server.new, se llama a initialize método en rails/commands/server/server_command.rb:

module Rails
  class Server < ::Rack::Server
    def initialize(options = nil)
      @default_options = options || {}
      super(@default_options)
      set_environment
    end
  end
end

En primer lugar, se llama a super que llama al método initialize en Rack::Server.

1.7 Rack: lib/rack/server.rb

Rack::Server es responsable de proporcionar una interfaz de servidor común para todas las aplicaciones basadas en Rack, de la que Rails ahora forma parte.

El método initialize en Rack::Server simplemente establece varias variables:

module Rack
  class Server
    def initialize(options = nil)
      @ignore_options = []

      if options
        @use_default_options = false
        @options = options
        @app = options[:app] if options[:app]
      else
        argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV
        @use_default_options = true
        @options = parse_options(argv)
      end
    end
  end
end

En este caso, el valor de retorno de Rails :: Command :: ServerCommand # server_options se asignará aoptions. Cuando se evalúan las líneas dentro de la instrucción if, se establecerán un par de variables de instancia.

El método server_options enRails :: Command :: ServerCommand se define de la siguiente manera:

module Rails
  module Command
    class ServerCommand
      no_commands do
        def server_options
          {
            user_supplied_options: user_supplied_options,
            server:                using,
            log_stdout:            log_to_stdout?,
            Port:                  port,
            Host:                  host,
            DoNotReverseLookup:    true,
            config:                options[:config],
            environment:           environment,
            daemonize:             options[:daemon],
            pid:                   pid,
            caching:               options[:dev_caching],
            restart_cmd:           restart_command,
            early_hints:           early_hints
          }
        end
      end
    end
  end
end

El valor se asignará a la variable de instancia @ options.

Después de que super haya terminado en Rack::Server, volvemos a rails/commands/server/server_command.rb. En este punto, set_environment se llama dentro del contexto del objeto Rails::Server.

module Rails
  module Server
    def set_environment
      ENV["RAILS_ENV"] ||= options[:environment]
    end
  end
end

Después de que haya finalizado initialize, volvemos al comando del servidor donde se requiere APP_PATH (que se configuró anteriormente).

1.8 config/application

Cuando se ejecuta require APP_PATH, se carga config/application.rb (recordar que APP_PATH está definido en bin/rails). Este archivo existe en su aplicación y es gratis para que cambie según sus necesidades.

1.9 Rails::Server#start

Después de cargar config/application, se llama a server.start. Este método es definido así:

module Rails
  class Server < ::Rack::Server
    def start(after_stop_callback = nil)
      trap(:INT) { exit }
      create_tmp_directories
      setup_dev_caching
      log_to_stdout if options[:log_stdout]

      super()
      ...
    end

    private
      def setup_dev_caching
        if options[:environment] == "development"
          Rails::DevCaching.enable_by_argument(options[:caching])
        end
      end

      def create_tmp_directories
        %w(cache pids sockets).each do |dir_to_make|
          FileUtils.mkdir_p(File.join(Rails.root, "tmp", dir_to_make))
        end
      end

      def log_to_stdout
        wrapped_app # touch the app so the logger is set up

        console = ActiveSupport::Logger.new(STDOUT)
        console.formatter = Rails.logger.formatter
        console.level = Rails.logger.level

        unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
          Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
        end
      end
  end
end

Este método crea una trampa para las señales INT, por lo que si CTRL-C el servidor, saldrá del proceso. Como podemos ver en el código aquí, creará el tmp/cache, Directorios tmp/pids y tmp/sockets. Luego habilita el almacenamiento en caché en desarrollo si se llama a bin/rails server con --dev-caching. Finalmente, llama a wrap_app que es responsable de crear la aplicación Rack, antes de crear y asignar una instancia de ActiveSupport::Logger.

El método super llamará a Rack::Server.start que comienza su definición de la siguiente manera:

module Rack
  class Server
    def start &blk
      if options[:warn]
        $-w = true
      end

      if includes = options[:include]
        $LOAD_PATH.unshift(*includes)
      end

      if library = options[:require]
        require library
      end

      if options[:debug]
        $DEBUG = true
        require "pp"
        p options[:server]
        pp wrapped_app
        pp app
      end

      check_pid! if options[:pid]

      # Touch the wrapped app, so that the config.ru is loaded before
      # daemonization (i.e. before chdir, etc).
      handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
        wrapped_app
      end

      daemonize_app if options[:daemonize]

      write_pid if options[:pid]

      trap(:INT) do
        if server.respond_to?(:shutdown)
          server.shutdown
        else
          exit
        end
      end

      server.run wrapped_app, options, &blk
    end
  end
end

La parte interesante de una aplicación Rails es la última línea, server.run. Aquí nos encontramos con el método wrap_app nuevamente, que esta vez vamos a explorar más (a pesar de que se ejecutó antes, y así memorizado por ahora).

module Rack
  class Server
    def wrapped_app
      @wrapped_app ||= build_app app
    end
  end
end

El método app aquí se define así:

module Rack
  class Server
    def app
      @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
    end
    ...

    private
      def build_app_and_options_from_config
        if !::File.exist? options[:config]
          abort "configuration #{options[:config]} not found"
        end

        app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
        @options.merge!(options) { |key, old, new| old }
        app
      end

      def build_app_from_string
        Rack::Builder.new_from_string(self.options[:builder])
      end

  end
end

El valor options[:config] predeterminado es config.ru que contiene esto:

# This file is used by Rack-based servers to start the application.

require_relative "config/environment"

run Rails.application

El método Rack::Builder.parse_file aquí toma el contenido de este archivo config.ru y lo analiza usando este código:

module Rack
  class Builder
    def self.load_file(path, opts = Server::Options.new)
      ...
      app = new_from_string cfgfile, config
      ...
    end

    ...

    def self.new_from_string(builder_script, file="(rackup)")
      eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
        TOPLEVEL_BINDING, file, 0
    end
  end
end

El método initialize de Rack::Builder tomará el bloque aquí y lo ejecutará dentro de una instancia de Rack :: Builder. Aquí es donde ocurre la mayor parte del proceso de inicialización de Rails. La línea require paraconfig/environment.rb en config.ru es la primera en ejecutarse:

require_relative "config/environment"

1.10 config/environment.rb

Este archivo es el archivo común requerido por config.ru (bin/rails server) y Passenger. Aquí es donde se encuentran estas dos formas de ejecutar el servidor; todo antes de este punto ha sido la configuración de Rack and Rails.

Este archivo comienza requiriendo config/application.rb:

require_relative "application"

1.11 config/application.rb

Este archivo requiere config/boot.rb:`

require_relative "boot"

Pero solo si no se ha requerido antes, que sería el caso en bin/rails server pero no sería el caso de Passenger.

¡Entonces comienza la diversión!

2 Loading Rails

La siguiente línea en config/application.rb es:

require "rails/all"

2.1 railties/lib/rails/all.rb

Este archivo es responsable de requerir todos los marcos individuales de Rails:

require "rails"

%w(
  active_record/railtie
  active_storage/engine
  action_controller/railtie
  action_view/railtie
  action_mailer/railtie
  active_job/railtie
  action_cable/engine
  action_mailbox/engine
  action_text/engine
  rails/test_unit/railtie
  sprockets/railtie
).each do |railtie|
  begin
    require railtie
  rescue LoadError
  end
end

Aquí es donde se cargan todos los frameworks Rails y así se hacen disponible para la aplicación. No entraremos en detalles de lo que pasa dentro de cada uno de esos marcos, pero le recomendamos que intente Explórelos por su cuenta.

Por ahora, solo tenga en cuenta que la funcionalidad común, como los motores Rails, La configuración de I18n y Rails se define aquí.

2.2 Back to config/environment.rb

El resto de config/application.rb define la configuración del Rails::Application que se utilizará una vez que la aplicación esté completamente inicializado. Cuando config/application.rb haya terminado de cargar Rails y definido el espacio de nombres de la aplicación, volvemos a config/environment.rb. Aquí el la aplicación se inicializa con Rails.application.initialize!, que es definido en rails/application.rb.

2.3 railties/lib/rails/application.rb

El método initialize! Se ve así:

def initialize!(group = :default) #:nodoc:
  raise "Application has been already initialized." if @initialized
  run_initializers(group, self)
  @initialized = true
  self
end

Comentarios Sobre el Contenido

Las guías de rieles se administran y publican en latinadeveloper/railsguides.es en GitHub.

Si lee esta guía y encuentra algún texto o código incorrecto que le interese, no dude en enviar una solicitud de extracción en el repositorio anterior. Consulte el archivo README en GitHub para saber cómo enviar una solicitud de extracción. Please contribute if you see any typos or factual errors.