Module: NameMagic::Namespace

Defined in:
lib/y_support/name_magic/namespace.rb

Overview

Module methods for the modules serving as NameMagic namespaces. What is a NameMagic namespace? For a class that includes NameMagic, namespace is the “civil registry” of all instances, both named and nameless. For this purpose, namespace has variable @instances. The registry of instances is a hash of pairs { instance => name }. Nameless instances have nil value instead of name in the registry. In general Ruby, namespace would mean that the module holds the instances in constants looking like Namespace::Name. In Ruby core, we can see such behavior with Struct class, which has native constant magic and stores its instances in constants using Struct as a namespace. NameMagic used to perform this constant assignment in the namespace prior to YSupport version ~2.0. This is one of the reasons why names of instances must start with a capital letter and be usable as constant names. Since YSupport version 2.0+, NameMagic does no constant assignment on its own – all constant assignments are left to the user. In this way, NameMagic now sees constant assignment purely as a way to learn instance names intended by the user.

The instance registry is accessible via #instances method. Individual instances can be queried for by #instance method, eg. by their names. Until Matz provides the possibility of constant magic in every class, which I requested some time ago, the registry of instances will remain the essential part, without which +NameMagic wouldn’t work.

Life cycle of instances of NameMagic user classes

Let us consider for example Human class that uses NameMagic.

class Human

require 'y_support/name_magic'
include NameMagic

end

Life cycle of Human in instances of begins, unsurprisingly, by instantiation. All instances are created nameless:

newborn = Human.new newborn.name #=> nil

User has several ways of naming the instances. Typical for NameMagic is naming by constant assignment:

Fred = newborn newborn.name #=> :Fred

+NameMagic makes it possible to supply :name parameter directly to the #new method. Such instances are named immediately:

newborn = Human.new name: “Joe” newborn.name #=> :Joe

Another way is to name the instances using #name= method.

newborn = Human.new newborn.name #=> nil newborn.name = “Mike” newborn.name #=> Mike

Just for the record, we have created three instances:

Human.instances #=> [Fred, Joe, Mike]

In other words, at some point in their life, instances may or may not undergo baptism, which involves a complicated procedure of searching all existing Ruby modules for constants to which nameless instances of the class in question are assigned. Baptized instances then know their names, and can be accessed by their names through the instance registry:

Human.instance( “Mike” ) #=> Mike

Namespace gives the user 2 hook methods, #instantiation_exec and #exec_when_naming. The first one is executed upon instantiation and passed one argument, the new instance. The second one is executed when the namespace baptizes a new instance, and passed three arguments: suggested name, instance, and previous name if any. (Renaming instances may require special care.) Consequantly, you should define unary block with #instantiation_exec and ternary one with #exec_when_naming. Example:

Human.instantiation_exec do |instance|

puts "Instance with object id #{instance.object_id} created!"

end newborn = Human.new #=> Instance with object id 75756140 created!

The naming hook can also be used to censor and modify the intended name. Consider the following censorship:

Human.exec_when_naming do |name, instance, old_name|

fail NameError, "#{name.capitalize} is not a saint in the " +
  "Church of Emacs!" unless name.end_with? "gnucius"
"St_IGNUcius"

end

Now we can no longer use ordinary names, we have to use names of saints in the Church of Emacs!

newborn.name = “Dave” #=> NameError: Dave is not a saint in the Church of Emacs!

Name Ignucius is OK, but the censor corrects it to St_IGNUcius:

newborn.name = “Ignucius” #=> St_IGNUcius

Life cycle of an instance ends when it is deleted from the instance registry and garbage-collected (unless something else holds a reference to it). Example:

Human.instances #=> [Fred, Joe, Mike, St_IGNUcius] Human.forget “St_IGNUcius” Human.instances #=> [Fred, Joe, Mike]

St. IGNUcius has just been deleted from the registry.

Human.forget_all_instances Human.instances #=> []

All Human instances have now been deleted from the registry, but only Joe and Mike are garbage-collected, because Fred is still assigned to a constant. Fred still exists, but he and his name has been deleted from the instance registry.

Fred #=> #<Human:0x89449b0>

We could even re-register Fred by recreating his entry, although this is far from the way NameMagic works in everyday life.

Human.__instances__.merge! Fred => :Fred Human.instances #=> [Fred]

Avidity of the instances

