Replace A Rails Initializer
Tags: rails, tutorial, ruby
Published: 2020-05-31 05:59:38

Rails uses a series of initializers to initialize your application. While some of them might come from the gems you use, most of them are the Rails' built-in ones. And in some rare cases, you might need to customize some of them for special usages. 

For example, I had to replace my app's `set_routes_reloader_hook` initializer (the one that generates routes for your application) to benchmark our route generation logic. And it took a while to figure out how to do it the right way.

So if you don't need to do this, that's totally normal. But if you do, this post is for you.

## The Steps

In this post, I'll use the `set_routes_reloader_hook` initializer as our example. And the replacement will be done in 3 steps:

1. Identify the Initializer's Source
2. Register the New Initializer
3. Drop the Old Initializer

And finally, I'll show you how to reuse the original initializer's body instead of copy&paste the code.

### Identify the Initializer's Source

When talking about replacing a Rails initializer, your first thought might be modifying the `Rails::Application#initializers` directly. But that won't work.

In Rails, `Rails::Application#initializers` actually consists of 3 initializer sources:

```ruby
def initializers #:nodoc:
  Bootstrap.initializers_for(self) +
  railties_initializers(super) +
  Finisher.initializers_for(self)
end
```

So every time you call `Rails::Application#initializers`, it returns a new array of initializers, and the change made on it won't be applied to the rest of the app.

What we need to do instead, is to identify which source that our target initializer belongs to:

```ruby
Rails::Application::Bootstrap.initializers_for(Rails.application).map(&:name).include?(:set_routes_reloader_hook) #=> false
Rails::Application.initializers_for(Rails.application).map(&:name).include?(:set_routes_reloader_hook) #=> false
Rails::Application::Finisher.initializers_for(Rails.application).map(&:name).include?(:set_routes_reloader_hook) #=> true
```

Now we can see that the `set_routes_reloader_hook` is defined in `Rails::Application::Finisher`.

### Register the New Initializer

Because Rails executes initializers in the order they are defined, we need to make sure the new initializer is placed in the right place. So we need to use the target initializer as our index before dropping it. To do this, we can use the `before` or `after` argument when registering the new initializer:

```ruby
# config/application.rb

require 'rails/all'

Rails::Application::Finisher.initializer :set_routes_reloader_hook_with_benchmark, after: :set_routes_reloader_hook do |app|
  # ....
end
```

### Drop the Old Initializer

And then the last step: drop the target initializer

```ruby
# config/application.rb

require 'rails/all'

Rails::Application::Finisher.initializer :set_routes_reloader_hook_with_benchmark, after: :set_routes_reloader_hook do |app|
  # ....
end

Rails::Application::Finisher.initializers.reject! { |i| i.name == :set_routes_reloader_hook }
```


### Additional Tip: Reuse the Old Initializer 

Since we're "replacing" the initializer instead of just dropping it or adding a new one, it means the new initializer may be very similar to the old one or just an extension of it. But because initializers aren't methods, we can't use `super` like

```ruby
def set_routes_reloader_hook
  super
  # other stuff you want to do
end
```

If this is bothering you, here's a trick: We can extract the old initializer's block, and use `instance_eval` to evaluate it inside the new initializer

```ruby

original_initializer_block = Rails::Application::Finisher.initializers.detect { |i| i.name == target_initializer }.block

Rails::Application::Finisher.initializer :new_initializer, after: target_initializer do |app|
  instance_eval(&original_initializer_block)
  # other stuff you want to do
end

Rails::Application::Finisher.initializers.reject! { |i| i.name == target_initializer }
```

---

Thank you for reading this post. If you like it, I also wrote some articles about debugging Rails applications/Ruby programs


{% link https://dev.to/appsignal/changing-the-approach-to-debugging-in-ruby-with-tracepoint-45k9 %}

{% link https://dev.to/st0012/optimize-your-debugging-process-with-object-oriented-tracing-and-tappingdevice-39c6 %}

I'm also working on a gem called [tapping_device](https://github.com/st0012/tapping_device). It can help you debug Ruby programs more easily by making objects tell you what they do. 
Not sure what it means? There are examples in the readme 👇

{% github st0012/tapping_device no-readme %}