initializer.rb 21.8 KB
Newer Older
1
require 'logger'
2
require 'set'
3
require File.join(File.dirname(__FILE__), 'railties_path')
4

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

module Rails
8 9 10 11
  # The Initializer is responsible for processing the Rails configuration, such
  # as setting the $LOAD_PATH, requiring the right frameworks, initializing
  # logging, and more. It can be run either as a single command that'll just
  # use the default configuration, like this:
12 13 14
  #
  #   Rails::Initializer.run
  #
15 16
  # But normally it's more interesting to pass in a custom configuration
  # through the block running:
17 18 19 20 21
  #
  #   Rails::Initializer.run do |config|
  #     config.frameworks -= [ :action_web_service ]
  #   end
  #
22 23
  # This will use the default configuration options from Rails::Configuration,
  # but allow for overwriting on select areas.
24
  class Initializer
25
    # The Configuration instance used by this Initializer instance.
26
    attr_reader :configuration
27 28 29 30

    # The set of loaded plugins.
    attr_reader :loaded_plugins

31 32 33 34 35 36 37 38
    # Runs the initializer. By default, this will invoke the #process method,
    # which simply executes all of the initialization routines. Alternately,
    # you can specify explicitly which initialization routine you want:
    #
    #   Rails::Initializer.run(:set_load_path)
    #
    # This is useful if you only want the load path initialized, without
    # incuring the overhead of completely loading the entire environment. 
39 40
    def self.run(command = :process, configuration = Configuration.new)
      yield configuration if block_given?
41 42 43
      initializer = new configuration
      initializer.send(command)
      initializer
44 45
    end
    
46 47
    # Create a new Initializer instance that references the given Configuration
    # instance.
48 49
    def initialize(configuration)
      @configuration = configuration
50
      @loaded_plugins = Set.new
51
    end
52

53 54 55 56 57 58 59 60 61 62 63 64 65 66
    # Sequentially step through all of the available initialization routines,
    # in order:
    #
    # * #set_load_path
    # * #set_connection_adapters
    # * #require_frameworks
    # * #load_environment
    # * #initialize_database
    # * #initialize_logger
    # * #initialize_framework_logging
    # * #initialize_framework_views
    # * #initialize_dependency_mechanism
    # * #initialize_breakpoints
    # * #initialize_whiny_nils
67
    # * #initialize_framework_settings
68 69
    # * #load_environment
    # * #load_plugins
70
    # * #initialize_routing
71 72 73 74 75
    #
    # (Note that #load_environment is invoked twice, once at the start and
    # once at the end, to support the legacy configuration style where the
    # environment could overwrite the defaults directly, instead of via the
    # Configuration instance. 
76 77
    def process
      set_load_path
78
      set_connection_adapters
79 80

      require_frameworks
81
      load_environment
82 83 84 85

      initialize_database
      initialize_logger
      initialize_framework_logging
86
      initialize_framework_views
87 88 89
      initialize_dependency_mechanism
      initialize_breakpoints
      initialize_whiny_nils
90 91
      initialize_temporary_directories

92
      initialize_framework_settings
93 94 95 96 97
      
      # Support for legacy configuration style where the environment
      # could overwrite anything set from the defaults/global through
      # the individual base class configurations.
      load_environment
98 99
      
      load_framework_info
J
Jamis Buck 已提交
100 101

      load_plugins
102 103 104

      # Routing must be initialized after plugins to allow the former to extend the routes
      initialize_routing
105 106 107
      
      # the framework is now fully initialized
      after_initialize
108 109
    end
    
110 111
    # Set the <tt>$LOAD_PATH</tt> based on the value of
    # Configuration#load_paths. Duplicates are removed.
112 113 114 115 116
    def set_load_path
      configuration.load_paths.reverse.each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
      $LOAD_PATH.uniq!
    end
    
117 118 119 120
    # Sets the +RAILS_CONNECTION_ADAPTERS+ constant based on the value of
    # Configuration#connection_adapters. This constant is used to determine
    # which database adapters should be loaded (by default, all adapters are
    # loaded).
121
    def set_connection_adapters
122
      Object.const_set("RAILS_CONNECTION_ADAPTERS", configuration.connection_adapters) if configuration.connection_adapters
123 124
    end
    