After the offered name is checked and modified by the name set hook closure, there is one more remaining problem to worry about: Whether the name is already used by another instance in the same namespace. If the name is taken, the ensuing action depends on whether the instance being named is avid. Avid instances are so eager to get a name, that they will steal the offered name for themselves even if other instances already use the name, making the conflicting instance nameless in the process. In NameMagic, it turns out to be convenient to make the new instances avid by default, unless the name was explicitly supplied to the constructor by :name argument, or avidity suppressed by setting +:name_avid option to false.

Techincally, avid instances are registered as an array kept by the namespace under the variable @avid_instances.

Forgetting instances

As mentioned earlier, namespace can de-register, or forget instances. For this purpose, see methods #forget, +#__forget__, #forget_nameless_instances, #forget_all_instances.

Ersatz constant magic

To imitate built-in constant magic of some Ruby classes, NamespaceMethods provides ersatz method #const_magic, that searches all the modules in the object space for the pertinent instances newly assigned to constants. Method #const_magic is called automatically before executing almost every public method of NameMagic, thus keeping the “civil registry” up-to-date. While not exactly computationally efficient, it tends to make the user code more readable and pays off in most usecases. For efficiency, we are looking forward to the #const_assigned hook promised by Ruby core team…

The namespace method versions that do not perform ersatz constant magic are generally denoted by underlines: Eg. methods #__instances__ and #__forget__ do not perform constant magic, while #instances and #forget do.

Instance Method Summary collapse

Instance Method Details

#__avid_instances__Object

