initializer.rb 17.2 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 149 150
    def load_environment
      silence_warnings do
        config = configuration
        eval(IO.read(configuration.environment_path), binding)
      end
151 152
    end
    
153 154 155 156
    # 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.
157 158 159 160 161 162
    def initialize_database
      return unless configuration.frameworks.include?(:active_record)
      ActiveRecord::Base.configurations = configuration.database_configuration
      ActiveRecord::Base.establish_connection
    end
    
163 164 165 166 167 168 169 170
    # 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+.
171
    def initialize_logger
172 173 174
      # if the environment has explicitly defined a logger, use it
      return if defined?(RAILS_DEFAULT_LOGGER)

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

212 213 214
    # 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).
215 216 217 218 219
    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
220
    
221 222
    # Sets the dependency loading mechanism based on the value of
    # Configuration#cache_classes.
223 224
    def initialize_dependency_mechanism
      Dependencies.mechanism = configuration.cache_classes ? :require : :load
225
    end
226
    
227 228
    # Sets the +BREAKPOINT_SERVER_PORT+ if Configuration#breakpoint_server
    # is true.
229 230
    def initialize_breakpoints
      silence_warnings { Object.const_set("BREAKPOINT_SERVER_PORT", 42531) if configuration.breakpoint_server }
231
    end
232 233 234

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

239 240 241
    # 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.
242
    def initialize_framework_settings
243 244
      configuration.frameworks.each do |framework|
        base_class = framework.to_s.camelize.constantize.const_get("Base")
245

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

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

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

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

        # Followed by the standard includes.
388
        # TODO: Don't include dirs for frameworks that are not used
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
        paths.concat %w(
          app 
          app/models 
          app/controllers 
          app/helpers 
          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
414
        environment == 'production' ? :info : :debug
415 416 417 418 419 420 421 422 423 424 425 426 427
      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
428
      
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
      def default_dependency_mechanism
        :load
      end
      
      def default_cache_classes
        false
      end
      
      def default_breakpoint_server
        false
      end
      
      def default_whiny_nils
        false
      end
444
  end
445
end
446

447 448
# Needs to be duplicated from Active Support since its needed before Active
# Support is available.
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
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 已提交
479
end