Understanding Ruby and Rails: Lazy load hooks

This article targets Rails 3

The article was written as of Rails 3.2. The information contained in this page might not apply to different versions.

This is article is part of my series Understanding Ruby and Rails. Please see the table of contents for the series to view the list of all posts.

A small-but-interesting feature introduced in Rails 3 is the built-in support for lazy loading.

Lazy loading is a very common design pattern. The concept is to defer initialization of an object until the point at which it is needed. This design pattern decreases the time required by an application to boot by distributing the computation cost during the execution. Also, if a specific feature is never used, the computation won’t be executed at all.

With Rails 3 you can now register specific hooks to be lazy-executed when the corresponding library is loaded.

```ruby class ApplicationController < ActionController::Base

initializer “active_record.include_plugins” do ActiveSupport.on_load(:active_record) do include MyApp::ActivePlugins end end

end ```

In this case we register the block to be executed when the ActiveRecord library is loaded. If you read the ActiveRecord::Base source code, the very last line is a call to

ruby ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)

This line of code executes all the hooks previously registered for ActiveRecord.

lazy-load hooks in the wild

Perhaps one of the most frequent usage of the lazy-load hooks is in Rails plugins.

For example, if your plugin needs to register some helpers, you can write a hook to include the helpers in ActionView only when ActionView is loaded. If the environment is loaded from a rake task (which doesn’t necessary need ActionView), then your plugin hook won’t be executed and the Rails application will boot faster.

Here’s an example from the will_paginate gem.

```ruby require ‘will_paginate’ require ‘will_paginate/collection’

module WillPaginate class Railtie < Rails::Railtie initializer “will_paginate.active_record” do |app| ActiveSupport.on_load :active_record do require ‘will_paginate/finders/active_record’ WillPaginate::Finders::ActiveRecord.enable! end end

initializer "will_paginate.action_dispatch" do |app|
  ActiveSupport.on_load :action_controller do
    ActionDispatch::ShowExceptions.rescue_responses['WillPaginate::InvalidPage'] = :not_found
  end
end

initializer "will_paginate.action_view" do |app|
  ActiveSupport.on_load :action_view do
    require 'will_paginate/view_helpers/action_view'
    include WillPaginate::ViewHelpers::ActionView
  end
end   end end ```

Using lazy load in your libraries

So far, we only discussed about using lazy-loading to hook Rails core library. Because lazy-loading is an ActiveSupport feature, you can use it in your Rails applications but also in your own Ruby classes.

First, make sure to add a call to ActiveSupport.run_load_hooks at the end of your Ruby class.

```ruby class HttpClient # …

ActiveSupport.run_load_hooks(:http_client, self) end ```

Now you can register on_load hooks everywhere passing the name of the library used in run_load_hooks.

```ruby class Request # …

ActiveSupport.on_load :http_client do # do something end end

class Response # …

ActiveSupport.on_load :http_client do # do something end end ```

Hook context

The run_load_hooks method takes a second parameter representing the context within the hook will be executed. It’s a common pattern to pass the class/instance the hooks refer to.

For instance, the ActiveRecord library mentioned at the beginning of the article passes self. In that context, self references the ActiveRecord::Base class.

This is useful because, if you want to include some custom methods into ActiveRecord, you can use the following block

ruby ActiveSupport.on_load :active_record do include MyPlugin::Extensions end

instead of

ruby ActiveSupport.on_load :active_record do ActiveRecord::Base.send :include, MyPlugin::Extensions end

There’s also an other interesting use case for this feature. If you want to perform some kind of lazy-initialization when an instance of a class is created, just pass the instance itself.

```ruby class Color def initialize(name) @name = name

ActiveSupport.run_load_hooks(:instance_of_color, self)   end end

ActiveSupport.on_load :instance_of_color do puts “The color is #{@name}” end

Color.new(“yellow”) # => “The color is yellow” ```

Source Code

The source code of the lazy-loading feature is available in the lazy_load_hooks.rb file.