Avid instances registered in this namespace. (“Avid” means that the instance will steal (overwrite) a name from another instance, should there be a conflict. The method does not trigger #const_magic.



214
215
216
# File 'lib/y_support/name_magic/namespace.rb', line 214

def __avid_instances__
  @avid_instances ||= []
end

#__forget__(instance) ⇒ Object

Removes the specified instance from the registry, without performing #const_magic first. The argument should be a registered instance. Returns instance name for forgotten named instances, nil for forgotten nameless instances, and false if the argument was not a registered instance.



278
279
280
281
282
283
# File 'lib/y_support/name_magic/namespace.rb', line 278

def __forget__ instance
  return false unless __instances__.keys.include? instance
  # namespace.send :remove_const, instance.name if instance.name
  __avid_instances__.delete( instance )
  __instances__.delete instance
end

#__instances__Object

Presents namespace-owned @instances hash. The hash consists of pairs { instance => instance_name }. Unnamed instances have nil value instead of their name. This method does not trigger #const_magic.



205
206
207
# File 'lib/y_support/name_magic/namespace.rb', line 205

def __instances__
  @instances ||= {}
end

#const_magicObject

Searches all the modules in the the object space for constants referring to receiver class objects, and names the found instances accordingly. Internally, it works by invoking private procedure +#search_all_modules. The return value is the remaining number of nameless instances.



241
242
243
244
245
# File 'lib/y_support/name_magic/namespace.rb', line 241

def const_magic
  return 0 if nameless_instances.size == 0
  search_all_modules
  return nameless_instances.size
end

#exec_when_naming(&block) ⇒ Object Also known as: name_set_hook

Registers a block to execute just prior to naming of an instance. (In other words, this method provides user class’es naming hook.) The block will be executed in the context of the user class and will be supplied three ordered arguments: suggested name, instance, and previous name. The block should thus be written as ternary, expecting these three arguments. The block can be used to validate / censor the suggested name and for this reason, it should return the censored name that will actually be requested for the instance. (Of course, just like there is no duty to use this hook, if you do use it, there is likewise no duty to censor the suggested name in it. You can just return the suggested name unchanged from the block.) The point is that the return value of the block will actually be used to name the instance. If no block is given, the method returns the previously defined block, if any, or a default block that does nothing and returns the suggested name without any changes.



338
339
340
341
342
# File 'lib/y_support/name_magic/namespace.rb', line 338

def exec_when_naming &block
  @exec_when_naming = block if block
  @exec_when_naming ||=
    -> name, instance, previous_name=nil { name }
end

#exec_when_unnaming(&block) ⇒ Object

Registers a block to execute just prior to unnaming of an instance. (In other words, this method provides user class’es unnaming hook.) The block will be executed in the context of the user class and will be supplied two ordered arguments: instance and its previous name. The block can thus be written as up to binary. Return value of the block is unimportant. If no block is given, the method returns the block defined earlier, if any, or a default block that does nothing.



355
356
357
358
# File 'lib/y_support/name_magic/namespace.rb', line 355

def exec_when_unnaming &block
  @exec_when_unnaming = block if block
  @exec_when_unnaming ||= -> instance, previous_name=nil { }
end

#forget(instance, *args) ⇒ Object

Removes the specified instance from the registry. Note that this is different from “unnaming” an instance by setting inst.name = nil, which makes the instance anonymous, but still registered.



259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/y_support/name_magic/namespace.rb', line 259

def forget instance, *args
  instance = begin
               instance instance
             rescue ArgumentError
               return nil # nothing to forget
             end
  ɴ = instance.nil? ? nil : instance.name
  # namespace.send :remove_const, ɴ if ɴ
  __instances__.delete( instance )
  __avid_instances__.delete( instance )
  return instance
end

#forget_all_instancesObject

Clears references to all the instances.



297
298
299
300
301
302
# File 'lib/y_support/name_magic/namespace.rb', line 297

def forget_all_instances
  instances.map { |instance| __forget__ instance }
  # constants( false ).each { |sym|
  #   namespace.send :remove_const, sym if
  #     const_get( sym ).is_a? self }
end

#forget_nameless_instancesObject

Removes all anonymous instances from the registry.



287
288
289
290
291
292
293
# File 'lib/y_support/name_magic/namespace.rb', line 287

def forget_nameless_instances
  const_magic # #nameless_instances doesn't trigger it
  nameless_instances.each { |instance|
    __instances__.delete( instance )
    __avid_instances__.delete( instance )
  }
end

#instance(arg) ⇒ Object

Returns the instance identified by the argument.



220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/y_support/name_magic/namespace.rb', line 220

def instance arg
  # In @instances hash, nil value denotes nameless instances!
  fail TypeError,
       "Nil is not an instance identifier!" if arg.nil?
  # Get the list of all instances.
  ii = instances
  # If arg belongs to the list, just return it back.
  return arg if ii.any? { |i| arg.equal? i }
  # Assume that arg is an instance name.
  name = arg.to_sym
  registry = __instances__
  ii.find { |i| registry[ i ] == name } or
    fail NameError, "No instance #{arg} in #{self}!"
end

#instances(*args) ⇒ Object

Presents the instances registered in this namespace.



195
196
197
198
# File 'lib/y_support/name_magic/namespace.rb', line 195

def instances *args
  const_magic
  __instances__.keys
end

#instantiation_exec(&block) ⇒ Object Also known as: new_instance_hook

Registers a block to execute when a new instance of the NameMagic user class is created. (In other words, this method provides user class’es instantiation hook.) Expects a unary block, whose argument is the new instance. Return value of the block is unimportant. The block will be executed in the context of the user class. If no block is given, the method returns the previously defined block, if any, or a default block that does nothing.



313
314
315
316
# File 'lib/y_support/name_magic/namespace.rb', line 313

def instantiation_exec &block
  @instantiation_exec = block if block
  @instantiation_exec ||= -> instance { }
end

#nameless_instances(*args) ⇒ Object

Returns those instances, whose name is nil. This method does not trigger #const_magic.



250
251
252
# File 'lib/y_support/name_magic/namespace.rb', line 250

def nameless_instances *args
  __instances__.select { |key, val| val.nil? }.keys
end

#permanent_names!Object

Orders the namespace to disallow unnaming instances. As a consequence, the instances’ names will now be permanent.



182
183
184
# File 'lib/y_support/name_magic/namespace.rb', line 182

def permanent_names!
  @permanent_names = true
end

#permanent_names?Boolean

Inquirer whether unnaming instances has been disallowed in the namespace.

Returns:

  • (Boolean)


189
190
191
# File 'lib/y_support/name_magic/namespace.rb', line 189

def permanent_names?
  @permanent_names
end

#validate_name(name) ⇒ Object

Checks whether a name is acceptable as a constant name.



362
363
364
365
366
367
368
369
370
# File 'lib/y_support/name_magic/namespace.rb', line 362

def validate_name name
  n = name.to_s
  # rejecting non-capitalized names
  fail NameError unless ( ?A..?Z ) === n.chars.first
  # rejecting names with spaces
  fail NameError if n.chars.include? ' '
  # Return value is the validated name.
  return name
end