initializer.rb 20.3 KB
Newer Older
1
require "pathname"
2 3

$LOAD_PATH.unshift File.dirname(__FILE__)
4 5 6 7
require 'railties_path'
require 'rails/version'
require 'rails/gem_dependency'
require 'rails/rack'
8
require 'rails/paths'
9 10
require 'rails/core'
require 'rails/configuration'
11

12 13
RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)

14 15
module Rails
  class Initializer
16
    class Error < StandardError ; end
17

18 19 20 21 22
    class Base
      class << self
        def run(&blk)
          define_method(:run, &blk)
        end
23

24 25 26
        def config=(config)
          @@config = config
        end
27

28 29 30 31
        def config
          @@config
        end
        alias configuration config
32

33 34
        def gems_dependencies_loaded
          config.gems_dependencies_loaded
35
        end
36

37 38 39
        def plugin_loader
          @plugin_loader ||= configuration.plugin_loader.new(self)
        end
40
      end
41

42 43
      def gems_dependencies_loaded
        self.class.gems_dependencies_loaded
44
      end
45 46 47 48

      def plugin_loader
        self.class.plugin_loader
      end
49 50
    end

51
    class Runner
52

53
      attr_reader :names, :initializers
54 55
      attr_accessor :config
      alias configuration config
56

57 58 59
      def initialize(parent = nil)
        @names        = parent ? parent.names.dup        : {}
        @initializers = parent ? parent.initializers.dup : []
60
      end
61

62 63 64 65 66 67 68
      def add(name, options = {}, &block)
        # If :before or :after is specified, set the index to the right spot
        if other = options[:before] || options[:after]
          raise Error, "The #{other.inspect} initializer does not exist" unless @names[other]
          index = @initializers.index(@names[other])
          index += 1 if options[:after]
        end
69

70 71 72
        @initializers.insert(index || -1, block)
        @names[name] = block
      end
73

74 75 76 77
      def delete(name)
        @names[name].tap do |initializer|
          @initializers.delete(initializer)
          @names.delete(name)
78 79 80
        end
      end

81 82 83 84 85
      def run_initializer(initializer)
        init_block = initializer.is_a?(Proc) ? initializer : @names[initializer]
        container = Class.new(Base, &init_block).new
        container.run if container.respond_to?(:run)
      end
86

87
      def run(initializer = nil)
88
        Rails.configuration = Base.config = @config
89

90 91 92 93
        if initializer
          run_initializer(initializer)
        else
          @initializers.each {|block| run_initializer(block) }
94 95
        end
      end
96 97
    end

98 99
    def self.default
      @default ||= Runner.new
100 101
    end

102 103
    def self.run(initializer = nil, config = nil)
      default.config = config if config
104
      default.config ||= Configuration.new
105
      yield default.config if block_given?
106
      default.run(initializer)
J
Jamis Buck 已提交
107
    end
108
  end
J
Jamis Buck 已提交
109

110 111 112 113 114
  # Check for valid Ruby version (1.8.2 or 1.8.4 or higher). This is done in an
  # external file, so we can use it from the `rails` program as well without duplication.
  Initializer.default.add :check_ruby_version do
    require 'ruby_version_check'
  end
J
Joshua Peek 已提交
115

116 117 118 119 120 121 122
  # If Rails is vendored and RubyGems is available, install stub GemSpecs
  # for Rails, Active Support, Active Record, Action Pack, Action Mailer, and
  # Active Resource. This allows Gem plugins to depend on Rails even when
  # the Gem version of Rails shouldn't be loaded.
  Initializer.default.add :install_gem_spec_stubs do
    unless Rails.respond_to?(:vendor_rails?)
      abort %{Your config/boot.rb is outdated: Run "rake rails:update".}
123
    end
124

125 126
    if Rails.vendor_rails?
      begin; require "rubygems"; rescue LoadError; return; end
127

128 129
      %w(rails activesupport activerecord actionpack actionmailer activeresource).each do |stub|
        Gem.loaded_specs[stub] ||= Gem::Specification.new do |s|
130 131 132
          s.name = stub
          s.version = Rails::VERSION::STRING
          s.loaded_from = ""
133
        end
134 135
      end
    end
136
  end
137

138 139 140
  # Set the <tt>$LOAD_PATH</tt> based on the value of
  # Configuration#load_paths. Duplicates are removed.
  Initializer.default.add :set_load_path do
