aboutsummaryrefslogblamecommitdiffstats
path: root/activesupport/lib/active_support/dependencies.rb
blob: b82c7e56f3ff281460eddf3a6bb5c9636bdba6d7 (plain) (tree)
1
2
3
4
5
6
7
8
             
                                                                       
                                                       
                                                   
 
                            
             
 



                                                                         




                               
                        
                       
 
                                         
                           

                        


                      
 
                                                       


                                    




                               
 
           
                
     
 
                                

                                               
 


                                                                      
 
            
           


                                                                      
                                
            
                                                    



                               
         


                       
 
                                                                 
                        
     

   
                                                                                                                                                     

                                                                                                                                                         
 
                     

                                                                 

















                                                         


                                                                  
                                                   
                                                                                
         



                                                                                                            
                                 















                                                                                  
                                                                              

       
   
 









                                                        
                     
                         



                              
     
 
                            



                              



                                
                        


                                       
 


                        
 

                                     
                                                                                            
     




                                          
  
require 'set'
require File.dirname(__FILE__) + '/core_ext/module/attribute_accessors'
require File.dirname(__FILE__) + '/core_ext/load_error'
require File.dirname(__FILE__) + '/core_ext/kernel'

module Dependencies #:nodoc:
  extend self

  # Should we turn on Ruby warnings on the first load of dependent files?
  mattr_accessor :warnings_on_first_load
  self.warnings_on_first_load = false

  # All files ever loaded.
  mattr_accessor :history
  self.history = Set.new

  # All files currently loaded.
  mattr_accessor :loaded
  self.loaded = Set.new

  # Should we load files or require them?
  mattr_accessor :mechanism
  self.mechanism = :load

  def load?
    mechanism == :load
  end

  def depend_on(file_name, swallow_load_errors = false)
    require_or_load(file_name)
  rescue LoadError
    raise unless swallow_load_errors
  end

  def associate_with(file_name)
    depend_on(file_name, true)
  end

  def clear
    loaded.clear
  end

  def require_or_load(file_name)
    file_name = $1 if file_name =~ /^(.*)\.rb$/
    return if loaded.include?(file_name)

    # Record that we've seen this file *before* loading it to avoid an
    # infinite loop with mutual dependencies.
    loaded << file_name

    if load?
      begin
        # Enable warnings iff this file has not been loaded before and
        # warnings_on_first_load is set.
        if !warnings_on_first_load or history.include?(file_name)
          load "#{file_name}.rb"
        else
          enable_warnings { load "#{file_name}.rb" }
        end
      rescue
        loaded.delete file_name
        raise
      end
    else
      require file_name
    end

    # Record history *after* loading so first load gets warnings.
    history << file_name
  end
end

Object.send(:define_method, :require_or_load)     { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
Object.send(:define_method, :require_dependency)  { |file_name| Dependencies.depend_on(file_name) }       unless Object.respond_to?(:require_dependency)
Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) }  unless Object.respond_to?(:require_association)

class Module #:nodoc:
  # Rename the original handler so we can chain it to the new one
  alias :rails_original_const_missing :const_missing
  
  def parent
    parent_name = name.split('::')[0..-2] * '::'
    parent_name.empty? ? Object : parent_name.constantize
  end
  
  def as_load_path
    if self == Object || self == Kernel
      ''
    elsif is_a? Class
      parent == self ? '' : parent.as_load_path
    else
      name.split('::').collect do |word|
        word.underscore
      end * '/'
    end
  end
  
  # Use const_missing to autoload associations so we don't have to
  # require_association when using single-table inheritance.
  def const_missing(class_id)
    file_name = class_id.to_s.demodulize.underscore
    file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
    begin
      require_dependency(file_path)
      brief_name = self == Object ? '' : "#{name}::"
      raise NameError.new("uninitialized constant #{brief_name}#{class_id}") unless const_defined?(class_id)
      return const_get(class_id)
    rescue MissingSourceFile => e
      # Re-raise the error if it does not concern the file we were trying to load.
      raise unless e.is_missing? file_path
      
      # Look for a directory in the load path that we ought to load.
      if $LOAD_PATH.any? { |base| File.directory? "#{base}/#{file_path}" }
        mod = Module.new
        const_set class_id, mod # Create the new module
        return mod
      end
      
      if parent && parent != self
        suppress(NameError) do
          return parent.send(:const_missing, class_id)
        end
      end
      
      raise NameError.new("uninitialized constant #{class_id}").copy_blame!(e)
    end
  end
end

class Class
  def const_missing(class_id)
    if [Object, Kernel].include?(self) || parent == self
      super
    else
      parent.send :const_missing, class_id
    end
  end
end

class Object #:nodoc:
  def load(file, *extras)
    super(file, *extras)
  rescue Object => exception
    exception.blame_file! file
    raise
  end

  def require(file, *extras)
    super(file, *extras)
  rescue Object => exception
    exception.blame_file! file
    raise
  end
end

# Add file-blaming to exceptions
class Exception #:nodoc:
  def blame_file!(file)
    (@blamed_files ||= []).unshift file
  end

  def blamed_files
    @blamed_files ||= []
  end

  def describe_blame
    return nil if blamed_files.empty?
    "This error occured while loading the following files:\n   #{blamed_files.join "\n   "}"
  end

  def copy_blame!(exc)
    @blamed_files = exc.blamed_files.clone
    self
  end
end