在 rails server 执行时,有的文件会被提前加载进来,所以这些文件中的 class 就已经被定义了,而有的 class,实际上,是在第一次被使用的时候,才会去根据 class 名,在目录中,去定位到文件,然后读取加载进来。
造成上述差别的,就是 eager_load!, 如果 rails 初始化时,通过 eager_load! 加载的文件过多,显然初始化时间也越长,但是也会运行的更快,因为省去了根据 class 名,去读取文件的过程。
在 rails 中,application, 各 engine, 或者是 namespace,如果需要在初始化时,就去加载和自己相关的文件,也就是执行 eager_load!, 需要将自己的 namespace 注册到 eager_load_namespaces 中,且必须 config.eager_load 是 true。application 在初始化时,会去运行各种 Railtie#initializer,在 railties/lib/application/finisher.rb 中,
initializer :eager_load! do
if config.eager_load
ActiveSupport.run_load_hooks(:before_eager_load, self)
config.eager_load_namespaces.each(&:eager_load!)
end
end
那么怎么去修改 config.eager_load_namespaces 呢?请看
chuck@chuck-MacBook-Pro:~/seaify/rails(master|●1✚3…) % grep eager_load_namespace * -ri
actionmailer/lib/action_mailer/railtie.rb: config.eager_load_namespaces << ActionMailer
actionpack/lib/action_controller/railtie.rb: config.eager_load_namespaces << ActionController
actionpack/lib/action_dispatch/railtie.rb: config.eager_load_namespaces << ActionDispatch
actionview/lib/action_view/railtie.rb: config.eager_load_namespaces << ActionView
activemodel/lib/active_model/railtie.rb: config.eager_load_namespaces << ActiveModel
activerecord/lib/active_record/railtie.rb: config.eager_load_namespaces << ActiveRecord
activesupport/lib/active_support/railtie.rb: config.eager_load_namespaces << ActiveSupport
guides/source/configuring.md:* `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
guides/source/configuring.md:* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is true. All namespaces in the list must respond to the `eager_load!` method.
guides/source/configuring.md:* `eager_load!` If `config.eager_load` is true, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`.
railties/lib/rails/application/finisher.rb: config.eager_load_namespaces.each(&:eager_load!)
railties/lib/rails/engine.rb: Rails::Railtie::Configuration.eager_load_namespaces << base
railties/lib/rails/railtie/configuration.rb: # Expose the eager_load_namespaces at "module" level for convenience.
railties/lib/rails/railtie/configuration.rb: def self.eager_load_namespaces #:nodoc:
railties/lib/rails/railtie/configuration.rb: @@eager_load_namespaces ||= []
railties/lib/rails/railtie/configuration.rb: def eager_load_namespaces
railties/lib/rails/railtie/configuration.rb: @@eager_load_namespaces ||= []
railties/test/application/configuration_test.rb: assert_includes Rails.application.config.eager_load_namespaces, AppTemplate::Application
可以注意到 ActiveSupport, ActionDispatch, ActiveModel, ActionController 等这些 module, 都已经将自己的 namespace 追加到了 config.eager_load_namespaces, 而且我们能注意到 eager_load_namespaces 实际上返回的是一个类变量@@eager_load_namespaces,所以实际上只有一份,各个 module,gem 修改的是同一个变量。
以我的实际项目 homepage 为例,可以看到 rails 下常用的几个 module 的 namespace 都有注册到 eager_load_namespace, 而那些第三方的 engine,也都有注册
2.1.5 :001 > Homepage::Application.config.eager_load_namespaces
=> [ActiveSupport, ActionDispatch, ActiveModel, ActionView, ActionController, ActiveRecord, ActionMailer, Coffee::Rails::Engine, Jquery::Rails::Engine, Turbolinks::Engine, Haml::Rails::Engine, Bootstrap::Rails::Engine, RailsSettingsUi::Engine, SimpleForm, MdEmoji::Engine, Devise::Engine, SocialShareButton::Rails::Engine, Owlcarousel::Rails::Engine, Sidekiq::Rails, DisqusRails::Rails::Engine, Ahoy::Engine, Kaminari::Engine, Wice::WiceGridEngine, BootstrapDatepickerRails::Rails::Engine, Jquery::Ui::Rails::Engine, Highcharts::Rails::Engine, LazyHighCharts::Rails::Engine, FontAwesome::Rails::Engine, Spree::Core::Engine, Spree::Api::Engine, Select2::Rails::Engine, Spree::Backend::Engine, CanonicalRails::Engine, Spree::Frontend::Engine, SpreeSample::Engine, SpreeGateway::Engine, KaminariI18n::Engine, SpreeI18n::Engine, QuietAssets::Engine, Homepage::Application, ExceptionNotification::Engine
第三方的 engine 注册,是通过 railties/lib/engine.rb, 每个 engine 都有将自己的 namespace 写入,注意 Rails::Railtie Rails::Engine Rails::Application, 是定义好的 abstract_railtie,不会写入
def inherited(base)
unless base.abstract_railtie?
Rails::Railtie::Configuration.eager_load_namespaces << base
综上解释了,eager_load_namespaces 的修改及由来,但每个 namespace 下,还需要有 method,eager_load!
eager_load!, 实际上分了 2 派,一种是通过 activesupport/lib/active_support/dependencies/autoload.rb 提供的 eager_load!, 也就是去 extend ActiveSupport::Autoload, 如对于 ActionMailer, ActionDispatch 等
module ActionDispatch
extend ActiveSupport::Autoload
class IllegalStateError < StandardError
end
eager_autoload do
autoload_under 'http' do
autoload :Request
autoload :Response
end
end
而 activesupport/lib/active_support/dependencies/autoload.rb 的实现方法
def eager_load!
@_autoloads.each_value { |file| require file }
end
而@_autoloads
的由来,是因为在 eager_autoload 的代码块中,@_eager_autoload
已经被暂时置为 true, 所以其中的 autoload 函数,能够为@_autoloads
写入路径的映射关系
def autoload(const_name, path = @_at_path)
unless path
full = [name, @_under_path, const_name.to_s].compact.join("::")
path = Inflector.underscore(full)
end
if @_eager_autoload
@_autoloads[const_name] = path
end
super const_name, path
end
上述这种办法,是针对于非 engine 的其它 namespace.
下面介绍基于 engine 的 eager_load! 的实现
2.1.5 :005 > Homepage::Application.config.eager_load_namespaces[-2]
=> Homepage::Application
2.1.5 :006 > Homepage::Application.config.eager_load_namespaces[-2].method(:eager_load!).source_location
=> ["/Users/chuck/.rvm/gems/ruby-2.1.5/gems/railties-4.2.2/lib/rails/engine.rb", 346]
# Eager load the application by loading all ruby
# files inside eager_load paths.
def eager_load!
config.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file.sub(matcher, '\1')
end
end
end
所以注意到,在 engine 中,是需要去设置变量 eager_load_paths, 而 railties/lib/rails/engine/configuration.rb 中
def eager_load_paths
@eager_load_paths ||= paths.eager_load
end
而 paths.eager_load, 是已经被设置好的,也就是 app/assets, app/helpers, app/xxx/concerns, 也就是每个 engine 的下列 eager_load 是 true 的路径都会被加载,
def paths
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
paths.add "app", eager_load: true, glob: "{*,*/concerns}"
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
paths.add "app/helpers", eager_load: true
paths.add "app/models", eager_load: true
paths.add "app/mailers", eager_load: true
paths.add "app/views"
paths.add "lib", load_path: true
paths.add "lib/assets", glob: "*"
paths.add "lib/tasks", glob: "**/*.rake"
paths.add "config"
paths.add "config/environments", glob: "#{Rails.env}.rb"
paths.add "config/initializers", glob: "**/*.rb"
paths.add "config/locales", glob: "*.{rb,yml}"
paths.add "config/routes.rb"
paths.add "db"
paths.add "db/migrate"
paths.add "db/seeds.rb"
paths.add "vendor", load_path: true
paths.add "vendor/assets", glob: "*"
paths
end
end
从这些内部代码中,也可得出,若想将我们的代码,也在启动时,自动启动,则在 config/application.rb 中加入
config.paths.add "extras", eager_load: true