module RequireAll
Constants
- LoadError
Public Instance Methods
Performs Kernel#autoload on all of the files rather than requiring immediately.
Note that all Ruby files inside of the specified directories should have same module name as the directory itself and file names should reflect the class/module names. For example if there is a my_file.rb in directories dir1/dir2/ then there should be a declaration like this in my_file.rb:
module Dir1 module Dir2 class MyFile ... end end end
If the filename and namespaces won't match then my_file.rb will be loaded into wrong module! Better to fix these files.
Set $DEBUG=true to see how files will be autoloaded if experiencing any problems.
If trying to perform autoload on some individual file or some inner module, then you'd have to always specify :base_dir option to specify where top-level namespace resides. Otherwise it's impossible to know the namespace of the loaded files.
For example loading only my_file.rb from dir1/dir2 with autoload_all
:
autoload_all File.dirname(__FILE__) + '/dir1/dir2/my_file', base_dir: File.dirname(__FILE__) + '/dir1'
WARNING: All modules will be created even if files themselves aren't loaded yet, meaning that all the code which depends of the modules being loaded or not will not work, like usages of define? and it's friends.
Also, normal caveats of using Kernel#autoload apply - you have to remember that before applying any monkey-patches to code using autoload, you'll have to reference the full constant to load the code before applying your patch!
# File lib/require_all.rb 175 def autoload_all(*paths) 176 paths.flatten! 177 return false if paths.empty? 178 require "pathname" 179 180 options = {method: :autoload} 181 options.merge!(paths.pop) if paths.last.is_a?(Hash) 182 183 paths.each do |path| 184 require_all path, {base_dir: path}.merge(options) 185 end 186 end
Performs autoloading relatively from the caller instead of using current working directory
# File lib/require_all.rb 189 def autoload_rel(*paths) 190 paths.flatten! 191 return false if paths.empty? 192 require "pathname" 193 194 options = {method: :autoload} 195 options.merge!(paths.pop) if paths.last.is_a?(Hash) 196 197 source_directory = File.dirname caller.first.sub(/:\d+$/, '') 198 paths.each do |path| 199 file_path = Pathname.new(source_directory).join(path).to_s 200 require_all file_path, {method: :autoload, 201 base_dir: source_directory}.merge(options) 202 end 203 end
Loads all files like require_all
instead of requiring
# File lib/require_all.rb 123 def load_all(*paths) 124 require_all paths, method: :load 125 end
Loads all files by using relative paths of the caller rather than the current working directory
# File lib/require_all.rb 129 def load_rel(*paths) 130 paths.flatten! 131 return false if paths.empty? 132 133 source_directory = File.dirname caller.first.sub(/:\d+$/, '') 134 paths.each do |path| 135 require_all File.join(source_directory, path), method: :load 136 end 137 end
A wonderfully simple way to load your code.
The easiest way to use require_all
is to just point it at a directory containing a bunch of .rb files. These files can be nested under subdirectories as well:
require_all 'lib'
This will find all the .rb files under the lib directory and load them.
If a file required by require_all
references a constant that is not yet loaded, a RequireAll::LoadError
will be thrown.
You can also give it a glob, which will enumerate all the matching files:
require_all 'lib/**/*.rb'
It will also accept an array of files:
require_all Dir.glob("blah/**/*.rb").reject { |f| stupid_file(f) }
Or if you want, just list the files directly as arguments:
require_all 'lib/a.rb', 'lib/b.rb', 'lib/c.rb', 'lib/d.rb'
# File lib/require_all.rb 35 def require_all(*args) 36 # Handle passing an array as an argument 37 args.flatten! 38 39 options = {method: :require} 40 options.merge!(args.pop) if args.last.is_a?(Hash) 41 42 if args.empty? 43 puts "no files were loaded due to an empty Array" if $DEBUG 44 return false 45 end 46 47 if args.size > 1 48 # Expand files below directories 49 files = args.map do |path| 50 if File.directory? path 51 Dir[File.join(path, '**', '*.rb')] 52 else 53 path 54 end 55 end.flatten 56 else 57 arg = args.first 58 begin 59 # Try assuming we're doing plain ol' require compat 60 stat = File.stat(arg) 61 62 if stat.file? 63 files = [arg] 64 elsif stat.directory? 65 files = Dir.glob File.join(arg, '**', '*.rb') 66 else 67 raise ArgumentError, "#{arg} isn't a file or directory" 68 end 69 rescue SystemCallError 70 # If the stat failed, maybe we have a glob! 71 files = Dir.glob arg 72 73 # Maybe it's an .rb file and the .rb was omitted 74 if File.file?(arg + '.rb') 75 file = arg + '.rb' 76 options[:method] != :autoload ? __require(options[:method], file) : __autoload(file, file, options) 77 return true 78 end 79 80 # If we ain't got no files, the glob failed 81 raise LoadError, "no such file to load -- #{arg}" if files.empty? 82 end 83 end 84 85 return if files.empty? 86 87 if options[:method] == :autoload 88 files.map! { |file_| [file_, File.expand_path(file_)] } 89 files.each do |file_, full_path| 90 __autoload(file_, full_path, options) 91 end 92 93 return true 94 end 95 96 files.map { |file_| File.expand_path file_ }.sort.each do |file_| 97 begin 98 __require(options[:method], file_) 99 rescue NameError => e 100 # Only wrap NameError exceptions for uninitialized constants 101 raise e unless e.instance_of?(NameError) && e.message.include?('uninitialized constant') 102 raise LoadError, "Could not require #{file_} (#{e}). Please require the necessary files" 103 end 104 end 105 106 true 107 end
Works like require_all
, but paths are relative to the caller rather than the current working directory
# File lib/require_all.rb 111 def require_rel(*paths) 112 # Handle passing an array as an argument 113 paths.flatten! 114 return false if paths.empty? 115 116 source_directory = File.dirname caller.first.sub(/:\d+$/, '') 117 paths.each do |path| 118 require_all File.join(source_directory, path) 119 end 120 end
Private Instance Methods
# File lib/require_all.rb 211 def __autoload(file, full_path, options) 212 last_module = "Object" # default constant where namespaces are created into 213 begin 214 base_dir = Pathname.new(options[:base_dir]).realpath 215 rescue Errno::ENOENT 216 raise LoadError, ":base_dir doesn't exist at #{options[:base_dir]}" 217 end 218 Pathname.new(file).realpath.descend do |entry| 219 # skip until *entry* is same as desired directory 220 # or anything inside of it avoiding to create modules 221 # from the top-level directories 222 next if (entry <=> base_dir) < 0 223 224 # get the module into which a new module is created or 225 # autoload performed 226 mod = Object.class_eval(last_module) 227 228 without_ext = entry.basename(entry.extname).to_s 229 230 const = 231 if defined? ActiveSupport::Inflector 232 ActiveSupport::Inflector.camelize(without_ext) 233 else 234 without_ext.split("_").map {|word| word.capitalize}.join 235 end 236 237 if entry.file? || (entry.directory? && entry.sub_ext('.rb').file?) 238 mod.class_eval do 239 puts "autoloading #{mod}::#{const} from #{full_path}" if $DEBUG 240 autoload const, full_path 241 end 242 else 243 mod.class_eval "module #{const} end" if entry.directory? 244 end 245 246 last_module += "::#{const}" if entry.directory? 247 end 248 end
# File lib/require_all.rb 207 def __require(method, file) 208 Kernel.send(method, file) 209 end