initializer.rb 17.4 KB
Newer Older
1 2
require 'logger'

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

module Rails
6 7 8 9
  # 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:
10 11 12
  #
  #   Rails::Initializer.run
  #
13 14
  # But normally it's more interesting to pass in a custom configuration
  # through the block running:
15 16 17 18 19
  #
  #   Rails::Initializer.run do |config|
  #     config.frameworks -= [ :action_web_service ]
  #   end
  #
20 21
  # This will use the default configuration options from Rails::Configuration,
  # but allow for overwriting on select areas.
22
  class Initializer
23
    # The Configuration instance used by this Initializer instance.
24 25
    attr_reader :configuration
    
26 27 28 29 30 31 32 33
    # 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. 
34 35 36 37 38
    def self.run(command = :process, configuration = Configuration.new)
      yield configuration if block_given?
      new(configuration).send(command)
    end
    
39 40
    # Create a new Initializer instance that references the given Configuration
    # instance.
41 42 43 44
    def initialize(configuration)
      @configuration = configuration
    end
    
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    # 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_routing
    # * #initialize_dependency_mechanism
    # * #initialize_breakpoints
    # * #initialize_whiny_nils
60
    # * #initialize_framework_settings
61 62 63 64 65 66 67
    # * #load_environment
    # * #load_plugins
    #
    # (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. 
68 69
    def process
      set_load_path
70
      set_connection_adapters
71 72

      require_frameworks
73
      load_environment
74 75 76 77

      initialize_database
      initialize_logger
      initialize_framework_logging
78
      initialize_framework_views
79
      initialize_routing
80 81 82 83
      initialize_dependency_mechanism
      initialize_breakpoints
      initialize_whiny_nils
      
84
      initialize_framework_settings
85 86 87 88 89
      
      # Support for legacy configuration style where the environment
      # could overwrite anything set from the defaults/global through
      # the individual base class configurations.
      load_environment
J
Jamis Buck 已提交
90 91

      load_plugins
92 93
    end
    
94 95
    # Set the <tt>$LOAD_PATH</tt> based on the value of
    # Configuration#load_paths. Duplicates are removed.
96 97 98 99 100
    def set_load_path
      configuration.load_paths.reverse.each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
      $LOAD_PATH.uniq!
    end
    
101 102 103 104
    # 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).
105
    def set_connection_adapters
106
      Object.const_set("RAILS_CONNECTION_ADAPTERS", configuration.connection_adapters) if configuration.connection_adapters
107 108
    end
    
109 110 111
    # Requires all frameworks specified by the Configuration#frameworks
    # list. By default, all frameworks (ActiveRecord, ActiveSupport,
    # ActionPack, ActionMailer, and ActionWebService) are loaded.
112 113 114 115
    def require_frameworks
      configuration.frameworks.each { |framework| require(framework.to_s) }
    end
    
116 117 118 119 120 121 122 123
    # Loads all plugins in the <tt>vendor/plugins</tt> directory. Each
    # subdirectory of <tt>vendor/plugins</tt> is inspected as follows:
    #
    # * if the directory has a +lib+ subdirectory, add it to the load path
    # * if the directory contains an <tt>init.rb</tt> file, read it in and
    #   eval it.
    #
    # After all plugins are loaded, duplicates are removed from the load path.
J
Jamis Buck 已提交
124 125 126 127 128 129
    def load_plugins
      config = configuration

      Dir.glob("#{configuration.plugins_path}/*") do |directory|
        next if File.basename(directory)[0] == ?. || !File.directory?(directory)

130 131 132 133
        if File.directory?("#{directory}/lib")
          $LOAD_PATH.unshift "#{directory}/lib"
        end

J
Jamis Buck 已提交
134 135 136 137 138 139 140 141 142 143
        if File.exist?("#{directory}/init.rb")
          silence_warnings do
            eval(IO.read("#{directory}/init.rb"), binding)
          end
        end
      end

      $LOAD_PATH.uniq!
    end

144 145
    # Loads the environment specified by Configuration#environment_path, which
    # is typically one of development, testing, or production.
146 147 148
    def load_environment
      silence_warnings do
        config = configuration
149
        constants = self.class.constants
150
        eval(IO.read(configuration.environment_path), binding)
151 152 153
        (self.class.constants - constants).each do |const|
          Object.const_set(const, self.class.const_get(const))
        end
154
      end
155 156
    end
    
157 158 159 160
    # 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.
161 162 163 164 165 166
    def initialize_database
      return unless configuration.frameworks.include?(:active_record)
      ActiveRecord::Base.configurations = configuration.database_configuration
      ActiveRecord::Base.establish_connection
    end
    
167 168 169 170 171 172 173 174
    # 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+.
175
    def initialize_logger
176 177 178
      # if the environment has explicitly defined a logger, use it
      return if defined?(RAILS_DEFAULT_LOGGER)

179 180 181 182 183 184 185 186 187 188 189 190
      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
191 192
      end
      
193
      silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
194 195
    end
    
196 197 198 199
    # 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+.
200
    def initialize_framework_logging
201 202 203
      for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks)
        framework.to_s.camelize.constantize.const_get("Base").logger ||= RAILS_DEFAULT_LOGGER
      end
204 205
    end
    
206 207 208 209
    # 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.
210
    def initialize_framework_views