141 142 143
    # TODO: Think about unifying this with the general Rails paths
    configuration.framework_paths.reverse_each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
    configuration.paths.add_to_load_path
144 145
    $LOAD_PATH.uniq!
  end
J
Joshua Peek 已提交
146

147 148 149
  Initializer.default.add :add_gem_load_paths do
    require 'rails/gem_dependency'
    Rails::GemDependency.add_frozen_gem_path
150
    unless config.gems.empty?
151
      require "rubygems"
152
      config.gems.each { |gem| gem.add_load_paths }
153
    end
154
  end
155

156 157 158 159 160
  # Requires all frameworks specified by the Configuration#frameworks
  # list. By default, all frameworks (Active Record, Active Support,
  # Action Pack, Action Mailer, and Active Resource) are loaded.
  Initializer.default.add :require_frameworks do
    begin
161 162
      require 'active_support'
      require 'active_support/core_ext/kernel/reporting'
163 164 165 166
      require 'active_support/core_ext/logger'

      # TODO: This is here to make Sam Ruby's tests pass. Needs discussion.
      require 'active_support/core_ext/numeric/bytes'
167 168 169 170
      configuration.frameworks.each { |framework| require(framework.to_s) }
    rescue LoadError => e
      # Re-raise as RuntimeError because Mongrel would swallow LoadError.
      raise e.to_s
171
    end
172
  end
173

174 175 176 177 178 179
  # Set the paths from which Rails will automatically load source files, and
  # the load_once paths.
  Initializer.default.add :set_autoload_paths do
    require 'active_support/dependencies'
    ActiveSupport::Dependencies.load_paths = configuration.load_paths.uniq
    ActiveSupport::Dependencies.load_once_paths = configuration.load_once_paths.uniq
180

181 182 183 184 185 186
    extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths
    unless extra.empty?
      abort <<-end_error
        load_once_paths must be a subset of the load_paths.
        Extra items in load_once_paths: #{extra * ','}
      end_error
187
    end
188

189 190 191
    # Freeze the arrays so future modifications will fail rather than do nothing mysteriously
    configuration.load_once_paths.freeze
  end
192

193 194 195 196 197 198
  # Adds all load paths from plugins to the global set of load paths, so that
  # code from plugins can be required (explicitly or automatically via ActiveSupport::Dependencies).
  Initializer.default.add :add_plugin_load_paths do
    require 'active_support/dependencies'
    plugin_loader.add_plugin_load_paths
  end
199

200 201 202 203
  # Loads the environment specified by Configuration#environment_path, which
  # is typically one of development, test, or production.
  Initializer.default.add :load_environment do
    silence_warnings do
204
      next if @environment_loaded
205 206
      next unless File.file?(configuration.environment_path)

207
      @environment_loaded = true
208

209 210
      config = configuration
      constants = self.class.constants
211

212
      eval(IO.read(configuration.environment_path), binding, configuration.environment_path)
213

214 215
      (self.class.constants - constants).each do |const|
        Object.const_set(const, self.class.const_get(const))
216 217
      end
    end
218
  end
219

220 221 222 223 224 225 226 227 228
  # Preload all frameworks specified by the Configuration#frameworks.
  # Used by Passenger to ensure everything's loaded before forking and
  # to avoid autoload race conditions in JRuby.
  Initializer.default.add :preload_frameworks do
    if configuration.preload_frameworks
      configuration.frameworks.each do |framework|
        # String#classify and #constantize aren't available yet.
        toplevel = Object.const_get(framework.to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
        toplevel.load_all! if toplevel.respond_to?(:load_all!)
229 230
      end
    end
231
  end
232

233 234 235 236 237 238 239 240 241 242 243 244 245 246
  # For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the
  # multibyte safe operations. Plugin authors supporting other encodings
  # should override this behaviour and set the relevant +default_charset+
  # on ActionController::Base.
  #
  # For Ruby 1.9, UTF-8 is the default internal and external encoding.
  Initializer.default.add :initialize_encoding do
    if RUBY_VERSION < '1.9'
      $KCODE='u'
    else
      Encoding.default_internal = Encoding::UTF_8
      Encoding.default_external = Encoding::UTF_8
    end
  end
247

248 249 250 251 252 253 254 255
  # This initialization routine does nothing unless <tt>:active_record</tt>
  # is one of the frameworks to load (Configuration#frameworks). If it is,
  # this sets the database configuration from Configuration#database_configuration
  # and then establishes the connection.
  Initializer.default.add :initialize_database do
    if configuration.frameworks.include?(:active_record)
      ActiveRecord::Base.configurations = configuration.database_configuration
      ActiveRecord::Base.establish_connection
J
Joshua Peek 已提交
256
    end
257
  end
J
Joshua Peek 已提交
258

259 260 261
  Initializer.default.add :initialize_cache do
    unless defined?(RAILS_CACHE)
      silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(configuration.cache_store) }
