1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
|
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
# LoadingModules implement namespace-safe dynamic loading.
# They support automatic loading via const_missing, allowing contained items to be automatically
# loaded when required. No extra syntax is required, as expressions such as Controller::Admin::UserController
# load the relavent files automatically.
#
# Ruby-style modules are supported, as a folder named 'submodule' will load 'submodule.rb' when available.
class LoadingModule < Module #:nodoc:
attr_reader :path
attr_reader :root
class << self
def root(*load_paths)
RootLoadingModule.new(*load_paths)
end
end
def initialize(root, path=[])
@path = path.clone.freeze
@root = root
end
def root?() self.root == self end
def load_paths() self.root.load_paths end
# Load missing constants if possible.
def const_missing(name)
const_load!(name) ? const_get(name) : super(name)
end
# Load the controller class or a parent module.
def const_load!(name, file_name = nil)
file_name ||= 'application' if root? && name.to_s == 'ApplicationController'
path = self.path + [file_name || name]
load_paths.each do |load_path|
fs_path = load_path.filesystem_path(path)
next unless fs_path
case
when File.directory?(fs_path)
new_module = LoadingModule.new(self.root, self.path + [name])
self.const_set name, new_module
if self.root?
if Object.const_defined?(name)
msg = "Cannot load module #{name}: Object::#{name} is set to #{Object.const_get(name).inspect}"
raise NameError, msg
end
Object.const_set(name, new_module)
end
break
when File.file?(fs_path)
loaded_file = self.root.load_file!(fs_path)
# Import the loaded constant from Object provided we are the root node.
self.const_set(name, Object.const_get(name)) if self.root? && Object.const_defined?(name)
# Throw an error if we load the file but we don't find the Object we expect
if loaded_file and not self.const_defined?(name)
msg = "Already loaded file '#{fs_path}' but '#{name.to_s}' was not set, perhaps you need to rename '#{fs_path}'?"
raise LoadError, msg
end
break
end
end
self.const_defined?(name)
end
# Is this name present or loadable?
# This method is used by Routes to find valid controllers.
def const_available?(name)
self.const_defined?(name) || load_paths.any? {|lp| lp.filesystem_path(path + [name])}
end
# Erase all items in this module
def clear!
constants.each do |name|
Object.send(:remove_const, name) if Object.const_defined?(name) && Object.const_get(name).object_id == self.const_get(name).object_id
self.send(:remove_const, name)
end
end
end
class RootLoadingModule < LoadingModule #:nodoc:
attr_reader :load_paths
def initialize(*paths)
@load_paths = paths.flatten.collect {|p| p.kind_of?(ConstantLoadPath) ? p : ConstantLoadPath.new(p)}
end
def root() self end
def path() [] end
# Load the source file at the given file path
def load_file!(file_path)
require_dependency(file_path)
end
end
# This object defines a path from which Constants can be loaded.
class ConstantLoadPath #:nodoc:
# Create a new load path with the filesystem path
def initialize(root) @root = root end
# Return nil if the path does not exist, or the path to a directory
# if the path leads to a module, or the path to a file if it leads to an object.
def filesystem_path(path, allow_module=true)
fs_path = [@root]
fs_path += path[0..-2].map {|name| const_name_to_module_name name}
if allow_module
result = File.join(fs_path, const_name_to_module_name(path.last))
return result if File.directory? result # Return the module path if one exists
end
result = File.join(fs_path, const_name_to_file_name(path.last))
File.file?(result) ? result : nil
end
def const_name_to_file_name(name)
name.to_s.underscore + '.rb'
end
def const_name_to_module_name(name)
name.to_s.underscore
end
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)
if Object.const_defined?(:Controllers) && Object::Controllers.const_available?(class_id)
return Object::Controllers.const_get(class_id)
end
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
|