| 70 | | end |
|---|
| 71 | | |
|---|
| 72 | | # LoadingModules implement namespace-safe dynamic loading. |
|---|
| 73 | | # They support automatic loading via const_missing, allowing contained items to be automatically |
|---|
| 74 | | # loaded when required. No extra syntax is required, as expressions such as Controller::Admin::UserController |
|---|
| 75 | | # load the relavent files automatically. |
|---|
| 76 | | # |
|---|
| 77 | | # Ruby-style modules are supported, as a folder named 'submodule' will load 'submodule.rb' when available. |
|---|
| 78 | | class LoadingModule < Module #:nodoc: |
|---|
| 79 | | attr_reader :path |
|---|
| 80 | | attr_reader :root |
|---|
| 81 | | |
|---|
| 82 | | class << self |
|---|
| 83 | | def root(*load_paths) |
|---|
| 84 | | RootLoadingModule.new(*load_paths) |
|---|
| 85 | | end |
|---|
| 86 | | end |
|---|
| 87 | | |
|---|
| 88 | | def initialize(root, path=[]) |
|---|
| 89 | | @path = path.clone.freeze |
|---|
| 90 | | @root = root |
|---|
| 91 | | end |
|---|
| 92 | | |
|---|
| 93 | | def root?() self.root == self end |
|---|
| 94 | | def load_paths() self.root.load_paths end |
|---|
| 95 | | |
|---|
| 96 | | # Load missing constants if possible. |
|---|
| 97 | | def const_missing(name) |
|---|
| 98 | | const_load!(name) ? const_get(name) : super(name) |
|---|
| 99 | | end |
|---|
| 100 | | |
|---|
| 101 | | # Load the controller class or a parent module. |
|---|
| 102 | | def const_load!(name, file_name = nil) |
|---|
| 103 | | file_name ||= 'application' if root? && name.to_s == 'ApplicationController' |
|---|
| 104 | | path = self.path + [file_name || name] |
|---|
| 105 | | |
|---|
| 106 | | load_paths.each do |load_path| |
|---|
| 107 | | fs_path = load_path.filesystem_path(path) |
|---|
| 108 | | next unless fs_path |
|---|
| 109 | | |
|---|
| 110 | | case |
|---|
| 111 | | when File.directory?(fs_path) |
|---|
| 112 | | new_module = LoadingModule.new(self.root, self.path + [name]) |
|---|
| 113 | | self.const_set name, new_module |
|---|
| 114 | | if self.root? |
|---|
| 115 | | if Object.const_defined?(name) |
|---|
| 116 | | msg = "Cannot load module #{name}: Object::#{name} is set to #{Object.const_get(name).inspect}" |
|---|
| 117 | | raise NameError, msg |
|---|
| 118 | | end |
|---|
| 119 | | Object.const_set(name, new_module) |
|---|
| 120 | | end |
|---|
| 121 | | break |
|---|
| 122 | | when File.file?(fs_path) |
|---|
| 123 | | loaded_file = self.root.load_file!(fs_path) |
|---|
| 124 | | |
|---|
| 125 | | # Import the loaded constant from Object provided we are the root node. |
|---|
| 126 | | self.const_set(name, Object.const_get(name)) if self.root? && Object.const_defined?(name) |
|---|
| 127 | | |
|---|
| 128 | | # Throw an error if we load the file but we don't find the Object we expect |
|---|
| 129 | | if loaded_file and not self.const_defined?(name) |
|---|
| 130 | | msg = "Already loaded file '#{fs_path}' but '#{name.to_s}' was not set, perhaps you need to rename '#{fs_path}'?" |
|---|
| 131 | | raise LoadError, msg |
|---|
| 132 | | end |
|---|
| 133 | | break |
|---|
| 134 | | end |
|---|
| 135 | | end |
|---|
| 136 | | |
|---|
| 137 | | self.const_defined?(name) |
|---|
| 138 | | end |
|---|
| 139 | | |
|---|
| 140 | | # Is this name present or loadable? |
|---|
| 141 | | # This method is used by Routes to find valid controllers. |
|---|
| 142 | | def const_available?(name) |
|---|
| 143 | | self.const_defined?(name) || load_paths.any? {|lp| lp.filesystem_path(path + [name])} |
|---|
| 144 | | end |
|---|
| 145 | | |
|---|
| 146 | | # Erase all items in this module |
|---|
| 147 | | def clear! |
|---|
| 148 | | constants.each do |name| |
|---|
| 149 | | Object.send(:remove_const, name) if Object.const_defined?(name) && Object.const_get(name).object_id == self.const_get(name).object_id |
|---|
| 150 | | self.send(:remove_const, name) |
|---|
| 151 | | end |
|---|
| 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 | | end |
|---|
| 171 | | |
|---|
| 172 | | # This object defines a path from which Constants can be loaded. |
|---|
| 173 | | class ConstantLoadPath #:nodoc: |
|---|
| 174 | | # Create a new load path with the filesystem path |
|---|
| 175 | | def initialize(root) @root = root end |
|---|
| 176 | | |
|---|
| 177 | | # Return nil if the path does not exist, or the path to a directory |
|---|
| 178 | | # if the path leads to a module, or the path to a file if it leads to an object. |
|---|
| 179 | | def filesystem_path(path, allow_module=true) |
|---|
| 180 | | fs_path = [@root] |
|---|
| 181 | | fs_path += path[0..-2].map {|name| const_name_to_module_name name} |
|---|
| 182 | | |
|---|
| 183 | | if allow_module |
|---|
| 184 | | result = File.join(fs_path, const_name_to_module_name(path.last)) |
|---|
| 185 | | return result if File.directory? result # Return the module path if one exists |
|---|
| 186 | | end |
|---|
| 187 | | |
|---|
| 188 | | result = File.join(fs_path, const_name_to_file_name(path.last)) |
|---|
| 189 | | |
|---|
| 190 | | File.file?(result) ? result : nil |
|---|
| 191 | | end |
|---|
| 192 | | |
|---|
| 193 | | def const_name_to_file_name(name) |
|---|
| 194 | | name.to_s.underscore + '.rb' |
|---|
| 195 | | end |
|---|
| 196 | | |
|---|
| 197 | | def const_name_to_module_name(name) |
|---|
| 198 | | name.to_s.underscore |
|---|
| 199 | | end |
|---|