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.