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:

  1. Module NameMagic is included in that class, as expected.

  2. Class is extended with NameMagic::ClassMethods.

  3. 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

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.)

Returns:

  • (Boolean)


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_nameObject

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

#inspectObject

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

#namespaceObject

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_sObject

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.

Returns:

  • (Boolean)


341
342
343
# File 'lib/y_support/name_magic.rb', line 341

def unnaming_allowed?
  ! self.class.permanent_names?
end