125 126 127
    # Requires all frameworks specified by the Configuration#frameworks
    # list. By default, all frameworks (ActiveRecord, ActiveSupport,
    # ActionPack, ActionMailer, and ActionWebService) are loaded.
128 129 130 131
    def require_frameworks
      configuration.frameworks.each { |framework| require(framework.to_s) }
    end
    
132
    # Loads Rails::VERSION and Rails::Info.
133 134 135 136
    # TODO: Make this work via dependencies.rb/const_missing instead.
    def load_framework_info
      require 'rails_info'
    end
137 138 139 140 141

    # 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 = ['lib/plugins', 'vendor/plugins']
142
    #
143 144 145
    # Each plugin discovered in <tt>plugin_paths</tt> is initialized:
    # * add its +lib+ directory, if present, to the beginning of the load path
    # * evaluate <tt>init.rb</tt> if present
146 147
    #
    # After all plugins are loaded, duplicates are removed from the load path.
148
    # Plugins are loaded in alphabetical order.
J
Jamis Buck 已提交
149
    def load_plugins
150
      find_plugins(configuration.plugin_paths).sort.each { |path| load_plugin path }
J
Jamis Buck 已提交
151 152 153
      $LOAD_PATH.uniq!
    end

154 155
    # Loads the environment specified by Configuration#environment_path, which
    # is typically one of development, testing, or production.
156 157 158
    def load_environment
      silence_warnings do
        config = configuration
159
        constants = self.class.constants
160
        eval(IO.read(configuration.environment_path), binding)
161 162 163
        (self.class.constants - constants).each do |const|
          Object.const_set(const, self.class.const_get(const))
        end
164
      end
165 166
    end
    
167 168 169 170
    # 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.
171 172 173 174 175 176
    def initialize_database
      return unless configuration.frameworks.include?(:active_record)
      ActiveRecord::Base.configurations = configuration.database_configuration
      ActiveRecord::Base.establish_connection
    end
    
177 178 179 180 181 182 183 184
    # If the +RAILS_DEFAULT_LOGGER+ constant is already set, this initialization
    # routine does nothing. If the constant is not set, and Configuration#logger
    # is not +nil+, this also does nothing. Otherwise, a new logger instance
    # is created at Configuration#log_path, with a default log level of
    # Configuration#log_level.
    #
    # If the log could not be created, the log will be set to output to
    # +STDERR+, with a log level of +WARN+.
185
    def initialize_logger
186 187 188
      # if the environment has explicitly defined a logger, use it
      return if defined?(RAILS_DEFAULT_LOGGER)

189 190 191 192 193 194 195 196 197 198 199 200
      unless logger = configuration.logger
        begin
          logger = Logger.new(configuration.log_path)
          logger.level = Logger.const_get(configuration.log_level.to_s.upcase)
        rescue StandardError
          logger = Logger.new(STDERR)
          logger.level = Logger::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
201 202
      end
      
203
      silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
204 205
    end
    
206 207 208 209
    # Sets the logger for ActiveRecord, ActionController, and ActionMailer
    # (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+.
210
    def initialize_framework_logging
211 212 213
      for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks)
        framework.to_s.camelize.constantize.const_get("Base").logger ||= RAILS_DEFAULT_LOGGER
      end
214 215
    end
    
216 217 218 219
    # Sets the +template_root+ for ActionController::Base and ActionMailer::Base
    # (but only for those frameworks that are to be loaded). If the framework's
    # +template_root+ has already been set, it is not changed, otherwise it is
    # set to use Configuration#view_path.
220
    def initialize_framework_views
221 222 223
      for framework in ([ :action_controller, :action_mailer ] & configuration.frameworks)
        framework.to_s.camelize.constantize.const_get("Base").template_root ||= configuration.view_path
      end
224 225
    end

226 227 228
    # If ActionController 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).
229 230 231 232
    def initialize_routing
      return unless configuration.frameworks.include?(:action_controller)
      ActionController::Routing::Routes.reload
    end
233
    
234 235
    # Sets the dependency loading mechanism based on the value of
    # Configuration#cache_classes.
236 237
    def initialize_dependency_mechanism
      Dependencies.mechanism = configuration.cache_classes ? :require : :load
238
    end
239
    