262

263 264 265
      if RAILS_CACHE.respond_to?(:middleware)
        # Insert middleware to setup and teardown local cache for each request
        configuration.middleware.insert_after(:"Rack::Lock", RAILS_CACHE.middleware)
266
      end
267
    end
268
  end
269

270 271 272
  Initializer.default.add :initialize_framework_caches do
    if configuration.frameworks.include?(:action_controller)
      ActionController::Base.cache_store ||= RAILS_CACHE
273
    end
274
  end
275

276 277 278 279 280 281 282 283
  Initializer.default.add :initialize_logger do
    # if the environment has explicitly defined a logger, use it
    next if Rails.logger

    unless logger = configuration.logger
      begin
        logger = ActiveSupport::BufferedLogger.new(configuration.log_path)
        logger.level = ActiveSupport::BufferedLogger.const_get(configuration.log_level.to_s.upcase)
284
        if RAILS_ENV == "production"
285
          logger.auto_flushing = false
286
        end
287 288 289 290 291 292 293
      rescue StandardError => e
        logger = ActiveSupport::BufferedLogger.new(STDERR)
        logger.level = ActiveSupport::BufferedLogger::WARN
        logger.warn(
          "Rails Error: Unable to access log file. Please ensure that #{configuration.log_path} exists and is chmod 0666. " +
          "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
        )
294 295
      end
    end
296

297
    # TODO: Why are we silencing warning here?
298
    silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
299
  end
300

301 302 303 304 305 306 307 308
  # Sets the logger for Active Record, Action Controller, and Action Mailer
  # (but only for those frameworks that are to be loaded). If the framework's
  # logger is already set, it is not changed, otherwise it is set to use
  # RAILS_DEFAULT_LOGGER.
  Initializer.default.add :initialize_framework_logging do
    for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks)
      framework.to_s.camelize.constantize.const_get("Base").logger ||= Rails.logger
    end
309

310 311 312
    ActiveSupport::Dependencies.logger ||= Rails.logger
    Rails.cache.logger ||= Rails.logger
  end
313

314 315 316
  # Sets the dependency loading mechanism based on the value of
  # Configuration#cache_classes.
  Initializer.default.add :initialize_dependency_mechanism do
317
    # TODO: Remove files from the $" and always use require
318 319
    ActiveSupport::Dependencies.mechanism = configuration.cache_classes ? :require : :load
  end
320

321 322 323 324 325
  # Loads support for "whiny nil" (noisy warnings when methods are invoked
  # on +nil+ values) if Configuration#whiny_nils is true.
  Initializer.default.add :initialize_whiny_nils do
    require('active_support/whiny_nil') if configuration.whiny_nils
  end
326

327

328 329 330 331 332
  # Sets the default value for Time.zone, and turns on ActiveRecord::Base#time_zone_aware_attributes.
  # If assigned value cannot be matched to a TimeZone, an exception will be raised.
  Initializer.default.add :initialize_time_zone do
    if configuration.time_zone
      zone_default = Time.__send__(:get_zone, configuration.time_zone)
333

334 335 336 337 338
      unless zone_default
        raise \
          'Value assigned to config.time_zone not recognized.' +
          'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
      end
339

340
      Time.zone_default = zone_default
341

342 343 344 345
      if configuration.frameworks.include?(:active_record)
        ActiveRecord::Base.time_zone_aware_attributes = true
        ActiveRecord::Base.default_timezone = :utc
      end
346
    end
347
  end
J
Joshua Peek 已提交
348

349 350 351 352 353 354 355 356 357
  # Set the i18n configuration from config.i18n but special-case for the load_path which should be
  # appended to what's already set instead of overwritten.
  Initializer.default.add :initialize_i18n do
    configuration.i18n.each do |setting, value|
      if setting == :load_path
        I18n.load_path += value
      else
        I18n.send("#{setting}=", value)
      end
