Class: Tap::Env::Constant
- Inherits:
-
Object
- Object
- Tap::Env::Constant
- Defined in:
- lib/tap/env/constant.rb
Overview
A Constant serves as a placeholder for an actual constant, sort of like autoload. Use the constantize method to retrieve the actual constant; if it doesn’t exist, constantize requires require_path and tries again.
Object.const_defined?(:Net) # => false
$".include?('net/http') # => false
http = Constant.new('Net::HTTP', 'net/http.rb')
http.constantize # => Net::HTTP
$".include?('net/http.rb') # => true
Unloading
Constant also supports constant unloading. Unloading can be useful in various development modes, but may cause code to behave unpredictably. When a Constant unloads, the constant value is removed from the nesting constant and the require paths are removed from $“. This allows a require statement to re-require, and in theory, reload the constant.
# [simple.rb]
# class Simple
# end
const = Constant.new('Simple', 'simple')
const.constantize # => Simple
Object.const_defined?(:Simple) # => true
const.unload # => Simple
Object.const_defined?(:Simple) # => false
const.constantize # => Simple
Object.const_defined?(:Simple) # => true
Unloading and reloading works best for scripts that have no side effects; ie scripts that do not require other files and only define the specified class or module.
–
Rationale for that last statement
Scripts that require other files will not re-require the other files because unload doesn’t remove the other files from $“. Likewise scripts that define other constants effectively overwrite the existing constant; that may or may not be a big deal, but it can cause warnings. Moreover, if a script actually DOES something (like create a file), that something will be repeated when it gets re-required.
Constant Summary collapse
- CONST_REGEXP =
Matches a valid constant
/\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/
Instance Attribute Summary collapse
-
#const_name ⇒ Object
readonly
The full constant name.
-
#require_paths ⇒ Object
readonly
An array of paths that will be required when the constantize is called and the constant does not exist.
-
#types ⇒ Object
readonly
A hash of (type, summary) pairs used to classify self.
Class Method Summary collapse
-
.constantize(const_name, const = Object) ⇒ Object
Constantize tries to look up the specified constant under const.
-
.scan(dir, pattern = "**/*.rb", constants = {}) ⇒ Object
Scans the directory and pattern for constants and adds them to the constants hash by name.
Instance Method Summary collapse
-
#==(another) ⇒ Object
True if another is a Constant with the same const_name, require_path, and comment as self.
-
#basename ⇒ Object
Returns the basename of path.
-
#constantize(autorequire = true) ⇒ Object
Looks up and returns the constant indicated by const_name.
-
#dirname ⇒ Object
Returns the path, minus the basename of path.
-
#initialize(const_name, *require_paths) ⇒ Constant
constructor
Initializes a new Constant with the specified constant name, require_path, and comment.
-
#inspect ⇒ Object
Returns a string like:.
-
#name ⇒ Object
Returns the name of the constant, minus nesting.
-
#nesting ⇒ Object
Returns the nesting constant of const_name.
-
#nesting_depth ⇒ Object
Returns the number of constants in nesting.
-
#path ⇒ Object
Returns the underscored const_name.
-
#register_as(type, summary = nil, override = false) ⇒ Object
Registers the type and summary with self.
-
#to_s ⇒ Object
Returns const_name.
-
#unload(unrequire = true) ⇒ Object
Undefines the constant indicated by const_name.
Constructor Details
#initialize(const_name, *require_paths) ⇒ Constant
Initializes a new Constant with the specified constant name, require_path, and comment. The const_name should be a valid constant name.
148 149 150 151 152 |
# File 'lib/tap/env/constant.rb', line 148 def initialize(const_name, *require_paths) @const_name = const_name @require_paths = require_paths @types = {} end |
Instance Attribute Details
#const_name ⇒ Object (readonly)
The full constant name
136 137 138 |
# File 'lib/tap/env/constant.rb', line 136 def const_name @const_name end |
#require_paths ⇒ Object (readonly)
An array of paths that will be required when the constantize is called and the constant does not exist. Require paths are required in order.
140 141 142 |
# File 'lib/tap/env/constant.rb', line 140 def require_paths @require_paths end |
#types ⇒ Object (readonly)
A hash of (type, summary) pairs used to classify self.
143 144 145 |
# File 'lib/tap/env/constant.rb', line 143 def types @types end |
Class Method Details
.constantize(const_name, const = Object) ⇒ Object
Constantize tries to look up the specified constant under const. A block may be given to manually look up missing constants; the last existing const and any non-existant constant names are yielded to the block, which is expected to return the desired constant. For instance in the example ‘Non::Existant’ is essentially mapping to ConstName.
module ConstName; end
Constant.constantize('ConstName') # => ConstName
Constant.constantize('Non::Existant') { ConstName } # => ConstName
Raises a NameError for invalid/missing constants.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/tap/env/constant.rb', line 68 def constantize(const_name, const=Object) # :yields: const, missing_const_names unless CONST_REGEXP =~ const_name raise NameError, "#{const_name.inspect} is not a valid constant name!" end constants = $1.split(/::/) while !constants.empty? unless const_is_defined?(const, constants[0]) if block_given? return yield(const, constants) else raise NameError.new("uninitialized constant #{const_name}", constants[0]) end end const = const.const_get(constants.shift) end const end |
.scan(dir, pattern = "**/*.rb", constants = {}) ⇒ Object
Scans the directory and pattern for constants and adds them to the constants hash by name.
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 |
# File 'lib/tap/env/constant.rb', line 89 def scan(dir, pattern="**/*.rb", constants={}) if pattern.include?("..") raise "patterns cannot include relative paths: #{pattern.inspect}" end # note changing dir here makes require paths relative to load_path, # hence they can be directly converted into a default_const_name # rather than first performing Root.relative_path Dir.chdir(dir) do Dir.glob(pattern).each do |path| default_const_name = path.chomp(File.extname(path)).camelize # scan for constants Lazydoc::Document.scan(File.read(path)) do |const_name, type, summary| const_name = default_const_name if const_name.empty? constant = (constants[const_name] ||= Constant.new(const_name)) constant.register_as(type, summary) constant.require_paths << path end end end constants end |
Instance Method Details
#==(another) ⇒ Object
True if another is a Constant with the same const_name, require_path, and comment as self.
204 205 206 207 208 |
# File 'lib/tap/env/constant.rb', line 204 def ==(another) another.kind_of?(Constant) && another.const_name == self.const_name && another.require_paths == self.require_paths end |
#basename ⇒ Object
166 167 168 |
# File 'lib/tap/env/constant.rb', line 166 def basename @basename ||= File.basename(path) end |
#constantize(autorequire = true) ⇒ Object
Looks up and returns the constant indicated by const_name. If the constant cannot be found, constantize requires the require_paths in order and tries again.
Raises a NameError if the constant cannot be found.
226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/tap/env/constant.rb', line 226 def constantize(autorequire=true) Constant.constantize(const_name) do break unless autorequire require_paths.each do |require_path| require require_path end Constant.constantize(const_name) end end |
#dirname ⇒ Object
174 175 176 |
# File 'lib/tap/env/constant.rb', line 174 def dirname @dirname ||= (dirname = File.dirname(path)) == "." ? "" : dirname end |
#inspect ⇒ Object
Returns a string like:
"#<Tap::Env::Constant:object_id Const::Name (require_path)>"
267 268 269 |
# File 'lib/tap/env/constant.rb', line 267 def inspect "#<#{self.class}:#{object_id} #{const_name} #{require_paths.inspect}>" end |
#name ⇒ Object
182 183 184 |
# File 'lib/tap/env/constant.rb', line 182 def name @name ||= (const_name =~ /.*::(.*)\z/ ? $1 : const_name) end |
#nesting ⇒ Object
190 191 192 |
# File 'lib/tap/env/constant.rb', line 190 def nesting @nesting ||= (const_name =~ /(.*)::.*\z/ ? $1 : '') end |
#nesting_depth ⇒ Object
198 199 200 |
# File 'lib/tap/env/constant.rb', line 198 def nesting_depth @nesting_depth ||= nesting.split(/::/).length end |
#path ⇒ Object
158 159 160 |
# File 'lib/tap/env/constant.rb', line 158 def path @path ||= const_name.underscore end |
#register_as(type, summary = nil, override = false) ⇒ Object
Registers the type and summary with self. Raises an error if self is already registerd as the type and override is false.
212 213 214 215 216 217 218 219 |
# File 'lib/tap/env/constant.rb', line 212 def register_as(type, summary=nil, override=false) if types.include?(type) && !override raise "already registered as a #{type.inspect}" end types[type] = summary self end |
#to_s ⇒ Object
Returns const_name
272 273 274 |
# File 'lib/tap/env/constant.rb', line 272 def to_s const_name end |
#unload(unrequire = true) ⇒ Object
Undefines the constant indicated by const_name. The nesting constants are not removed. If specified, the require_paths will be removed from $“.
When removing require_path, unload will add ‘.rb’ to the require_path if require_path has no extension (this echos the behavior of require). Other extension names like ‘.so’, ‘.dll’, etc. are not tried and will not be removed.
Does nothing if const_name doesn’t exist. Returns the unloaded constant. Obviously, this method should be used with caution.
248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/tap/env/constant.rb', line 248 def unload(unrequire=true) const = nesting.empty? ? Object : Constant.constantize(nesting) { Object } if const.const_defined?(name) require_paths.each do |require_path| path = File.extname(require_path).empty? ? "#{require_path}.rb" : require_path $".delete(path) end if unrequire return const.send(:remove_const, name) end nil end |