240 241
    # Sets the +BREAKPOINT_SERVER_PORT+ if Configuration#breakpoint_server
    # is true.
242 243
    def initialize_breakpoints
      silence_warnings { Object.const_set("BREAKPOINT_SERVER_PORT", 42531) if configuration.breakpoint_server }
244
    end
245 246 247

    # Loads support for "whiny nil" (noisy warnings when methods are invoked
    # on +nil+ values) if Configuration#whiny_nils is true.
248 249 250
    def initialize_whiny_nils
      require('active_support/whiny_nil') if configuration.whiny_nils
    end
251

252 253 254 255 256 257 258 259 260 261 262 263
    def initialize_temporary_directories
      if configuration.frameworks.include?(:action_controller)
        session_path = "#{RAILS_ROOT}/tmp/sessions/"
        ActionController::Base.session_options[:tmpdir] = File.exist?(session_path) ? session_path : Dir::tmpdir
        
        cache_path = "#{RAILS_ROOT}/tmp/cache/"
        if File.exist?(cache_path)
          ActionController::Base.fragment_cache_store = :file_store, cache_path
        end
      end
    end

264
    # Initializes framework-specific settings for each of the loaded frameworks
265 266
    # (Configuration#frameworks). The available settings map to the accessors
    # on each of the corresponding Base classes.
267
    def initialize_framework_settings
268 269
      configuration.frameworks.each do |framework|
        base_class = framework.to_s.camelize.constantize.const_get("Base")
270

271 272 273
        configuration.send(framework).each do |setting, value|
          base_class.send("#{setting}=", value)
        end
274 275
      end
    end
276 277 278 279 280 281
    
    # Fires the user-supplied after_initialize block (Configuration#after_initialize) 
    def after_initialize
      configuration.after_initialize_block.call if configuration.after_initialize_block
    end
    
282 283 284 285 286

    protected
      # Return a list of plugin paths within base_path.  A plugin path is
      # a directory that contains either a lib directory or an init.rb file.
      # This recurses into directories which are not plugin paths, so you
287
      # may organize your plugins within the plugin path.
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
      def find_plugins(*base_paths)
        base_paths.flatten.inject([]) do |plugins, base_path|
          Dir.glob(File.join(base_path, '*')).each do |path|
            if plugin_path?(path)
              plugins << path
            elsif File.directory?(path)
              plugins += find_plugins(path)
            end
          end
          plugins
        end
      end

      def plugin_path?(path)
        File.directory?(path) and (File.directory?(File.join(path, 'lib')) or File.file?(File.join(path, 'init.rb')))
      end

      # Load the plugin at <tt>path</tt> unless already loaded.
      #
      # Each plugin is initialized:
      # * add its +lib+ directory, if present, to the beginning of the load path
      # * evaluate <tt>init.rb</tt> if present
      #
      # Returns <tt>true</tt> if the plugin is successfully loaded or
      # <tt>false</tt> if it is already loaded (similar to Kernel#require).
      # Raises <tt>LoadError</tt> if the plugin is not found.
314 315
      def load_plugin(directory)
        name = File.basename(directory)
316
        return false if loaded_plugins.include?(name)
317 318

        # Catch nonexistent and empty plugins.
319
        raise LoadError, "No such plugin: #{directory}" unless plugin_path?(directory)
320

321 322
        lib_path  = File.join(directory, 'lib')
        init_path = File.join(directory, 'init.rb')
323 324 325
        has_lib   = File.directory?(lib_path)
        has_init  = File.file?(init_path)

326 327 328 329 330 331 332
        # Add lib to load path *after* the application lib, to allow
        # application libraries to override plugin libraries.
        if has_lib
          application_lib_index = $LOAD_PATH.index(File.join(RAILS_ROOT, "lib")) || 0  
          $LOAD_PATH.insert(application_lib_index + 1, lib_path)
        end

333 334
        # Allow plugins to reference the current configuration object
        config = configuration 
335 336 337
	
        # Add to set of loaded plugins before 'name' collapsed in eval.
        loaded_plugins << name
338 339

        # Evaluate init.rb.
340
        silence_warnings { eval(IO.read(init_path), binding, init_path) } if has_init
341 342 343

        true
      end
344
  end
345