358
    end
359
  end
360

361 362 363 364 365 366
  # Initializes framework-specific settings for each of the loaded frameworks
  # (Configuration#frameworks). The available settings map to the accessors
  # on each of the corresponding Base classes.
  Initializer.default.add :initialize_framework_settings do
    configuration.frameworks.each do |framework|
      base_class = framework.to_s.camelize.constantize.const_get("Base")
367

368 369
      configuration.send(framework).each do |setting, value|
        base_class.send("#{setting}=", value)
370
      end
371
    end
372 373
    configuration.active_support.each do |setting, value|
      ActiveSupport.send("#{setting}=", value)
374
    end
375
  end
376

377 378 379 380 381 382 383 384 385
  # Sets +ActionController::Base#view_paths+ and +ActionMailer::Base#template_root+
  # (but only for those frameworks that are to be loaded). If the framework's
  # paths have already been set, it is not changed, otherwise it is
  # set to use Configuration#view_path.
  Initializer.default.add :initialize_framework_views do
    if configuration.frameworks.include?(:action_view)
      view_path = ActionView::PathSet.type_cast(configuration.view_path)
      ActionMailer::Base.template_root  = view_path if configuration.frameworks.include?(:action_mailer) && ActionMailer::Base.view_paths.blank?
      ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller) && ActionController::Base.view_paths.blank?
386
    end
387
  end
388

389
  Initializer.default.add :initialize_metal do
390 391 392 393 394 395 396 397 398
    # TODO: Make Rails and metal work without ActionController
    if defined?(ActionController)
      Rails::Rack::Metal.requested_metals = configuration.metals
      Rails::Rack::Metal.metal_paths += plugin_loader.engine_metal_paths

      configuration.middleware.insert_before(
        :"ActionDispatch::ParamsParser",
        Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?)
    end
399
  end
J
Jamis Buck 已提交
400

401
  Initializer.default.add :check_for_unbuilt_gems do
402
    unbuilt_gems = config.gems.select {|gem| gem.frozen? && !gem.built? }
403 404 405 406 407 408
    if unbuilt_gems.size > 0
      # don't print if the gems:build rake tasks are being run
      unless $gems_build_rake_task
        abort <<-end_error
The following gems have native components that need to be built
#{unbuilt_gems.map { |gemm| "#{gemm.name}  #{gemm.requirement}" } * "\n  "}
409

410 411 412
You're running:
ruby #{Gem.ruby_version} at #{Gem.ruby}
rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '}
413

414 415
Run `rake gems:build` to build the unbuilt gems.
        end_error
416
      end
N
Nicholas Seckar 已提交
417
    end
418
  end
419

420 421
  Initializer.default.add :load_gems do
    unless $gems_rake_task
422
      config.gems.each { |gem| gem.load }
423
    end
424
  end
425

426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
  # Loads all plugins in <tt>config.plugin_paths</tt>.  <tt>plugin_paths</tt>
  # defaults to <tt>vendor/plugins</tt> but may also be set to a list of
  # paths, such as
  #   config.plugin_paths = ["#{RAILS_ROOT}/lib/plugins", "#{RAILS_ROOT}/vendor/plugins"]
  #
  # In the default implementation, as each plugin discovered in <tt>plugin_paths</tt> is initialized:
  # * its +lib+ directory, if present, is added to the load path (immediately after the applications lib directory)
  # * <tt>init.rb</tt> is evaluated, if present
  #
  # After all plugins are loaded, duplicates are removed from the load path.
  # If an array of plugin names is specified in config.plugins, only those plugins will be loaded
  # and they plugins will be loaded in that order. Otherwise, plugins are loaded in alphabetical
  # order.
  #
  # if config.plugins ends contains :all then the named plugins will be loaded in the given order and all other
  # plugins will be loaded in alphabetical order
  Initializer.default.add :load_plugins do
    plugin_loader.load_plugins
  end
J
Joshua Peek 已提交
445

446 447 448 449
  #
  # # pick up any gems that plugins depend on
  Initializer.default.add :add_gem_load_paths do
    require 'rails/gem_dependency'
450
    # TODO: This seems extraneous
451
    Rails::GemDependency.add_frozen_gem_path
