3 # We need to know the version of Rails that we are running before we
4 # can override any of the dependency stuff, since Rails' own behaviour
5 # has changed over the various releases. We need to explicily make sure
6 # that the Rails::VERSION constant is loaded, because such things could
7 # not automatically be achieved prior to 1.1, and the location of the
10 # At this point, we can't even rely on RAILS_ROOT existing, so we have to figure
11 # the path to RAILS_ROOT/vendor/rails manually
12 rails_base = File.expand_path(
13 File.join(File.dirname(__FILE__), # RAILS_ROOT/vendor/plugins/engines/lib
14 '..', # RAILS_ROOT/vendor/plugins/engines
15 '..', # RAILS_ROOT/vendor/plugins
16 '..', # RAILS_ROOT/vendor
17 'rails', 'railties', 'lib')) # RAILS_ROOT/vendor/rails/railties/lib
19 load File.join(rails_base, 'rails', 'version.rb')
20 #puts 'loaded 1.1.1+ from vendor: ' + File.join(rails_base, 'rails', 'version.rb')
21 rescue MissingSourceFile # this means they DON'T have Rails 1.1.1 or later installed in vendor
23 load File.join(rails_base, 'rails_version.rb')
24 #puts 'loaded 1.1.0- from vendor: ' + File.join(rails_base, 'rails_version.rb')
25 rescue MissingSourceFile # this means they DON'T have Rails 1.1.0 or previous installed in vendor
27 # try and load version information for Rails 1.1.1 or later from the $LOAD_PATH
28 require 'rails/version'
29 #puts 'required 1.1.1+ from load path'
31 # try and load version information for Rails 1.1.0 or previous from the $LOAD_PATH
32 require 'rails_version'
33 #puts 'required 1.1.0- from load path'
39 # Actually perform the load
41 #puts "Detected Rails version: #{Rails::VERSION::STRING}"
43 require 'engines/ruby_extensions'
44 # ... further files are required at the bottom of this file
46 # Holds the Rails Engine loading logic and default constants
50 # Return the version string for this plugin
52 "#{Version::Major}.#{Version::Minor}.#{Version::Release}"
55 # For holding the rails configuration object
56 attr_accessor :rails_config
58 # A flag to stop searching for views in the application
59 attr_accessor :disable_app_views_loading
61 # A flag to stop code being mixed in from the application
62 attr_accessor :disable_app_code_mixing
65 # The DummyLogger is a class which might pass through to a real Logger
66 # if one is assigned. However, it can gracefully swallow any logging calls
67 # if there is now Logger assigned.
69 def initialize(logger=nil)
72 # Assign the 'real' Logger instance that this dummy instance wraps around.
73 def set_logger(logger)
76 # log using the appropriate method if we have a logger
77 # if we dont' have a logger, ignore completely.
78 def method_missing(name, *args)
79 if @logger && @logger.respond_to?(name)
80 @logger.send(name, *args)
85 LOGGER = Engines::LoggerWrapper.new
88 # Create a new Logger instance for Engines, with the given outputter and level
89 def create_logger(outputter=STDOUT, level=Logger::INFO)
90 LOGGER.set_logger(Logger.new(outputter, level))
92 # Sets the Logger instance that Engines will use to send logging information to
93 def set_logger(logger)
94 Engines::LOGGER.set_logger(logger) # TODO: no need for Engines:: part
96 # Retrieves the current Logger instance
98 Engines::LOGGER # TODO: no need for Engines:: part
103 # An array of active engines. This should be accessed via the Engines.active method.
106 # The root directory for engines
107 config :root, File.join(RAILS_ROOT, "vendor", "plugins")
109 # The name of the public folder under which engine files are copied
110 config :public_dir, "engine_files"
114 # Initializes a Rails Engine by loading the engine's init.rb file and
115 # ensuring that any engine controllers are added to the load path.
116 # This will also copy any files in a directory named 'public'
117 # into the public webserver directory. Example usage:
119 # Engines.start :login
120 # Engines.start :login_engine # equivalent
122 # A list of engine names can be specified:
124 # Engines.start :login, :user, :wiki
126 # The engines will be loaded in the order given.
127 # If no engine names are given, all engines will be started.
129 # Options can include:
130 # * :copy_files => true | false
132 # Note that if a list of engines is given, the options will apply to ALL engines.
135 options = (args.last.is_a? Hash) ? args.pop : {}
140 args.each do |engine_name|
141 start_engine(engine_name, options)
146 # Starts all available engines. Plugins are considered engines if they
147 # include an init_engine.rb file, or they are named <something>_engine.
149 plugins = Dir[File.join(config(:root), "*")]
150 Engines.log.debug "considering plugins: #{plugins.inspect}"
151 plugins.each { |plugin|
152 engine_name = File.basename(plugin)
153 if File.exist?(File.join(plugin, "init_engine.rb")) || # if the directory contains init_engine.rb
154 (engine_name =~ /_engine$/) || # or it engines in '_engines'
155 (engine_name =~ /_bundle$/) # or even ends in '_bundle'
157 start(engine_name) # start the engine...
163 # Initialize the routing controller paths.
164 def initialize_routing
165 # See lib/engines/routing_extensions.rb for more information.
166 ActionController::Routing.controller_paths = Engines.rails_config.controller_paths
169 def start_engine(engine_name, options={})
171 # Create a new Engine and put this engine at the front of the ActiveEngines list
172 current_engine = Engine.new(engine_name)
173 Engines.active.unshift current_engine
174 Engines.log.info "Starting engine '#{current_engine.name}' from '#{File.expand_path(current_engine.root)}'"
176 # add the code directories of this engine to the load path
177 add_engine_to_load_path(current_engine)
179 # add the controller & component path to the Dependency system
180 engine_controllers = File.join(current_engine.root, 'app', 'controllers')
181 engine_components = File.join(current_engine.root, 'components')
184 # This mechanism is no longer required in Rails trunk
185 if Rails::VERSION::STRING =~ /^1.0/ && !Engines.config(:edge)
186 Controllers.add_path(engine_controllers) if File.exist?(engine_controllers)
187 Controllers.add_path(engine_components) if File.exist?(engine_components)
189 ActionController::Routing.controller_paths << engine_controllers
190 ActionController::Routing.controller_paths << engine_components
193 # copy the files unless indicated otherwise
194 if options[:copy_files] != false
195 current_engine.mirror_engine_files
198 # load the engine's init.rb file
199 startup_file = File.join(current_engine.root, "init_engine.rb")
200 if File.exist?(startup_file)
201 eval(IO.read(startup_file), binding, startup_file)
202 # possibly use require_dependency? Hmm.
204 Engines.log.debug "No init_engines.rb file found for engine '#{current_engine.name}'..."
208 # Adds all directories in the /app and /lib directories within the engine
210 def add_engine_to_load_path(engine)
212 # remove the lib directory added by load_plugin, and place it in the corrent
213 # location *after* the application/lib. This can be removed when
214 # http://dev.rubyonrails.org/ticket/2910 is fixed.
215 app_lib_index = $LOAD_PATH.index(File.join(RAILS_ROOT, "lib"))
216 engine_lib = File.join(engine.root, "lib")
218 $LOAD_PATH.delete(engine_lib)
219 $LOAD_PATH.insert(app_lib_index+1, engine_lib)
222 # Add ALL paths under the engine root to the load path
223 app_dirs = %w(controllers helpers models).collect { |d|
224 File.join(engine.root, 'app', d)
226 other_dirs = %w(components lib).collect { |d|
227 File.join(engine.root, d)
229 load_paths = (app_dirs + other_dirs).select { |d| File.directory?(d) }
231 # Remove other engines from the $LOAD_PATH by matching against the engine.root values
232 # in ActiveEngines. Store the removed engines in the order they came off.
234 old_plugin_paths = []
235 # assumes that all engines are at the bottom of the $LOAD_PATH
236 while (File.expand_path($LOAD_PATH.last).index(File.expand_path(Engines.config(:root))) == 0) do
237 old_plugin_paths.unshift($LOAD_PATH.pop)
241 # add these LAST on the load path.
242 load_paths.reverse.each { |dir|
243 if File.directory?(dir)
244 Engines.log.debug "adding #{File.expand_path(dir)} to the load path"
245 #$LOAD_PATH.push(File.expand_path(dir))
250 # Add the other engines back onto the bottom of the $LOAD_PATH. Put them back on in
252 $LOAD_PATH.push(*old_plugin_paths)
256 # Returns the directory in which all engine public assets are mirrored.
257 def public_engine_dir
258 File.expand_path(File.join(RAILS_ROOT, "public", Engines.config(:public_dir)))
261 # create the /public/engine_files directory if it doesn't exist
262 def create_base_public_directory
263 if !File.exists?(public_engine_dir)
264 # create the public/engines directory, with a warning message in it.
265 Engines.log.debug "Creating public engine files directory '#{public_engine_dir}'"
266 FileUtils.mkdir(public_engine_dir)
267 File.open(File.join(public_engine_dir, "README"), "w") do |f|
269 Files in this directory are automatically generated from your Rails Engines.
270 They are copied from the 'public' directories of each engine into this directory
271 each time Rails starts (server, console... any time 'start_engine' is called).
272 Any edits you make will NOT persist across the next server restart; instead you
273 should edit the files within the <engine_name>/public/ directory itself.
279 # Returns the Engine object for the specified engine, e.g.:
280 # Engines.get(:login)
282 active.find { |e| e.name == name.to_s || e.name == "#{name}_engine" }
284 alias_method :[], :get
286 # Returns the Engine object for the current engine, i.e. the engine
287 # in which the currently executing code lies.
289 current_file = caller[0]
290 active.find do |engine|
291 File.expand_path(current_file).index(File.expand_path(engine.root)) == 0
295 # Returns an array of active engines
300 # Pass a block to perform an operation on each engine. You may pass an argument
301 # to determine the order:
303 # * :load_order - in the order they were loaded (i.e. lower precidence engines first).
304 # * :precidence_order - highest precidence order (i.e. last loaded) first
305 def each(ordering=:precidence_order, &block)
306 engines = (ordering == :load_order) ? active.reverse : active
307 engines.each { |e| yield e }
312 # A simple class for holding information about loaded engines
315 # Returns the base path of this engine
318 # Returns the name of this engine
321 # An attribute for holding the current version of this engine. There are three
322 # ways of providing an engine version. The simplest is using a string:
324 # Engines.current.version = "1.0.7"
326 # Alternatively you can set it to a module which contains Major, Minor and Release
329 # module LoginEngine::Version
330 # Major = 1; Minor = 0; Release = 6;
332 # Engines.current.version = LoginEngine::Version
334 # Finally, you can set it to your own Proc, if you need something really fancy:
336 # Engines.current.version = Proc.new { File.open('VERSION', 'r').readlines[0] }
340 # Engine developers can store any information they like in here.
343 # Creates a new object holding information about an Engine.
347 suffixes = ['', '_engine', '_bundle']
348 while !File.exist?(@root) && !suffixes.empty?
349 suffix = suffixes.shift
350 @root = File.join(Engines.config(:root), name.to_s + suffix)
353 if !File.exist?(@root)
354 raise "Cannot find the engine '#{name}' in either /vendor/plugins/#{name}, " +
355 "/vendor/plugins/#{name}_engine or /vendor/plugins/#{name}_bundle."
358 @name = File.basename(@root)
361 # Returns the version string of this engine
365 "#{@version::Major}.#{@version::Minor}.#{@version::Release}"
366 when Proc # not sure about this
375 # Returns a string describing this engine
380 # Returns a string representation of this engine
382 "Engine<'#{@name}' [#{version}]:#{root.gsub(RAILS_ROOT, '')}>"
385 # return the path to this Engine's public files (with a leading '/' for use in URIs)
387 File.join("/", Engines.config(:public_dir), name)
390 # Replicates the subdirectories under the engine's /public directory into
391 # the corresponding public directory.
392 def mirror_engine_files
395 Engines.create_base_public_directory
397 source = File.join(root, "public")
398 Engines.log.debug "Attempting to copy public engine files from '#{source}'"
400 # if there is no public directory, just return after this file
401 return if !File.exist?(source)
403 source_files = Dir[source + "/**/*"]
404 source_dirs = source_files.select { |d| File.directory?(d) }
405 source_files -= source_dirs
407 Engines.log.debug "source dirs: #{source_dirs.inspect}"
409 # Create the engine_files/<something>_engine dir if it doesn't exist
410 new_engine_dir = File.join(RAILS_ROOT, "public", public_dir)
411 if !File.exists?(new_engine_dir)
412 # Create <something>_engine dir with a message
413 Engines.log.debug "Creating #{public_dir} public dir"
414 FileUtils.mkdir_p(new_engine_dir)
417 # create all the directories, transforming the old path into the new path
418 source_dirs.uniq.each { |dir|
420 # strip out the base path and add the result to the public path, i.e. replace
421 # ../script/../vendor/plugins/engine_name/public/javascript
423 # engine_name/javascript
425 relative_dir = dir.gsub(File.join(root, "public"), name)
426 target_dir = File.join(Engines.public_engine_dir, relative_dir)
427 unless File.exist?(target_dir)
428 Engines.log.debug "creating directory '#{target_dir}'"
429 FileUtils.mkdir_p(target_dir)
431 rescue Exception => e
432 raise "Could not create directory #{target_dir}: \n" + e
436 # copy all the files, transforming the old path into the new path
437 source_files.uniq.each { |file|
439 # change the path from the ENGINE ROOT to the public directory root for this engine
440 target = file.gsub(File.join(root, "public"),
441 File.join(Engines.public_engine_dir, name))
442 unless File.exist?(target) && FileUtils.identical?(file, target)
443 Engines.log.debug "copying file '#{file}' to '#{target}'"
444 FileUtils.cp(file, target)
446 rescue Exception => e
447 raise "Could not copy #{file} to #{target}: \n" + e
450 rescue Exception => e
451 Engines.log.warn "WARNING: Couldn't create the engine public file structure for engine '#{name}'; Error follows:"
458 # These files must be required after the Engines module has been defined.
459 require 'engines/dependencies_extensions'
460 require 'engines/routing_extensions'
461 require 'engines/action_view_extensions'
462 require 'engines/action_mailer_extensions'
463 require 'engines/migration_extensions'
464 require 'engines/active_record_extensions'
466 # only load the testing extensions if we are in the test environment
467 require 'engines/testing_extensions' if %w(test).include?(RAILS_ENV)