346 347 348 349 350 351
  # The Configuration class holds all the parameters for the Initializer and
  # ships with defaults that suites most Rails applications. But it's possible
  # to overwrite everything. Usually, you'll create an Configuration file
  # implicitly through the block running on the Initializer, but it's also
  # possible to create the Configuration instance in advance and pass it in
  # like this:
352 353 354
  #
  #   config = Rails::Configuration.new
  #   Rails::Initializer.run(:process, config)
355
  class Configuration
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
    # A stub for setting options on ActionController::Base
    attr_accessor :action_controller
    
    # A stub for setting options on ActionMailer::Base
    attr_accessor :action_mailer
    
    # A stub for setting options on ActionView::Base
    attr_accessor :action_view
    
    # A stub for setting options on ActionWebService::Base
    attr_accessor :action_web_service
    
    # A stub for setting options on ActiveRecord::Base
    attr_accessor :active_record
    
    # Whether or not to use the breakpoint server (boolean)
    attr_accessor :breakpoint_server
    
    # Whether or not classes should be cached (set to false if you want
    # application classes to be reloaded on each request)
    attr_accessor :cache_classes
    
    # The list of connection adapters to load. (By default, all connection
    # adapters are loaded. You can set this to be just the adapter(s) you
    # will use to reduce your application's load time.)
381
    attr_accessor :connection_adapters
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
    # The list of paths that should be searched for controllers. (Defaults
    # to <tt>app/controllers</tt> and <tt>components</tt>.)
    attr_accessor :controller_paths

    # The path to the database configuration file to use. (Defaults to
    # <tt>config/database.yml</tt>.)
    attr_accessor :database_configuration_file
    
    # The list of rails framework components that should be loaded. (Defaults
    # to <tt>:active_record</tt>, <tt>:action_controller</tt>,
    # <tt>:action_view</tt>, <tt>:action_mailer</tt>, and
    # <tt>:action_web_service</tt>).
    attr_accessor :frameworks
    
    # An array of additional paths to prepend to the load path. By default,
    # all +app+, +lib+, +vendor+ and mock paths are included in this list.
    attr_accessor :load_paths
    
    # The log level to use for the default Rails logger. In production mode,
    # this defaults to <tt>:info</tt>. In development mode, it defaults to
    # <tt>:debug</tt>.
    attr_accessor :log_level
    
    # The path to the log file to use. Defaults to log/#{environment}.log
    # (e.g. log/development.log or log/production.log).
    attr_accessor :log_path
    
    # The specific logger to use. By default, a logger will be created and
    # initialized using #log_path and #log_level, but a programmer may
    # specifically set the logger to use via this accessor and it will be
    # used directly.
    attr_accessor :logger
    
    # The root of the application's views. (Defaults to <tt>app/views</tt>.)
    attr_accessor :view_path
    
    # Set to +true+ if you want to be warned (noisily) when you try to invoke
    # any method of +nil+. Set to +false+ for the standard Ruby behavior.
    attr_accessor :whiny_nils
    
423 424 425 426
    # The path to the root of the plugins directory. By default, it is in
    # <tt>vendor/plugins</tt>.
    attr_accessor :plugin_paths

427 428
    # Create a new Configuration instance, initialized with the default
    # values.
429
    def initialize
430 431 432 433 434 435 436 437 438
      self.frameworks                   = default_frameworks
      self.load_paths                   = default_load_paths
      self.log_path                     = default_log_path
      self.log_level                    = default_log_level
      self.view_path                    = default_view_path
      self.controller_paths             = default_controller_paths
      self.cache_classes                = default_cache_classes
      self.breakpoint_server            = default_breakpoint_server
      self.whiny_nils                   = default_whiny_nils
439
      self.plugin_paths                 = default_plugin_paths
440
      self.database_configuration_file  = default_database_configuration_file
441

442
      for framework in default_frameworks
443
        self.send("#{framework}=", OrderedOptions.new)
444
      end
445 446
    end
    
447 448 449
    # Loads and returns the contents of the #database_configuration_file. The
    # contents of the file are processed via ERB before being sent through
    # YAML::load.
450
    def database_configuration
451
      YAML::load(ERB.new(IO.read(database_configuration_file)).result)
452 453
    end
    
454 455
    # The path to the current environment's file (development.rb, etc.). By
    # default the file is at <tt>config/environments/#{environment}.rb</tt>.
