ruby-on-railsruby-on-rails-3initializationrailtie

Why Rails::Application has many duplicate initializers which are defined in Rails::Engine?


As i was working throught the rails 3 initialization process, i found that all the initializers defined in Rails::Engine(there are 10) were added to Rails::Application instance more than once. This means that these initializers would run many times. The following is the analysis: 1. Rails::Application#initializers:

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

It will call super(Rails::Engine) method which is defined as follows:

def initializers
  initializers = []
  ordered_railties.each do |r|
    if r == self
      initializers += super
    else
      initializers += r.initializers 
    end                              
  end
  initializers
end

We can see from Rails::Engine#initializers that every engine(class that inherited from Rails::Engine) will add Rails::Engine's initializers to that of it, including Rails::Application. But all the other engines are included in Rails::Application's ordered_railties, so their initializers are also added into that of Rails::Application. We can conclude that Rails::Engine's initializers are added many times to Rails::Application. We can see from the console info:

1.9.3p194 :002 > Rails.application.initializers.map(&:name).size
 => 119 
1.9.3p194 :001 > Rails.application.initializers.map(&:name).uniq.size
 => 79

So every initializer in Rails::Engine is added 5 times to that of Rails::Application. I wonder why this happens? Is there some special reason?


Solution

  • An initializer has several other attributes except name: context, block, etc. So whenever an engine inherited from Rails::Engine, all of Rails::Engine's initializers are added to the child engine with a different context. That is, although there are duplicate initializers in Rails::Application with the same name, they are indeed differernt initializers which will run under different context:

    def run(*args)                         
      @context.instance_exec(*args, &block)
    end