aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/initializer.rb
blob: cd23158e98767d7f3e23eba09879f1b8c12e7e95 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
require "pathname"

$LOAD_PATH.unshift File.dirname(__FILE__)
require 'railties_path'
require 'rails/version'
require 'rails/gem_dependency'
require 'rails/rack'
require 'rails/paths'
require 'rails/core'
require 'rails/configuration'

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

module Rails
  class Initializer
    class Error < StandardError ; end

    class Base
      class << self
        def run(&blk)
          define_method(:run, &blk)
        end

        def config=(config)
          @@config = config
        end

        def config
          @@config
        end
        alias configuration config

        def gems_dependencies_loaded
          config.gems_dependencies_loaded
        end

        def plugin_loader
          @plugin_loader ||= configuration.plugin_loader.new(self)
        end
      end

      def gems_dependencies_loaded
        self.class.gems_dependencies_loaded
      end

      def plugin_loader
        self.class.plugin_loader
      end
    end

    class Runner

      attr_reader :names, :initializers
      attr_accessor :config
      alias configuration config

      def initialize(parent = nil)
        @names        = parent ? parent.names.dup        : {}
        @initializers = parent ? parent.initializers.dup : []
      end

      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

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

      def delete(name)
        @names[name].tap do |initializer|
          @initializers.delete(initializer)
          @names.delete(name)
        end
      end

      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

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

        if initializer
          run_initializer(initializer)
        else
          @initializers.each {|block| run_initializer(block) }
        end
      end
    end

    def self.default
      @default ||= Runner.new
    end

    def self.run(initializer = nil, config = nil)
      default.config = config if config
      default.config ||= Configuration.new
      yield default.config if block_given?
      default.run(initializer)
    end
  end

  # 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

  # 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".}
    end

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

      stubs = %w(rails activesupport activerecord actionpack actionmailer activeresource)
      stubs.reject! { |s| Gem.loaded_specs.key?(s) }

      stubs.each do |stub|
        Gem.loaded_specs[stub] = Gem::Specification.new do |s|
          s.name = stub
          s.version = Rails::VERSION::STRING
          s.loaded_from = ""
        end
      end
    end
  end

  # Set the <tt>$LOAD_PATH</tt> based on the value of
  # Configuration#load_paths. Duplicates are removed.
  Initializer.default.add :set_load_path do
    # 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
    $LOAD_PATH.uniq!
  end

  Initializer.default.add :add_gem_load_paths do
    require 'rails/gem_dependency'
    Rails::GemDependency.add_frozen_gem_path
    unless config.gems.empty?
      require "rubygems"
      config.gems.each { |gem| gem.add_load_paths }
    end
  end

  # 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
      require 'active_support'
      require 'active_support/core_ext/kernel/reporting'
      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'
      configuration.frameworks.each { |framework| require(framework.to_s) }
    rescue LoadError => e
      # Re-raise as RuntimeError because Mongrel would swallow LoadError.
      raise e.to_s
    end
  end

  # 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

    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
    end

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

  # 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

  # 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
      next if @environment_loaded
      next unless File.file?(configuration.environment_path)

      @environment_loaded = true

      config = configuration
      constants = self.class.constants

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

      (self.class.constants - constants).each do |const|
        Object.const_set(const, self.class.const_get(const))
      end
    end
  end

  # 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!)
      end
    end
  end

  # 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

  # 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
    end
  end

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

      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)
      end
    end
  end

  Initializer.default.add :initialize_framework_caches do
    if configuration.frameworks.include?(:action_controller)
      ActionController::Base.cache_store ||= RAILS_CACHE
    end
  end

  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)
        if RAILS_ENV == "production"
          logger.auto_flushing = false
        end
      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."
        )
      end
    end

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

  # 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

    ActiveSupport::Dependencies.logger ||= Rails.logger
    Rails.cache.logger ||= Rails.logger
  end

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

  # 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


  # 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)

      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

      Time.zone_default = zone_default

      if configuration.frameworks.include?(:active_record)
        ActiveRecord::Base.time_zone_aware_attributes = true
        ActiveRecord::Base.default_timezone = :utc
      end
    end
  end

  # 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
    end
  end

  # 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")

      configuration.send(framework).each do |setting, value|
        base_class.send("#{setting}=", value)
      end
    end
    configuration.active_support.each do |setting, value|
      ActiveSupport.send("#{setting}=", value)
    end
  end

  # 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?
    end
  end

  Initializer.default.add :initialize_metal do
    # 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
  end

  Initializer.default.add :check_for_unbuilt_gems do
    unbuilt_gems = config.gems.select {|gem| gem.frozen? && !gem.built? }
    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  "}

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

Run `rake gems:build` to build the unbuilt gems.
        end_error
      end
    end
  end

  Initializer.default.add :load_gems do
    unless $gems_rake_task
      config.gems.each { |gem| gem.load }
    end
  end

  # 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

  #
  # # pick up any gems that plugins depend on
  Initializer.default.add :add_gem_load_paths do
    require 'rails/gem_dependency'
    # TODO: This seems extraneous
    Rails::GemDependency.add_frozen_gem_path
    unless config.gems.empty?
      require "rubygems"
      config.gems.each { |gem| gem.add_load_paths }
    end
  end

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

  Initializer.default.add :check_gem_dependencies do
    unloaded_gems = config.gems.reject { |g| g.loaded? }
    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  "}

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

Run `rake gems:install` to install the missing gems.
        end_error
      end
    else
      configuration.gems_dependencies_loaded = true
    end
  end

  # # 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

  Initializer.default.add :load_application_initializers do
    if gems_dependencies_loaded
      Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer|
        load(initializer)
      end
    end
  end

  # 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
      end
    end
  end

  # # 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
      end
    end
  end

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

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

  # 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
    next unless configuration.frameworks.include?(:action_controller)

    ActionController::Routing.controller_paths += configuration.controller_paths
    ActionController::Routing::Routes.add_configuration_file(configuration.routes_configuration_file)
    ActionController::Routing::Routes.reload!
  end
  #
  # # 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
    end
  end

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

    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
    end
  end

  # Disable dependency loading during request cycle
  Initializer.default.add :disable_dependency_loading do
    if configuration.cache_classes && !configuration.dependency_loading
      ActiveSupport::Dependencies.unhook!
    end
  end
end