456
    def environment_path
457
      "#{root_path}/config/environments/#{environment}.rb"
458
    end
J
Jamis Buck 已提交
459

460 461
    # Return the currently selected environment. By default, it returns the
    # value of the +RAILS_ENV+ constant.
462 463 464
    def environment
      ::RAILS_ENV
    end
465 466 467 468 469 470 471 472 473 474 475 476 477
    
    # Sets a block which will be executed after rails has been fully initialized.
    # Useful for per-environment configuration which depends on the framework being 
    # fully initialized.
    def after_initialize(&after_initialize_block)
      @after_initialize_block = after_initialize_block
    end
    
    # Returns the block set in Configuration#after_initialize
    def after_initialize_block
      @after_initialize_block
    end
    
478
    private
479 480 481 482
      def root_path
        ::RAILS_ROOT
      end

483 484 485 486
      def framework_root_path
        defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root_path}/vendor/rails"
      end

487
      def default_frameworks
488
        [ :active_record, :action_controller, :action_view, :action_mailer, :action_web_service ]
489 490 491
      end
    
      def default_load_paths
492
        paths = ["#{root_path}/test/mocks/#{environment}"]
N
Nicholas Seckar 已提交
493 494 495
        
        # Add the app's controller directory
        paths.concat(Dir["#{root_path}/app/controllers/"])
496 497

        # Then model subdirectories.
498
        # TODO: Don't include .rb models as load paths
499 500
        paths.concat(Dir["#{root_path}/app/models/[_a-z]*"])
        paths.concat(Dir["#{root_path}/components/[_a-z]*"])
501 502 503 504 505

        # Followed by the standard includes.
        paths.concat %w(
          app 
          app/models 
506 507 508
          app/controllers
          app/helpers
          app/services
509 510 511 512 513
          app/apis 
          components 
          config 
          lib 
          vendor 
514
        ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
515 516 517 518 519 520 521 522 523 524 525

        # TODO: Don't include dirs for frameworks that are not used
        paths.concat %w(
          railties
          railties/lib
          actionpack/lib
          activesupport/lib
          activerecord/lib
          actionmailer/lib
          actionwebservice/lib
        ).map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
526 527 528
      end

      def default_log_path
529
        File.join(root_path, 'log', "#{environment}.log")
530 531 532
      end
      
      def default_log_level
533
        environment == 'production' ? :info : :debug
534 535 536
      end
      
      def default_database_configuration_file
537
        File.join(root_path, 'config', 'database.yml')
538 539 540
      end
      
      def default_view_path
541
        File.join(root_path, 'app', 'views')
542 543 544
      end
      
      def default_controller_paths
545
        [ File.join(root_path, 'app', 'controllers'), File.join(root_path, 'components'), File.join(RAILTIES_PATH, 'builtin', 'controllers') ]
546
      end
547
      
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
      def default_dependency_mechanism
        :load
      end
      
      def default_cache_classes
        false
      end
      
      def default_breakpoint_server
        false
      end
      
      def default_whiny_nils
        false
      end
563 564 565 566

      def default_plugin_paths
        ["#{root_path}/vendor/plugins"]
      end
567
  end
568
end
569

570 571
# Needs to be duplicated from Active Support since its needed before Active
# Support is available.
572 573
class OrderedHash < Array #:nodoc:
  def []=(key, value)    
574 575 576 577 578 579 580
    if pair = find_pair(key)
      pair.pop
      pair << value
    else
      self << [key, value]
    end
  end
581
  
582
  def [](key)
583
    pair = find_pair(key)
584 585 586
    pair ? pair.last : nil
  end

587 588
  def keys
    self.collect { |i| i.first }
589 590 591 592 593 594 595
  end

  private
    def find_pair(key)
      self.each { |i| return i if i.first == key }
      return false
    end
J
Jamis Buck 已提交
596
end
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614

class OrderedOptions < OrderedHash #:nodoc:
  def []=(key, value)
    super(key.to_sym, value)
  end
  
  def [](key)
    super(key.to_sym)
  end

  def method_missing(name, *args)
    if name.to_s =~ /(.*)=$/
      self[$1.to_sym] = args.first
    else
      self[name]
    end
  end
end