module SimpleEngines class Base class << self def engines() @@engines ||= [] end def add_engine(engine) @@engines ||= [] case engine when Engine::Base @@engines << engine when String return unless File.directory? engine @@engines << Engine::Base.new(engine) end @@engines.uniq! rescue => e raise Engine::InitializeError.new(engine, e) end def add_engines(*engines) engines.flatten.each {|e| add_engine(e) } end def default_engines @@default_engines ||= Dir["#{RAILS_ROOT}/vendor/engines/[_a-z]*_engine"].collect {|e| Engine::Base.new(e) } end end end module Engine class InitializeError < Exception #:nodoc: def initialize(engine, error) @engine_name = File.basename(engine) @engine_path = engine @error = error end def backtrace @error.backtrace end def to_s "The #{@engine_name} engine failed to initialize because of a #{@error} error." end end class Base attr_reader :engine_name attr_reader :engine_path def initialize(path) @engine_path = path @engine_name = File.basename(path).camelize auto_load_files.each do |file| file = "#{engine_path}/#{file}" load(file) if File.exists? file end end def auto_load_files %w( app/controllers/application.rb config/routes.rb config/init.rb ) end def load_paths @load_paths ||= %w( app app/models app/controllers app/helpers app/services app/apis components lib ).map { |dir| File.join(engine_path, dir) }.select { |dir| File.directory?(dir) } end def using_load_paths(&block) load_paths.each {|path| $LOAD_PATH.unshift(path) } yield load_paths.size.times { $LOAD_PATH.shift } end end end end module Dependencies def depend_on_with_engine(file_name, swallow_load_errors = false) # Try to find the same file in our engines SimpleEngines::Base.engines.each do |engine| engine.using_load_paths do file_name += file_name.split('.').last == 'rb' ? '' : '.rb' load(file_name) end end # Let rails do it's thing depend_on_without_engine(file_name, swallow_load_errors) end alias_method_chain :depend_on, :engine end class Module def const_missing_with_engine(class_id) const_missing_without_engine(class_id) rescue NameError => e SimpleEngines::Base.engines.each do |engine| file_name = class_id.to_s.demodulize.underscore file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}" if engine.load_paths.any? { |base| File.directory? "#{base}/#{file_path}" } mod = Module.new const_set class_id, mod # Create the new module return mod end end raise end alias_method_chain :const_missing, :engine end module ActionView class Base def render_template_with_engine(template_extension, template, file_path = nil, local_assigns = {}) render_template_without_engine(template_extension, template, file_path, local_assigns) rescue Exception => e raise if TemplateError === e file_path.gsub!(/^#{Regexp.escape(RAILS_ROOT)}/,'') SimpleEngines::Base.engines.each do |engine| new_path = engine.engine_path + file_path next unless File.exists? new_path return render_template_without_engine(template_extension, template, new_path, local_assigns) end raise end alias_method_chain :render_template, :engine def template_exists_with_engine?(template_path, extension) template_exists_without_engine?(template_path, extension) or SimpleEngines::Base.engines.any? {|e| File.exists? "#{e.engine_path}/app/views/#{template_path}.#{extension}" } end alias_method :template_exists_without_engine?, :template_exists? alias_method :template_exists?, :template_exists_with_engine? end end