Module: NameMagic
- Defined in:
- lib/y_support/name_magic.rb
Overview
Module NameMagic imitates Ruby constant magic and automates the named argument :name, alias :ɴ (Character “ɴ”, Unicode small capital N). At the same time, NameMagic provides the registry of instances of the user class. In Ruby, we frequently want to keep a list of instances of some classes, and NameMagic also helps with this task. Simple example:
require 'y_support/name_magic'
class Foo
include NameMagic
end
Bar = Foo.new
The resulting object will know its #name
:
Bar.name #=> "Bar"
We can get the list of instances by:
Foo.instances #=> [Bar]
Additionally, NameMagic provides two hooks: Instantiation hook activates when a new instance is created, and naming hook activates when the created instance is baptized. Let us set the first hook, for example, as follows:
Foo.instantiation_hook do |instance|
puts "New instance of Foo with object id " +
"#{instance.object_id} created!"
end
Now let us set the second hook as follows:
Foo.naming_hook do |name, instance|
puts "Instance with object id #{instance.object_id} " +
"is baptized #{name}!"
name
end
Note that the naming hook must always return name. (This can be used to censor the name on the fly, but that’s beyond this basic intro.) Now that we set the hooks, let us observe when do they activate:
i = Foo.new
New instance of Foo with object id 73054440 created!
Baz = i
Instance with object id 73054440 is baptized Baz!
We can see that the registry of instances now registers two instances:
Foo.instances #=> [Bar, Baz]
NameMagic has been programmed to be simple and intuitive to use, so with this little demonstration, you can start using it without fear. You can find the rest of the methods provided by NameMagic in the documentation.
However, behind the scenes, inner workings of NameMagic require understanding. The key part of NameMagic is the code that searches Ruby namespace for constants. This search is done automatically when necessary. It can be also explicitly initiated by calling .const_magic class method, but this is rarely needed. When you write “include NameMagic” in some class, three things happen:
-
Module NameMagic is included in that class, as expected.
-
Class is extended with NameMagic::ClassMethods.
-
Namespace is extended with NameMagic::NamespaceMethods.
Namespace (in the NameMagic sense) is a module that holds the list of instances and their names. Typically, the user class acts as its own namespace. Note that the meaning of the word ‘namespace’ is somewhat different from its meaning in general Ruby. NameMagic actually provides class method .namespace that returns the namespace of the class. Consider this example:
require 'y_support/name_magic'
class Animal
include NameMagic
end
Animal.namespace #=> Animal
We can see that the namespace of Animal class is again Animal class. But when we subclass it:
class Dog < Animal; end
class Cat < Animal; end
Dog.namespace #=> Animal
Cat.namespace #=> Animal
The subclasses retain Animal as their namespace. Let us continue with the example:
Livia = Cat.new
Cat.instances #=> [Livia]
Dog.instances #=> []
Animal.instances #=> [Livia]
Let us demonstrate alternative ways of creating named objects:
Dog.new name: :Spot
Dog.new ɴ: "Rover"
Cat.instances #=> [Livia]
Dog.instances #=> [Spot, Rover]
Animal.instances #=> [Livia, Spot, Rover]
Make the subclasses be their own namespaces with +#namespace!:
Dog.namespace!
NameMagic also provides another way of naming objects by taking care of :name (alias :ɴ) parameter of #new constructor:
Dog.new name: "Spot"
Dog.new.name = :Rover
Dog.instances._names_ #=> [:Spot, :Rover]
Animal.instances._names_ #=> []
Note that the Dog instances above did not disappear even though we did not assign them to any variables or constants. This is because the instances of the classes using NameMagic, whether named or not, are since their creation referred to from the instance registry, which prevents them from being garbage collected. To get rid of Spot and Rover, we would have to delete them from the instance registry:
Dog.forget "Spot"
Dog.forget "Rover"
Spot and Rover show their inspect string for the last time and are garbage collected.
Defined Under Namespace
Modules: ArrayMethods, ClassMethods, HashMethods, Namespace
Class Method Summary collapse
Instance Method Summary collapse
-
#__name__ ⇒ Object
Retrieves the instance name.
-
#_name_ ⇒ Object
(also: #ɴ, #name)
Retrieves the demodulized instance’s name as a symbol.
-
#avid? ⇒ Boolean
Is the instance avid? (“Avid” means that the instance is so eager to get a name that it will use name even if this is already in use by another instance.).
-
#exec_when_named(&block) ⇒ Object
(also: #name_set_hook)
Registers a block to execute as soon as the instance is named.
-
#exec_when_unnamed(&block) ⇒ Object
Registers a block to execute right after the instance is unnamed.
-
#full_name ⇒ Object
Returns the instance’s full name, a string in the style of those returned by Module#name method, eg.
-
#inspect ⇒ Object
Default
#inspect
method forNameMagic
includers. -
#make_not_avid! ⇒ Object
Make the instance not avid.
-
#name!(name) ⇒ Object
Names the receiver aggresively.
-
#name=(name) ⇒ Object
Names the receiver, while rejecting names already in use by another instance.
-
#namespace ⇒ Object
The namespace of the instance’s class.
-
#to_s ⇒ Object
NameMagic
redefines #to_s method to show names. -
#unname! ⇒ Object
Unnames the instance.
-
#unnaming_allowed? ⇒ Boolean
Is unnaming of the instance allowed? Note: This method just relies on class’es .permanent_names? method.
Class Method Details
.included(target) ⇒ Object
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 |
# File 'lib/y_support/name_magic.rb', line 151 def self.included target if target.is_a? Class then # Define target.namespace method. target.singleton_class.class_exec do # Primer that sets the namespace of the class to self if # the user has not defined otherwise when this method is # first called. # define_method :namespace do # The method first extends target with Namespace methods. target.extend NameMagic::Namespace # The method then redefines itself. define_singleton_method :namespace do target end # And finally calls the redefined version of itself. namespace end end # Prepend NameMagic::ClassMethod to class << target. target.singleton_class.class_exec do prepend NameMagic::ClassMethods end else # Target is a Module, infect it with this #include original_included_method = target.method :included this_method = method :included target.define_singleton_method :included do |target| this_method.( target ) original_included_method.( target ) end end end |
Instance Method Details
#__name__ ⇒ Object
Retrieves the instance name. Does not trigger #const_magic before doing so.
225 226 227 |
# File 'lib/y_support/name_magic.rb', line 225 def __name__ self.class.__instances__[ self ] end |
#_name_ ⇒ Object Also known as: ɴ, name
Retrieves the demodulized instance’s name as a symbol. “Demodulized” means that if the full name is “Foo::Bar”, only :Bar is returned. Underlines (#name
) distinguish this method from #name
method, which returns full name string for compatibility with vanilla Ruby Module#name.
194 195 196 197 |
# File 'lib/y_support/name_magic.rb', line 194 def _name_ self.class.const_magic __name__ or ( yield self if block_given? ) end |
#avid? ⇒ Boolean
Is the instance avid? (“Avid” means that the instance is so eager to get a name that it will use name even if this is already in use by another instance.)
326 327 328 |
# File 'lib/y_support/name_magic.rb', line 326 def avid? namespace.__avid_instances__.any? &method( :equal? ) end |
#exec_when_named(&block) ⇒ Object Also known as: name_set_hook
Registers a block to execute as soon as the instance is named. (In other words, this method provides instance’s naming hook.) The block is executed in the context of the instance. Return value of the block is unimportant. If no block is given, the method returns the previously defined block, if any, or a default block that does nothing.
352 353 354 355 |
# File 'lib/y_support/name_magic.rb', line 352 def exec_when_named &block @exec_when_named = block if block @exec_when_named ||= -> { } end |
#exec_when_unnamed(&block) ⇒ Object
Registers a block to execute right after the instance is unnamed. (In other words, this method provides instance’s unnaming hook.) The block is executed in the context of the instance. Return value of the block is unimportant. If no block is given, the method returns the previously defined block, if any, or a default block that does nothing.
366 367 368 369 |
# File 'lib/y_support/name_magic.rb', line 366 def exec_when_unnamed &block @exec_when_unnamed = block if block @exec_when_unnamed ||= -> { } end |
#full_name ⇒ Object
Returns the instance’s full name, a string in the style of those returned by Module#name method, eg. “Namespace::Name”.
206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/y_support/name_magic.rb', line 206 def full_name # FIXME: This method cannot work until Namespace#const_magic # starts noticing not just constant names, as it does now, # but also names of the modules those constants are in. This # is no simple task. # # The code below is the closest approximation, yet still # patently wrong. [ namespace.name || namespace.inspect, namespace.instances[ self ] ].join "::" end |
#inspect ⇒ Object
Default #inspect
method for NameMagic
includers.
382 383 384 |
# File 'lib/y_support/name_magic.rb', line 382 def inspect to_s end |
#make_not_avid! ⇒ Object
Make the instance not avid.
332 333 334 335 |
# File 'lib/y_support/name_magic.rb', line 332 def make_not_avid! namespace.__avid_instances__.delete_if { |i| equal? i } return nil end |
#name!(name) ⇒ Object
Names the receiver aggresively. “Aggresively” means that in case the requested new name of the instance is already in use by another instance, the other instance is unnamed. In other words, in case of conflict of a name, the name is stolen from the conflicting instance, which becomes unnamed as a result. (The method does not trigger const_magic.)
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 |
# File 'lib/y_support/name_magic.rb', line 264 def name!( name ) # If the argument is nil, the method performs unnaming. return unname! if name.nil? # Otherwise, the method performs aggresive naming the instance, # where "aggresive" means that in case of conflict over a name, # the name is stolen from the conflicting instance. # Let us look at the current name of the instance first. previous_name = namespace.__instances__[ self ] # Honor class'es #exec_when_naming hook. requested_new_name = honor_exec_when_naming( name ) # Return if the instance is already named as requested. return if previous_name == requested_new_name # See if the requested name is already taken. colliding_entry = namespace.__instances__.rassoc( requested_new_name ) # Unname the colliding instance, if any. begin colliding_entry.first.unname! if colliding_entry ensure # Unnaming the colliding instance may fail. But whether it # fails or succeeds, self must quit being avid. If the # unnaming succeeded, self is getting a new name and thus # no longer needs to be avid. However, if the unnaming # raises an error, we want to avoid seeing the same error # over and over again just because avid self assigned to # a constant with conflicting name tries over and over again # to perform the impossible feat of stealing that name. make_not_avid! end # Rename self by modifying the registry. namespace.__instances__.update self => requested_new_name # Honor instance's #exec_when_named hook. honor_exec_when_named # Return the receiver. return self end |
#name=(name) ⇒ Object
Names the receiver, while rejecting names already in use by another instance. If nil is supplied as an argument, unnames the instance. (This method does not trigger const_magic.)
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/y_support/name_magic.rb', line 233 def name=( name ) # If the argument is nil, the method performs unnaming. return unname! if name.nil? # Otherwise, the method performs naming the instance, while # avoiding stealing names already in use by another instance. # Let us look at the current name of the instance first. previous_name = namespace.__instances__[ self ] # Honor class'es #exec_when_naming hook. requested_new_name = honor_exec_when_naming( name ) # Return if the instance is already named as requested. return if previous_name == requested_new_name # Raise error if the requested name is already taken. self.class.__instances__.rassoc( requested_new_name ) and fail NameError, "Name '#{requested_new_name}' already " + "exists in #{namespace} namespace!" # Now it is sure that the instance will be named, so it does # not need to be avid. make_not_avid! # Rename self by modifying the registry. namespace.__instances__.update self => requested_new_name # Honor instance's #exec_when_named hook. honor_exec_when_named end |
#namespace ⇒ Object
The namespace of the instance’s class.
184 185 186 |
# File 'lib/y_support/name_magic.rb', line 184 def namespace self.class.namespace end |
#to_s ⇒ Object
NameMagic
redefines #to_s method to show names. name.
376 377 378 |
# File 'lib/y_support/name_magic.rb', line 376 def to_s name ? name.to_s : super end |
#unname! ⇒ Object
Unnames the instance. Does not trigger #const_magic.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/y_support/name_magic.rb', line 303 def unname! # Get the current name of the instance. name = namespace.__instances__[ self ] # If the instance is anonymous, we are done. return if name.nil? # Check whether unnaming instances is allowed at all. fail NameError, "Unnaming and naming by a name already in " + "use by another instance has been disallowed!" unless unnaming_allowed? # Honor class'es #exec_when_unnaming hook. honor_exec_when_unnaming # Unname the instance by deleting the name from the registry. namespace.__instances__.update( self => nil ) # Honor instance's #exec_when_unnamed hook. honor_exec_when_unnamed # Return value is the previous name. return name end |
#unnaming_allowed? ⇒ Boolean
Is unnaming of the instance allowed? Note: This method just relies on class’es .permanent_names? method. Unnaming is not allowed when .permanent_names? is true.
341 342 343 |
# File 'lib/y_support/name_magic.rb', line 341 def unnaming_allowed? ! self.class.permanent_names? end |