Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/tags/rel_1-2-1/railties/lib/initializer.rb

Revision 5802, 25.1 kB (checked in by rick, 2 years ago)

apply [5801] to 1.2 stable branch

Line 
1 require 'logger'
2 require 'set'
3 require File.join(File.dirname(__FILE__), 'railties_path')
4 require File.join(File.dirname(__FILE__), 'rails/version')
5
6 RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)
7
8 module Rails
9   # The Initializer is responsible for processing the Rails configuration, such
10   # as setting the $LOAD_PATH, requiring the right frameworks, initializing
11   # logging, and more. It can be run either as a single command that'll just
12   # use the default configuration, like this:
13   #
14   #   Rails::Initializer.run
15   #
16   # But normally it's more interesting to pass in a custom configuration
17   # through the block running:
18   #
19   #   Rails::Initializer.run do |config|
20   #     config.frameworks -= [ :action_web_service ]
21   #   end
22   #
23   # This will use the default configuration options from Rails::Configuration,
24   # but allow for overwriting on select areas.
25   class Initializer
26     # The Configuration instance used by this Initializer instance.
27     attr_reader :configuration
28
29     # The set of loaded plugins.
30     attr_reader :loaded_plugins
31
32     # Runs the initializer. By default, this will invoke the #process method,
33     # which simply executes all of the initialization routines. Alternately,
34     # you can specify explicitly which initialization routine you want:
35     #
36     #   Rails::Initializer.run(:set_load_path)
37     #
38     # This is useful if you only want the load path initialized, without
39     # incuring the overhead of completely loading the entire environment.
40     def self.run(command = :process, configuration = Configuration.new)
41       yield configuration if block_given?
42       initializer = new configuration
43       initializer.send(command)
44       initializer
45     end
46
47     # Create a new Initializer instance that references the given Configuration
48     # instance.
49     def initialize(configuration)
50       @configuration = configuration
51       @loaded_plugins = []
52     end
53
54     # Sequentially step through all of the available initialization routines,
55     # in order:
56     #
57     # * #set_load_path
58     # * #set_connection_adapters
59     # * #require_frameworks
60     # * #load_environment
61     # * #initialize_database
62     # * #initialize_logger
63     # * #initialize_framework_logging
64     # * #initialize_framework_views
65     # * #initialize_dependency_mechanism
66     # * #initialize_breakpoints
67     # * #initialize_whiny_nils
68     # * #initialize_framework_settings
69     # * #load_environment
70     # * #load_plugins
71     # * #load_observers
72     # * #initialize_routing
73     #
74     # (Note that #load_environment is invoked twice, once at the start and
75     # once at the end, to support the legacy configuration style where the
76     # environment could overwrite the defaults directly, instead of via the
77     # Configuration instance.
78     def process
79       check_ruby_version
80       set_load_path
81       set_connection_adapters
82
83       require_frameworks
84       set_autoload_paths
85       load_environment
86
87       initialize_encoding
88       initialize_database
89       initialize_logger
90       initialize_framework_logging
91       initialize_framework_views
92       initialize_dependency_mechanism
93       initialize_breakpoints
94       initialize_whiny_nils
95       initialize_temporary_directories
96       initialize_framework_settings
97
98       # Support for legacy configuration style where the environment
99       # could overwrite anything set from the defaults/global through
100       # the individual base class configurations.
101       load_environment
102
103       add_support_load_paths
104
105       load_plugins
106
107       # Observers are loaded after plugins in case Observers or observed models are modified by plugins.
108       load_observers
109
110       # Routing must be initialized after plugins to allow the former to extend the routes
111       initialize_routing
112
113       # the framework is now fully initialized
114       after_initialize
115     end
116
117     # Check for valid Ruby version
118     # This is done in an external file, so we can use it
119     # from the `rails` program as well without duplication.
120     def check_ruby_version
121       require 'ruby_version_check'
122     end
123
124     # Set the <tt>$LOAD_PATH</tt> based on the value of
125     # Configuration#load_paths. Duplicates are removed.
126     def set_load_path
127       load_paths = configuration.load_paths + configuration.framework_paths
128       load_paths.reverse_each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
129       $LOAD_PATH.uniq!
130     end
131
132     # Set the paths from which Rails will automatically load source files, and
133     # the load_once paths.
134     def set_autoload_paths
135       Dependencies.load_paths = configuration.load_paths.uniq
136       Dependencies.load_once_paths = configuration.load_once_paths.uniq
137
138       extra = Dependencies.load_once_paths - Dependencies.load_paths
139       unless extra.empty?
140         abort <<-end_error
141           load_once_paths must be a subset of the load_paths.
142           Extra items in load_once_paths: #{extra * ','}
143         end_error
144       end
145
146       # Freeze the arrays so future modifications will fail rather than do nothing mysteriously
147       configuration.load_once_paths.freeze
148     end
149
150     # Sets the +RAILS_CONNECTION_ADAPTERS+ constant based on the value of
151     # Configuration#connection_adapters. This constant is used to determine
152     # which database adapters should be loaded (by default, all adapters are
153     # loaded).
154     def set_connection_adapters
155       Object.const_set("RAILS_CONNECTION_ADAPTERS", configuration.connection_adapters) if configuration.connection_adapters
156     end
157
158     # Requires all frameworks specified by the Configuration#frameworks
159     # list. By default, all frameworks (ActiveRecord, ActiveSupport,
160     # ActionPack, ActionMailer, and ActionWebService) are loaded.
161     def require_frameworks
162       configuration.frameworks.each { |framework| require(framework.to_s) }
163     end
164
165     # Add the load paths used by support functions such as the info controller
166     def add_support_load_paths
167     end
168
169     # Loads all plugins in <tt>config.plugin_paths</tt>.  <tt>plugin_paths</tt>
170     # defaults to <tt>vendor/plugins</tt> but may also be set to a list of
171     # paths, such as
172     #   config.plugin_paths = ['lib/plugins', 'vendor/plugins']
173     #
174     # Each plugin discovered in <tt>plugin_paths</tt> is initialized:
175     # * add its +lib+ directory, if present, to the beginning of the load path
176     # * evaluate <tt>init.rb</tt> if present
177     #
178     # After all plugins are loaded, duplicates are removed from the load path.
179     # If an array of plugin names is specified in config.plugins, the plugins
180     # will be loaded in that order. Otherwise, plugins are loaded in alphabetical
181     # order.
182     def load_plugins
183       if configuration.plugins.nil?
184         # a nil value implies we don't care about plugins; load 'em all in a reliable order
185         find_plugins(configuration.plugin_paths).sort.each { |path| load_plugin path }
186       elsif !configuration.plugins.empty?
187         # we've specified a config.plugins array, so respect that order
188         plugin_paths = find_plugins(configuration.plugin_paths)
189         configuration.plugins.each do |name|
190           path = plugin_paths.find { |p| File.basename(p) == name }
191           raise(LoadError, "Cannot find the plugin '#{name}'!") if path.nil?
192           load_plugin path
193         end
194       end
195       $LOAD_PATH.uniq!
196     end
197
198     # Loads the environment specified by Configuration#environment_path, which
199     # is typically one of development, testing, or production.
200     def load_environment
201       silence_warnings do
202         config = configuration
203         constants = self.class.constants
204         eval(IO.read(configuration.environment_path), binding, configuration.environment_path)
205         (self.class.constants - constants).each do |const|
206           Object.const_set(const, self.class.const_get(const))
207         end
208       end
209     end
210
211     def load_observers
212       ActiveRecord::Base.instantiate_observers
213     end
214
215     # This initialzation sets $KCODE to 'u' to enable the multibyte safe operations.
216     # Plugin authors supporting other encodings should override this behaviour and
217     # set the relevant +default_charset+ on ActionController::Base
218     def initialize_encoding
219       $KCODE='u'
220     end
221
222     # This initialization routine does nothing unless <tt>:active_record</tt>
223     # is one of the frameworks to load (Configuration#frameworks). If it is,
224     # this sets the database configuration from Configuration#database_configuration
225     # and then establishes the connection.
226     def initialize_database
227       return unless configuration.frameworks.include?(:active_record)
228       ActiveRecord::Base.configurations = configuration.database_configuration
229       ActiveRecord::Base.establish_connection
230     end
231
232     # If the +RAILS_DEFAULT_LOGGER+ constant is already set, this initialization
233     # routine does nothing. If the constant is not set, and Configuration#logger
234     # is not +nil+, this also does nothing. Otherwise, a new logger instance
235     # is created at Configuration#log_path, with a default log level of
236     # Configuration#log_level.
237     #
238     # If the log could not be created, the log will be set to output to
239     # +STDERR+, with a log level of +WARN+.
240     def initialize_logger
241       # if the environment has explicitly defined a logger, use it
242       return if defined?(RAILS_DEFAULT_LOGGER)
243
244       unless logger = configuration.logger
245         begin
246           logger = Logger.new(configuration.log_path)
247           logger.level = Logger.const_get(configuration.log_level.to_s.upcase)
248         rescue StandardError
249           logger = Logger.new(STDERR)
250           logger.level = Logger::WARN
251           logger.warn(
252             "Rails Error: Unable to access log file. Please ensure that #{configuration.log_path} exists and is chmod 0666. " +
253             "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
254           )
255         end
256       end
257
258       silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
259     end
260
261     # Sets the logger for ActiveRecord, ActionController, and ActionMailer
262     # (but only for those frameworks that are to be loaded). If the framework's
263     # logger is already set, it is not changed, otherwise it is set to use
264     # +RAILS_DEFAULT_LOGGER+.
265     def initialize_framework_logging
266       for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks)
267         framework.to_s.camelize.constantize.const_get("Base").logger ||= RAILS_DEFAULT_LOGGER
268       end
269     end
270
271     # Sets the +template_root+ for ActionController::Base and ActionMailer::Base
272     # (but only for those frameworks that are to be loaded). If the framework's
273     # +template_root+ has already been set, it is not changed, otherwise it is
274     # set to use Configuration#view_path.
275     def initialize_framework_views
276       for framework in ([ :action_controller, :action_mailer ] & configuration.frameworks)
277         framework.to_s.camelize.constantize.const_get("Base").template_root ||= configuration.view_path
278       end
279     end
280
281     # If ActionController is not one of the loaded frameworks (Configuration#frameworks)
282     # this does nothing. Otherwise, it loads the routing definitions and sets up
283     # loading module used to lazily load controllers (Configuration#controller_paths).
284     def initialize_routing
285       return unless configuration.frameworks.include?(:action_controller)
286       ActionController::Routing.controller_paths = configuration.controller_paths
287       ActionController::Routing::Routes.reload
288     end
289
290     # Sets the dependency loading mechanism based on the value of
291     # Configuration#cache_classes.
292     def initialize_dependency_mechanism
293       Dependencies.mechanism = configuration.cache_classes ? :require : :load
294     end
295
296     # Sets the +BREAKPOINT_SERVER_PORT+ if Configuration#breakpoint_server
297     # is true.
298     def initialize_breakpoints
299       silence_warnings { Object.const_set("BREAKPOINT_SERVER_PORT", 42531) if configuration.breakpoint_server }
300     end
301
302     # Loads support for "whiny nil" (noisy warnings when methods are invoked
303     # on +nil+ values) if Configuration#whiny_nils is true.
304     def initialize_whiny_nils
305       require('active_support/whiny_nil') if configuration.whiny_nils
306     end
307
308     def initialize_temporary_directories
309       if configuration.frameworks.include?(:action_controller)
310         session_path = "#{RAILS_ROOT}/tmp/sessions/"
311         ActionController::Base.session_options[:tmpdir] = File.exist?(session_path) ? session_path : Dir::tmpdir
312
313         cache_path = "#{RAILS_ROOT}/tmp/cache/"
314         if File.exist?(cache_path)
315           ActionController::Base.fragment_cache_store = :file_store, cache_path
316         end
317       end
318     end
319
320     # Initializes framework-specific settings for each of the loaded frameworks
321     # (Configuration#frameworks). The available settings map to the accessors
322     # on each of the corresponding Base classes.
323     def initialize_framework_settings
324       configuration.frameworks.each do |framework|
325         base_class = framework.to_s.camelize.constantize.const_get("Base")
326
327         configuration.send(framework).each do |setting, value|
328           base_class.send("#{setting}=", value)
329         end
330       end
331     end
332
333     # Fires the user-supplied after_initialize block (Configuration#after_initialize)
334     def after_initialize
335       configuration.after_initialize_block.call if configuration.after_initialize_block
336     end
337
338     protected
339       # Return a list of plugin paths within base_path.  A plugin path is
340       # a directory that contains either a lib directory or an init.rb file.
341       # This recurses into directories which are not plugin paths, so you
342       # may organize your plugins within the plugin path.
343       def find_plugins(*base_paths)
344         base_paths.flatten.inject([]) do |plugins, base_path|
345           Dir.glob(File.join(base_path, '*')).each do |path|
346             if plugin_path?(path)
347               plugins << path if plugin_enabled?(path)
348             elsif File.directory?(path)
349               plugins += find_plugins(path)
350             end
351           end
352           plugins
353         end
354       end
355
356       def plugin_path?(path)
357         File.directory?(path) and (File.directory?(File.join(path, 'lib')) or File.file?(File.join(path, 'init.rb')))
358       end
359
360       def plugin_enabled?(path)
361         configuration.plugins.nil? || configuration.plugins.include?(File.basename(path))
362       end
363
364       # Load the plugin at <tt>path</tt> unless already loaded.
365       #
366       # Each plugin is initialized:
367       # * add its +lib+ directory, if present, to the beginning of the load path
368       # * evaluate <tt>init.rb</tt> if present
369       #
370       # Returns <tt>true</tt> if the plugin is successfully loaded or
371       # <tt>false</tt> if it is already loaded (similar to Kernel#require).
372       # Raises <tt>LoadError</tt> if the plugin is not found.
373       def load_plugin(directory)
374         name = File.basename(directory)
375         return false if loaded_plugins.include?(name)
376
377         # Catch nonexistent and empty plugins.
378         raise LoadError, "No such plugin: #{directory}" unless plugin_path?(directory)
379
380         lib_path  = File.join(directory, 'lib')
381         init_path = File.join(directory, 'init.rb')
382         has_lib   = File.directory?(lib_path)
383         has_init  = File.file?(init_path)
384
385         # Add lib to load path *after* the application lib, to allow
386         # application libraries to override plugin libraries.
387         if has_lib
388           application_lib_index = $LOAD_PATH.index(File.join(RAILS_ROOT, "lib")) || 0
389           $LOAD_PATH.insert(application_lib_index + 1, lib_path)
390           Dependencies.load_paths << lib_path
391           Dependencies.load_once_paths << lib_path
392         end
393
394         # Allow plugins to reference the current configuration object
395         config = configuration
396        
397         # Add to set of loaded plugins before 'name' collapsed in eval.
398         loaded_plugins << name
399
400         # Evaluate init.rb.
401         silence_warnings { eval(IO.read(init_path), binding, init_path) } if has_init
402
403         true
404       end
405   end
406
407   # The Configuration class holds all the parameters for the Initializer and
408   # ships with defaults that suites most Rails applications. But it's possible
409   # to overwrite everything. Usually, you'll create an Configuration file
410   # implicitly through the block running on the Initializer, but it's also
411   # possible to create the Configuration instance in advance and pass it in
412   # like this:
413   #
414   #   config = Rails::Configuration.new
415   #   Rails::Initializer.run(:process, config)
416   class Configuration
417     # A stub for setting options on ActionController::Base
418     attr_accessor :action_controller
419
420     # A stub for setting options on ActionMailer::Base
421     attr_accessor :action_mailer
422
423     # A stub for setting options on ActionView::Base
424     attr_accessor :action_view
425
426     # A stub for setting options on ActionWebService::Base
427     attr_accessor :action_web_service
428
429     # A stub for setting options on ActiveRecord::Base
430     attr_accessor :active_record
431
432     # Whether or not to use the breakpoint server (boolean)
433     attr_accessor :breakpoint_server
434
435     # Whether or not classes should be cached (set to false if you want
436     # application classes to be reloaded on each request)
437     attr_accessor :cache_classes
438
439     # The list of connection adapters to load. (By default, all connection
440     # adapters are loaded. You can set this to be just the adapter(s) you
441     # will use to reduce your application's load time.)
442     attr_accessor :connection_adapters
443
444     # The list of paths that should be searched for controllers. (Defaults
445     # to <tt>app/controllers</tt> and <tt>components</tt>.)
446     attr_accessor :controller_paths
447
448     # The path to the database configuration file to use. (Defaults to
449     # <tt>config/database.yml</tt>.)
450     attr_accessor :database_configuration_file
451
452     # The list of rails framework components that should be loaded. (Defaults
453     # to <tt>:active_record</tt>, <tt>:action_controller</tt>,
454     # <tt>:action_view</tt>, <tt>:action_mailer</tt>, and
455     # <tt>:action_web_service</tt>).
456     attr_accessor :frameworks
457
458     # An array of additional paths to prepend to the load path. By default,
459     # all +app+, +lib+, +vendor+ and mock paths are included in this list.
460     attr_accessor :load_paths
461
462     # An array of paths from which Rails will automatically load from only once.
463     # All elements of this array must also be in +load_paths+.
464     attr_accessor :load_once_paths
465
466     # The log level to use for the default Rails logger. In production mode,
467     # this defaults to <tt>:info</tt>. In development mode, it defaults to
468     # <tt>:debug</tt>.
469     attr_accessor :log_level
470
471     # The path to the log file to use. Defaults to log/#{environment}.log
472     # (e.g. log/development.log or log/production.log).
473     attr_accessor :log_path
474
475     # The specific logger to use. By default, a logger will be created and
476     # initialized using #log_path and #log_level, but a programmer may
477     # specifically set the logger to use via this accessor and it will be
478     # used directly.
479     attr_accessor :logger
480
481     # The root of the application's views. (Defaults to <tt>app/views</tt>.)
482     attr_accessor :view_path
483
484     # Set to +true+ if you want to be warned (noisily) when you try to invoke
485     # any method of +nil+. Set to +false+ for the standard Ruby behavior.
486     attr_accessor :whiny_nils
487
488     # The list of plugins to load. If this is set to <tt>nil</tt>, all plugins will
489     # be loaded. If this is set to <tt>[]</tt>, no plugins will be loaded. Otherwise,
490     # plugins will be loaded in the order specified.
491     attr_accessor :plugins
492
493     # The path to the root of the plugins directory. By default, it is in
494     # <tt>vendor/plugins</tt>.
495     attr_accessor :plugin_paths
496
497     # Create a new Configuration instance, initialized with the default
498     # values.
499     def initialize
500       self.frameworks                   = default_frameworks
501       self.load_paths                   = default_load_paths
502       self.load_once_paths              = default_load_once_paths
503       self.log_path                     = default_log_path
504       self.log_level                    = default_log_level
505       self.view_path                    = default_view_path
506       self.controller_paths             = default_controller_paths
507       self.cache_classes                = default_cache_classes
508       self.breakpoint_server            = default_breakpoint_server
509       self.whiny_nils                   = default_whiny_nils
510       self.plugins                      = default_plugins
511       self.plugin_paths                 = default_plugin_paths
512       self.database_configuration_file  = default_database_configuration_file
513
514       for framework in default_frameworks
515         self.send("#{framework}=", Rails::OrderedOptions.new)
516       end
517     end
518
519     # Loads and returns the contents of the #database_configuration_file. The
520     # contents of the file are processed via ERB before being sent through
521     # YAML::load.
522     def database_configuration
523       YAML::load(ERB.new(IO.read(database_configuration_file)).result)
524     end
525
526     # The path to the current environment's file (development.rb, etc.). By
527     # default the file is at <tt>config/environments/#{environment}.rb</tt>.
528     def environment_path
529       "#{root_path}/config/environments/#{environment}.rb"
530     end
531
532     # Return the currently selected environment. By default, it returns the
533     # value of the +RAILS_ENV+ constant.
534     def environment
535       ::RAILS_ENV
536     end
537
538     # Sets a block which will be executed after rails has been fully initialized.
539     # Useful for per-environment configuration which depends on the framework being
540     # fully initialized.
541     def after_initialize(&after_initialize_block)
542       @after_initialize_block = after_initialize_block
543     end
544
545     # Returns the block set in Configuration#after_initialize
546     def after_initialize_block
547       @after_initialize_block
548     end
549
550     # Add a preparation callback that will run before every request in development
551     # mode, or before the first request in production.
552     #
553     # See Dispatcher#to_prepare.
554     def to_prepare(&callback)
555       Dispatcher.to_prepare(&callback)
556     end
557
558     def builtin_directories
559       # Include builtins only in the development environment.
560       (environment == 'development') ? Dir["#{RAILTIES_PATH}/builtin/*/"] : []
561     end
562
563     def framework_paths
564       # TODO: Don't include dirs for frameworks that are not used
565       %w(
566         railties
567         railties/lib
568         actionpack/lib
569         activesupport/lib
570         activerecord/lib
571         actionmailer/lib
572         actionwebservice/lib
573       ).map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
574     end
575
576     private
577       def root_path
578         ::RAILS_ROOT
579       end
580
581       def framework_root_path
582         defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root_path}/vendor/rails"
583       end
584
585       def default_frameworks
586         [ :active_record, :action_controller, :action_view, :action_mailer, :action_web_service ]
587       end
588
589       def default_load_paths
590         paths = ["#{root_path}/test/mocks/#{environment}"]
591
592         # Add the app's controller directory
593         paths.concat(Dir["#{root_path}/app/controllers/"])
594
595         # Then components subdirectories.
596         paths.concat(Dir["#{root_path}/components/[_a-z]*"])
597
598         # Followed by the standard includes.
599         paths.concat %w(
600           app
601           app/models
602           app/controllers
603           app/helpers
604           app/services
605           app/apis
606           components
607           config
608           lib
609           vendor
610         ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
611
612         paths.concat builtin_directories
613       end
614
615       # Doesn't matter since plugins aren't in load_paths yet.
616       def default_load_once_paths
617         []
618       end
619
620       def default_log_path
621         File.join(root_path, 'log', "#{environment}.log")
622       end
623
624       def default_log_level
625         environment == 'production' ? :info : :debug
626       end
627
628       def default_database_configuration_file
629         File.join(root_path, 'config', 'database.yml')
630       end
631
632       def default_view_path
633