Rails Internals

Summarized using AI

The Rails Boot Process

Xavier Noria • September 26, 2024 • Toronto, Canada

The video titled "The Rails Boot Process" features Xavier Noria discussing the intricacies involved in booting a Rails application. The session details the differences between booting the application and launching the server, emphasizing that booting prepares the application for use, making it accessible through commands or the console. Noria outlines the boot process, detailing crucial components and concepts essential for understanding Rails lifecycle and configuration.

Key points discussed include:

- Booting vs. Launching: Booting refers to preparing the application for action; launching a web server is a subsequent step. Booting occurs for most Rails commands (e.g., migrations via rake tasks).

- Configuration Files: The three main files involved in booting are config/boot.rb, config/environment.rb, and config/application.rb. The environment.rb file is the entry point, requiring application.rb, which in turn requires boot.rb.

- Bundler Setup: Important for managing gem dependencies, which ensures that only specified gems in the Gemfile are loaded. Thus, the application can operate with defined components readily available.

- Lazy Load and Initialization Hooks: These hooks allow developers to execute code when certain classes are ready or after booting is complete, enhancing flexibility during the boot process.

- Railties and Initializers: Railties help integrate components into Rails applications. Initializers allow user-defined configurations to register their effects, enabling Rails components like Active Record to manage their configuration aptly.

- Execution Order: The boot process sorts and executes initializers for effective organization of application dependencies and configuration settings.

- Final Steps in Booting: These include setting up autocoders, middleware stacks, and logging to properly route requests and load resources during application runtime.

The main takeaways from the talk include a comprehensive understanding of the processes that occur during a Rails application's boot stages, the significance of configuration files, the various hooks provided by Rails, and the concept of railties for integration. Overall, Noria emphasizes the structured nature of Rails initialization, vital for developers aiming to customize and extend their applications.

This insightful presentation provides a roadmap for effectively managing and understanding the inner workings of Rails applications, essential knowledge for both new and seasoned Rails developers.

The Rails Boot Process
Xavier Noria • September 26, 2024 • Toronto, Canada

What happens when a Rails application boots? When is the logger ready? When is $LOAD_PATH set? When do initializers run or when are the autoloaders are set up? Rails Core member Xavier Noria covered all this and more in his talk at #RailsWorld.

#Rails #Rails8 #internals #boot #railties #engines #initialization

Thank you Shopify for sponsoring the editing and post-production of these videos. Check out insights from the Engineering team at: https://shopify.engineering/

Stay tuned: all 2024 Rails World videos will be subtitled in Japanese and Brazilian Portuguese soon thanks to our sponsor Happy Scribe, a transcription service built on Rails. https://www.happyscribe.com/

Rails World 2024