211 212 213
      for framework in ([ :action_controller, :action_mailer ] & configuration.frameworks)
        framework.to_s.camelize.constantize.const_get("Base").template_root ||= configuration.view_path
      end
214 215
    end

216 217 218
    # 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).
219 220 221 222 223
    def initialize_routing
      return unless configuration.frameworks.include?(:action_controller)
      ActionController::Routing::Routes.reload
      Object.const_set "Controllers", Dependencies::LoadingModule.root(*configuration.controller_paths)
    end
224
    
225 226
    # Sets the dependency loading mechanism based on the value of
    # Configuration#cache_classes.
227 228
    def initialize_dependency_mechanism
      Dependencies.mechanism = configuration.cache_classes ? :require : :load
229
    end
230
    
231 232
    # Sets the +BREAKPOINT_SERVER_PORT+ if Configuration#breakpoint_server
    # is true.
233 234
    def initialize_breakpoints
      silence_warnings { Object.const_set("BREAKPOINT_SERVER_PORT", 42531) if configuration.breakpoint_server }
235
    end
236 237 238

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

243 244 245
    # Initialize 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.
246
    def initialize_framework_settings
247 248
      configuration.frameworks.each do |framework|
        base_class = framework.to_s.camelize.constantize.const_get("Base")
249

250 251 252
        configuration.send(framework).each do |setting, value|
          base_class.send("#{setting}=", value)
        end
253 254
      end
    end
255 256
  end
  
257 258 259 260 261 262
  # 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:
263 264 265
  #
  #   config = Rails::Configuration.new
  #   Rails::Initializer.run(:process, config)
266
  class Configuration
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
    # 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.)
292
    attr_accessor :connection_adapters
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
    # 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
    
    # Create a new Configuration instance, initialized with the default
    # values.
336
    def initialize
337 338 339 340 341 342 343 344 345
      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
346
      self.database_configuration_file  = default_database_configuration_file
347
      
348
      for framework in default_frameworks
349
        self.send("#{framework}=", OrderedOptions.new)
350
      end
351 352
    end
    
353 354 355
    # 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.
356
    def database_configuration
357
      YAML::load(ERB.new(IO.read(database_configuration_file)).result)
358 359
    end
    
360 361
    # The path to the current environment's file (development.rb, etc.). By
    # default the file is at <tt>config/environments/#{environment}.rb</tt>.
362 363
    def environment_path
      "#{RAILS_ROOT}/config/environments/#{environment}.rb"
364
    end
J
Jamis Buck 已提交
365

366 367
    # The path to the root of the plugins directory. By default, it is in
    # <tt>vendor/plugins</tt>.
J
Jamis Buck 已提交
368 369 370
    def plugins_path
      "#{RAILS_ROOT}/vendor/plugins"
    end
371
    
372 373
    # Return the currently selected environment. By default, it returns the
    # value of the +RAILS_ENV+ constant.
374 375 376
    def environment
      ::RAILS_ENV
    end
377

378 379
    private
      def default_frameworks
380
        [ :active_record, :action_controller, :action_view, :action_mailer, :action_web_service ]
381 382 383
      end
    
      def default_load_paths
D
David Heinemeier Hansson 已提交
384
        paths = ["#{RAILS_ROOT}/test/mocks/#{environment}"]
385 386

        # Then model subdirectories.
387
        # TODO: Don't include .rb models as load paths
388 389 390 391
        paths.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"])
        paths.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"])

        # Followed by the standard includes.
392
        # TODO: Don't include dirs for frameworks that are not used
393 394 395
        paths.concat %w(
          app 
          app/models 
396 397 398
          app/controllers
          app/helpers
          app/services
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
          app/apis 
          components 
          config 
          lib 
          vendor 
          vendor/rails/railties
          vendor/rails/railties/lib
          vendor/rails/actionpack/lib
          vendor/rails/activesupport/lib
          vendor/rails/activerecord/lib
          vendor/rails/actionmailer/lib
          vendor/rails/actionwebservice/lib
        ).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) }
      end

      def default_log_path
        File.join(RAILS_ROOT, 'log', "#{environment}.log")
      end
      
      def default_log_level
419
        environment == 'production' ? :info : :debug
420 421 422 423 424 425 426 427 428 429 430 431 432
      end
      
      def default_database_configuration_file
        File.join(RAILS_ROOT, 'config', 'database.yml')
      end
      
      def default_view_path
        File.join(RAILS_ROOT, 'app', 'views')
      end
      
      def default_controller_paths
        [ File.join(RAILS_ROOT, 'app', 'controllers'), File.join(RAILS_ROOT, 'components') ]
      end
433
      
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
      def default_dependency_mechanism
        :load
      end
      
      def default_cache_classes
        false
      end
      
      def default_breakpoint_server
        false
      end
      
      def default_whiny_nils
        false
      end
449
  end
450
end
451

452 453
# Needs to be duplicated from Active Support since its needed before Active
# Support is available.
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
class OrderedOptions < Array # :nodoc:
  def []=(key, value)
    key = key.to_sym

    if pair = find_pair(key)
      pair.pop
      pair << value
    else
      self << [key, value]
    end
  end

  def [](key)
    pair = find_pair(key.to_sym)
    pair ? pair.last : nil
  end

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

  private
    def find_pair(key)
      self.each { |i| return i if i.first == key }
      return false
    end
J
Jamis Buck 已提交
484
end