452
    unless config.gems.empty?
453
      require "rubygems"
454
      config.gems.each { |gem| gem.add_load_paths }
455
    end
456
  end
457

458 459
  # TODO: Figure out if this needs to run a second time
  # load_gems
460

461
  Initializer.default.add :check_gem_dependencies do
462
    unloaded_gems = config.gems.reject { |g| g.loaded? }
463 464 465 466 467 468 469
    if unloaded_gems.size > 0
      configuration.gems_dependencies_loaded = false
      # don't print if the gems rake tasks are being run
      unless $gems_rake_task
        abort <<-end_error
Missing these required gems:
#{unloaded_gems.map { |gemm| "#{gemm.name}  #{gemm.requirement}" } * "\n  "}
470

471 472 473
You're running:
ruby #{Gem.ruby_version} at #{Gem.ruby}
rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '}
474

475 476
Run `rake gems:install` to install the missing gems.
        end_error
477
      end
478 479 480 481
    else
      configuration.gems_dependencies_loaded = true
    end
  end
482

483 484 485
  # # bail out if gems are missing - note that check_gem_dependencies will have
  # # already called abort() unless $gems_rake_task is set
  # return unless gems_dependencies_loaded
486

487 488 489 490
  Initializer.default.add :load_application_initializers do
    if gems_dependencies_loaded
      Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer|
        load(initializer)
491
      end
492 493
    end
  end
J
Joshua Peek 已提交
494

495 496 497 498 499
  # Fires the user-supplied after_initialize block (Configuration#after_initialize)
  Initializer.default.add :after_initialize do
    if gems_dependencies_loaded
      configuration.after_initialize_blocks.each do |block|
        block.call
500
      end
501 502
    end
  end
J
Joshua Peek 已提交
503

504 505 506 507 508 509 510 511 512 513
  # # Setup database middleware after initializers have run
  Initializer.default.add :initialize_database_middleware do
    if configuration.frameworks.include?(:active_record)
      if configuration.frameworks.include?(:action_controller) &&
          ActionController::Base.session_store.name == 'ActiveRecord::SessionStore'
        configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::ConnectionAdapters::ConnectionManagement
        configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::QueryCache
      else
        configuration.middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement
        configuration.middleware.use ActiveRecord::QueryCache
514
      end
515 516
    end
  end
517

518
  # TODO: Make a DSL way to limit an initializer to a particular framework
519

520 521
  # # Prepare dispatcher callbacks and run 'prepare' callbacks
  Initializer.default.add :prepare_dispatcher do
522
    next unless configuration.frameworks.include?(:action_controller)
523 524 525
    require 'dispatcher' unless defined?(::Dispatcher)
    Dispatcher.define_dispatcher_callbacks(configuration.cache_classes)
  end
526

527 528 529 530 531 532
  # Routing must be initialized after plugins to allow the former to extend the routes
  # ---
  # If Action Controller is not one of the loaded frameworks (Configuration#frameworks)
  # this does nothing. Otherwise, it loads the routing definitions and sets up
  # loading module used to lazily load controllers (Configuration#controller_paths).
  Initializer.default.add :initialize_routing do
533
    next unless configuration.frameworks.include?(:action_controller)
534 535 536 537

    ActionController::Routing.controller_paths += configuration.controller_paths
    ActionController::Routing::Routes.add_configuration_file(configuration.routes_configuration_file)
    ActionController::Routing::Routes.reload!
538
  end
539 540 541 542 543
  #
  # # Observers are loaded after plugins in case Observers or observed models are modified by plugins.
  Initializer.default.add :load_observers do
    if gems_dependencies_loaded && configuration.frameworks.include?(:active_record)
      ActiveRecord::Base.instantiate_observers
544 545 546
    end
  end

547 548
  # Eager load application classes
  Initializer.default.add :load_application_classes do
549
    next if $rails_rake_task
550

551 552 553 554 555 556 557
    if configuration.cache_classes
      configuration.eager_load_paths.each do |load_path|
        matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/
        Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
          require_dependency file.sub(matcher, '\1')
        end
      end
558 559
    end
  end
560

561 562 563 564
  # Disable dependency loading during request cycle
  Initializer.default.add :disable_dependency_loading do
    if configuration.cache_classes && !configuration.dependency_loading
      ActiveSupport::Dependencies.unhook!
565
    end
566
  end
567
end