00:00:11.799 As you know, the Rails components do not need to live inside a Rails application. For instance, you can use Active Record outside of a Rails application.
00:00:19.119 However, somehow when you are in the context of a Rails application, everything gets coordinated, and you have access to everything available, including Active Job and the database.
00:00:31.439 So this talk is about how this works and also a little bit about what happens when.
00:00:38.040 The first thing that we are going to see is how you boot a Rails application. For many people, booting a Rails application means launching the server.
00:00:51.280 But there are actually two stages involved: booting, which is smaller than launching the server. First, you boot the application, and then you launch the web server. Booting an application means having everything configured and ready to be used.
00:01:06.439 For instance, you can do this in a console. Once you launch a console, you have all models available, can interact with the database, schedule jobs, and have the entire application at your disposal. In this case, there's no web server running, but everything has been booted and is ready to go.
00:01:29.479 Most Rails commands will boot the application. A few do not, such as 'rails stats,' which does not boot the application. However, most commands, such as rake tasks, will also boot the application.
00:01:41.079 For instance, if you have a migration, the models are available. So how do you do this? In the examples we have seen, we are booting the application from the console or shell. If you want to do this programmatically in a Ruby script, you can use the public interface by requiring 'config/environment.rb.' Doing this will boot the application, and from that point, everything is ready for you.
00:02:05.640 In the particular case of rake tasks, you also depend on the environment task, which is provided by Rails. If you follow the process through, it actually calls require 'config/environment.rb.' Now that we are on the same page about what it means to boot the application, let's proceed.
00:02:43.879 Before we dive into the actual topic, there are a couple of preliminary sections, a couple of concepts, that we need to understand in order to go through the boot process.
00:03:02.840 The first concept is lazy load hooks. Let's imagine we have an initializer that wants to extend Active Record with a module.
00:03:15.599 One way to do that is to require the module and then reopen the class. However, there is a convention in Rails applications that states the framework is responsible for loading things whenever they need to be loaded.
00:03:29.120 You simply hook into this process. When the framework decides it needs to load something, you can perform your actions through lazy load hooks.
00:03:43.560 So, you can say 'on_load :active_record' and provide a block that includes your extensions. This means that instead of reopening the class, you say: "Whenever the class is ready, call me, and then I will do my thing." This is the correct way to extend Active Record.
00:04:02.920 There are more than 40 load hooks similar to this one I just showed for Active Record.
00:04:18.480 The second preliminary concept I want to explain is initialization hooks, which you may have seen in your applications. These are implemented with the previous technique, but they have special syntax and API. For instance, 'config.after_initialize' means there's a checkpoint during boot where it is indicated to please call me once the initialization process is finished.
00:04:50.039 There are several of these initialization hooks, and they allow you to execute code only after the application has completed the boot process.
00:05:06.960 Now, with these two concepts in mind, we can begin to understand how things boot. We'll start with the main bootstrapping files in a newly generated Rails application, which are these three: config/boot.rb, config/environment.rb, and config/application.rb.
00:05:35.520 We saw before that config/environment.rb is the canonical entry point for booting the application, but we have these three files.
00:05:52.479 Normally, you do not even open config/boot.rb or config/environment.rb. They are generated and serve their purpose, but you typically work more with config/application.rb. However, let's take a look at what config/environment.rb, the entry point of the boot process, actually does.
00:06:11.400 It's a very simple file that does two things: It requires config/application.rb and then calls the method on the application singleton. Now, let's see what config/application.rb looks like.
00:06:43.000 The first thing that it does is to require 'config/boot.rb.' This boot.rb file has some additional setup necessary for the application.
00:07:06.080 The setup in this file includes environment variables for Bundler to locate the gemfile and performs the bundler setup, which basically means that the application will only be able to load the gems specified in the Gemfile.
00:07:30.680 Gems that are not declared there or those declared but with differing versions are simply not going to be available in your environment.
00:07:54.720 Once loaded, we have 'require rails/all' available, which tells us that at this point, Rails components are ready to use.
00:08:18.280 After this line, Rails components are available, but there is still a question about when specific components, like the Rails logger, are accessible.
00:08:39.040 That's something that I believe lacks proper documentation. It would be beneficial to define a clear contract and create a test suite to verify everything.
00:09:02.959 At this point, Rails.logger is available; however, Rails.root isn't accessible just yet.
00:09:10.480 Then we reach the bundle require, which will require all the gems listed in your Gemfile unless you choose to opt out.
00:09:31.480 Now we arrive at this line where we define an application class that inherits from 'Rails::Application.' This line looks innocuous, but it has profound implications.
00:09:51.200 In Ruby, classes have an optional method called 'inherited.' If a class implements this method and is then subclassed, the inherited hook is called.
00:10:18.560 As a side effect of subclassing, a number of things will happen, such as gaining access to the application singleton and Rails.root.
00:10:36.160 This is why you can use Rails.root in the class body; it is not available in the context of a standalone class.
00:11:01.360 Autoloaders are also available for configuration, although you won't yet be able to autoload. You can access them to print logs or ignore things, but at this stage, the load path gets prepended.
00:11:37.920 If you ever needed to load something from 'lib,' you would encounter issues if you attempted this before defining the application. However, once the class body has been executed, you will be able to.
00:12:06.520 There's also a last step where a before configuration hook is executed. This may not be particularly useful for applications because at this stage, the application still hasn't had the chance to do anything, but it can be practical for gems.
00:12:34.800 Finally, we execute the class body where our application class has been fully defined, and the application is now initialized.
00:12:54.319 The next important part is the 'initialize!' method on the application singleton. To fully understand this line, we need to look at the rest of the presentation.
00:13:06.640 To recap, we have set up Bundler, booted the application, required Rails, and now the gem dependencies are all available.
00:13:27.600 Following this, we execute the class body and prepare the application for initialization.
00:13:49.120 In summary, we need to introduce a few concepts, which have evolved from the early days of Rails when the boot process was more procedural.
00:14:07.440 The first concept is railties. A railtie is simply a class that belongs to the Rails framework. By subclassing a railtie, you gain access to certain features, such as configuration points and initializer registration.
00:14:25.120 One of the most important methods is 'Rails::Railtie#initializer,' which registers a block to be executed during initialization.
00:14:42.640 These initializers are registered in the order they appear, but you can control their order by providing options like 'before' and 'after.' You can also see a list of all registered initializers with 'bin/rails initializers.'
00:15:00.640 Active Record provides an example of a railtie, allowing it to hook into Rails and set its own logger by registering an initializer.
00:15:18.560 When Active Record is loaded, it can reference the application's logger, making it flexible to use within different contexts.
00:15:45.840 The design idea is to define initializers so that the ownership of configuration belongs to the user and the Rails application.
00:16:06.679 The initializer you register will read the parameters defined by the user to configure itself accordingly.
00:16:30.080 Another example from Active Record is the database initializer. Here, we see that it also uses a lazy hook to load the database configuration based on what the user specified.
00:16:54.160 We can also attach initializers to Rails commands, like the 'runner' command, which allows you to run Ruby code in the context of Rails.
00:17:30.080 When the runner command executes, it ensures that Active Record Base is loaded to establish a database connection when needed.
00:17:50.960 In a Rails 8 application, there are numerous engines which also provide the functionality to register initializers, configure endpoints, and handle routes.
00:18:12.720 Since engines inherit from railties, everything we discussed earlier applies to them. They can also have their own configuration.
00:18:31.120 A Rails 8 application comes with several engines, and we now have enough information to unpack how the Rails application functions.
00:18:53.680 When we require 'rails/all,' it essentially requires the railties and engines of the application, registering all initializers.
00:19:13.920 The final concept is applications: a Rails application is a subclass of Rails::Engine, which makes it a railtie as well.
00:19:41.559 This design allows for engines to utilize the features of railties, ensuring seamless integration within the Rails ecosystem.
00:20:04.959 Now, we have set up all the necessary components to understand how the 'initialize!' method works.
00:20:31.440 The actual process of initialization involves topologically sorting the registered initializers so that they execute in the correct order based on user-defined hooks.
00:20:53.600 These sets consist of 'bootstrap' railties—which we have focused on—config initializers from your application, and also the 'finisher' set.
00:21:17.920 During the bootstrapping phase, the environment configuration is loaded based on the environment we are currently running in.
00:21:37.600 For example, if in development mode, the application will load 'config/development.rb,' allowing for any configurations to take precedence.
00:21:58.480 Active Support is loaded during this phase to enable some functionalities that assist both application and engine behaviors.
00:22:18.280 Then, when initializing, the logger is defined, and the autoloaders are set up, allowing for dynamic loading of classes and modules during runtime.
00:22:58.760 The final step is the execution of initializers—those defined by the rails engines and your Rails application—allowing all the components to properly configure themselves.
00:23:23.360 At this point, we set up the main autoloader and configure the middleware stack, allowing for proper routing and loading of resources.
00:23:43.680 We also have to decide if we want to eager load routes, depending on what we're doing.
00:23:59.040 The final part of the boot process executes the 'after_initialize' hook, meaning that we can now track the configurations and parameters that were set by the user.
00:24:13.840 Once we've completed the boot setup, we enable JIT for performance, but we must remember that JIT monitoring should occur with code that executes multiple times.
00:24:34.960 As the boot process nears its end, we recap everything: loading bundler, the configurations, initializers, and executing them in a topologically sorted manner.
00:25:03.840 This brings us to the conclusion, and we acknowledge that we covered many essential elements of how Rails applications boot.
00:25:18.840 Thank you.
Explore all talks recorded at Rails World 2024
+17