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

root/trunk/activesupport/lib/active_support/dependencies.rb

Revision 3190, 8.3 kB (checked in by bitsweat, 5 years ago)

Introduce Dependencies.warnings_on_first_load setting. If true, enables warnings on first load of a require_dependency. Otherwise, loads without warnings. Disabled (set to false) by default.

Line 
1 require 'set'
2 require File.dirname(__FILE__) + '/module_attribute_accessors'
3 require File.dirname(__FILE__) + '/core_ext/load_error'
4 require File.dirname(__FILE__) + '/core_ext/kernel'
5
6 module Dependencies #:nodoc:
7   extend self
8
9   # Should we turn on Ruby warnings on the first load of dependent files?
10   mattr_accessor :warnings_on_first_load
11   self.warnings_on_first_load = false
12
13   # All files ever loaded.
14   mattr_accessor :history
15   self.history = Set.new
16
17   # All files currently loaded.
18   mattr_accessor :loaded
19   self.loaded = Set.new
20
21   # Should we load files or require them?
22   mattr_accessor :mechanism
23   self.mechanism = :load
24
25   def load?
26     mechanism == :load
27   end
28
29   def depend_on(file_name, swallow_load_errors = false)
30     unless loaded.include?(file_name)
31       begin
32         require_or_load(file_name)
33       rescue LoadError
34         raise unless swallow_load_errors
35       end
36     end
37   end
38
39   def associate_with(file_name)
40     depend_on(file_name, true)
41   end
42
43   def clear
44     loaded.clear
45   end
46
47   def require_or_load(file_name)
48     if load?
49       # Append .rb if we have a bare file name.
50       load_file_name = (file_name =~ /\.rb$/ ? file_name : "#{file_name}.rb")
51
52       # Record that we've seen this file *before* loading it to avoid an
53       # infinite loop with mutual dependencies.
54       loaded << file_name
55
56       begin
57         # Enable warnings iff this file has not been loaded before and
58         # warnings_on_first_load is set.
59         if !warnings_on_first_load or history.include?(file_name)
60           load load_file_name
61         else
62           enable_warnings { load load_file_name }
63         end
64       rescue
65         loaded.delete file_name
66         raise
67       end
68     else
69       require file_name
70     end
71
72     # Record history *after* loading so first load gets warnings.
73     history << file_name
74   end
75
76   def remove_subclasses_for(*classes)
77     Object.remove_subclasses_of(*classes)
78   end
79  
80   # LoadingModules implement namespace-safe dynamic loading.
81   # They support automatic loading via const_missing, allowing contained items to be automatically
82   # loaded when required. No extra syntax is required, as expressions such as Controller::Admin::UserController
83   # load the relavent files automatically.
84   #
85   # Ruby-style modules are supported, as a folder named 'submodule' will load 'submodule.rb' when available.
86   class LoadingModule < Module #:nodoc:
87     attr_reader :path
88     attr_reader :root
89    
90     class << self
91       def root(*load_paths)
92         RootLoadingModule.new(*load_paths)
93       end
94     end
95    
96     def initialize(root, path=[])
97       @path = path.clone.freeze
98       @root = root
99     end
100    
101     def root?()      self.root == self    end
102     def load_paths() self.root.load_paths end
103    
104     # Load missing constants if possible.
105     def const_missing(name)
106       const_load!(name) ? const_get(name) : super(name)
107     end
108  
109     # Load the controller class or a parent module.
110     def const_load!(name, file_name = nil)
111       file_name ||= 'application' if root? && name.to_s == 'ApplicationController'
112       path = self.path + [file_name || name]
113
114       load_paths.each do |load_path|
115         fs_path = load_path.filesystem_path(path)
116         next unless fs_path
117
118         case
119         when File.directory?(fs_path)
120           new_module = LoadingModule.new(self.root, self.path + [name])
121           self.const_set name, new_module
122           if self.root?
123             if Object.const_defined?(name)
124               msg = "Cannot load module #{name}: Object::#{name} is set to #{Object.const_get(name).inspect}"
125               raise NameError, msg
126             end
127             Object.const_set(name, new_module)
128           end
129           break
130         when File.file?(fs_path)
131           loaded_file = self.root.load_file!(fs_path)
132          
133           # Import the loaded constant from Object provided we are the root node.
134           self.const_set(name, Object.const_get(name)) if self.root? && Object.const_defined?(name)
135          
136           # Throw an error if we load the file but we don't find the Object we expect
137           if loaded_file and not self.const_defined?(name)
138             msg = "Already loaded file '#{fs_path}' but '#{name.to_s}' was not set, perhaps you need to rename '#{fs_path}'?"
139             raise LoadError, msg
140           end
141           break
142         end
143       end
144      
145       self.const_defined?(name)
146     end
147    
148     # Is this name present or loadable?
149     # This method is used by Routes to find valid controllers.
150     def const_available?(name)
151       self.const_defined?(name) || load_paths.any? {|lp| lp.filesystem_path(path + [name])}
152     end
153   end
154  
155   class RootLoadingModule < LoadingModule #:nodoc:
156     attr_reader :load_paths
157
158     def initialize(*paths)
159       @load_paths = paths.flatten.collect {|p| p.kind_of?(ConstantLoadPath) ? p : ConstantLoadPath.new(p)}
160     end
161
162     def root() self end
163
164     def path() [] end
165    
166     # Load the source file at the given file path
167     def load_file!(file_path)
168       require_dependency(file_path)
169     end
170
171     # Erase all items in this module
172     def clear!
173       constants.each do |name|
174         Object.send(:remove_const, name) if Object.const_defined?(name) && Object.const_get(name).object_id == self.const_get(name).object_id
175         self.send(:remove_const, name)
176       end
177     end
178   end
179  
180   # This object defines a path from which Constants can be loaded.
181   class ConstantLoadPath #:nodoc:
182     # Create a new load path with the filesystem path
183     def initialize(root) @root = root end
184    
185     # Return nil if the path does not exist, or the path to a directory
186     # if the path leads to a module, or the path to a file if it leads to an object.
187     def filesystem_path(path, allow_module=true)
188       fs_path = [@root]
189       fs_path += path[0..-2].map {|name| const_name_to_module_name name}
190
191       if allow_module
192         result = File.join(fs_path, const_name_to_module_name(path.last))
193         return result if File.directory? result # Return the module path if one exists
194       end
195
196       result = File.join(fs_path, const_name_to_file_name(path.last))
197
198       File.file?(result) ? result : nil
199     end
200    
201     def const_name_to_file_name(name)
202       name.to_s.underscore + '.rb'
203     end
204
205     def const_name_to_module_name(name)
206       name.to_s.underscore
207     end
208   end
209 end
210
211 Object.send(:define_method, :require_or_load)     { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
212 Object.send(:define_method, :require_dependency)  { |file_name| Dependencies.depend_on(file_name) }       unless Object.respond_to?(:require_dependency)
213 Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) }  unless Object.respond_to?(:require_association)
214
215 class Module #:nodoc:
216   # Rename the original handler so we can chain it to the new one
217   alias :rails_original_const_missing :const_missing
218
219   # Use const_missing to autoload associations so we don't have to
220   # require_association when using single-table inheritance.
221   def const_missing(class_id)
222     if Object.const_defined?(:Controllers) and Object::Controllers.const_available?(class_id)
223       return Object::Controllers.const_get(class_id)
224     end
225    
226     file_name = class_id.to_s.demodulize.underscore
227     begin
228       require_dependency(file_name)
229       raise NameError.new("uninitialized constant #{class_id}") unless Object.const_defined?(class_id)
230       return Object.const_get(class_id)
231     rescue MissingSourceFile => e
232       # Convert the exception to a NameError only if the file we are looking for is the missing one.
233       raise unless e.is_missing? file_name
234       raise NameError.new("uninitialized constant #{class_id}").copy_blame!(e)
235     end
236   end
237 end
238
239 class Object #:nodoc:
240   def load(file, *extras)
241     super(file, *extras)
242   rescue Object => exception
243     exception.blame_file! file
244     raise
245   end
246
247   def require(file, *extras)
248     super(file, *extras)
249   rescue Object => exception
250     exception.blame_file! file
251     raise
252   end
253 end
254
255 # Add file-blaming to exceptions
256 class Exception #:nodoc:
257   def blame_file!(file)
258     (@blamed_files ||= []).unshift file
259   end
260
261   def blamed_files
262     @blamed_files ||= []
263   end
264
265   def describe_blame
266     return nil if blamed_files.empty?
267     "This error occured while loading the following files:\n   #{blamed_files.join "\n   "}"
268   end
269
270   def copy_blame!(exc)
271     @blamed_files = exc.blamed_files.clone
272     self
273   end
274 end
Note: See TracBrowser for help on using the browser.