Module: NameMagic::NamespaceMethods

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

Overview

Module methods for the modules serving as NameMagic namespaces. A namespace for a certain class featuring NameMagic holds “civil registry” of all its instances, be they named or nameless. For this purpose, the namespace owns @instances hash of pairs { instance => name }, with nil values denoting nameless instances. For named instances, the namespace also holds references to them in constants in the style Namespace::Name. This is one of the reasons why instance names in NameMagic must start with a capital letter and generally must be usable as constant names. The list of instances is accessible via #instances method. Individual instances can be queried by #instance method, eg. by their names.

Life cycle of instances of classes featuring NameMagic

NameMagic offers 3 hooks for the instances of its user classes. These hooks are closures invoked at the relevant points of the instances’ life cycle:

  • new instance hook – when the instance is created

  • name set hook – when the instance is offered a name

  • name get hook – when the instance’s name is queried

These three hooks are stored in instance variables owned by the namespace, accesible by methods +#new_instance_hook, #name_set_hook and #name_get_hook. If called with a block, these methods also serve to set their respective hook closures.

When an instance is first created, unary new_instance_hook is called. When an instance is offered a name, name_set_hook is called. It is a ternary closure with 3 ordered arguments name, instance and old_name, receiving respectively the name offered, the instance, and the previous name of the instance (if any). The return value of the closure will be used to actually name the instance. This closure can thus be used to check and modify the names before they are actually used. Finally, when the instances’ name is queried, third closure, unary name_get_hook is applied to modify the name output. The purpose of the name get hook is not to really change the name upon reading, but mainly to tweak the preferred form or spelling where multiple forms of are possible for the same name. (For example, the standard form in the @instances hash could be in camel case, such as “AcinonyxJubatus”, while preferred querying output would be a binomial name with whitespaces, “Acinonyx jubatus”.)

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 even if it is already in use, 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

A 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 then called 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 is able to steal (overwrite) a name from another registered instance. (The method does not trigger #const_magic.)



109
110
111
# File 'lib/y_support/name_magic/namespace_methods.rb', line 109

def __avid_instances__
  @avid_instances ||= []
end

#__forget__(instance, *args) ⇒ Object

Clears namespace-owned references to an instance, without performing #const_magic first. The argument should be a registered instance. Returns the instance name, or false, if there was no such registered instance.



168
169
170
171
172
173
# File 'lib/y_support/name_magic/namespace_methods.rb', line 168

def __forget__( instance, *args )
  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 assigned to them as their name. (The method does not trigger #const_magic.)



101
102
103
# File 'lib/y_support/name_magic/namespace_methods.rb', line 101

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.



136
137
138
139
140
141
# File 'lib/y_support/name_magic/namespace_methods.rb', line 136

def const_magic
  puts "#{self}#const_magic invoked!" if ::NameMagic::DEBUG
  return 0 if nameless_instances.size == 0
  search_all_modules
  return nameless_instances.size
end

#forget(instance_identifier, *args) ⇒ Object

Clears namespace-owned references to a specified instance. (This is different from “unnaming” an instance by setting inst.name = nil, which makes the instance anonymous, but still registered.)



153
154
155
156
157
158
159
160
161
162
# File 'lib/y_support/name_magic/namespace_methods.rb', line 153

def forget instance_identifier, *args
  inst = begin; instance( instance_identifier ); rescue ArgumentError
           return nil # nothing to forget
         end
  ɴ = inst.nil? ? nil : inst.name
  namespace.send :remove_const, ɴ if ɴ   # clear constant assignment
  __instances__.delete( inst )           # remove @instances entry
  __avid_instances__.delete( inst )      # remove if any
  return inst                            # return the forgotten instance
end

#forget_all_instancesObject

Clears namespace-owned references to all the instances.



186
187
188
189
190
191
# File 'lib/y_support/name_magic/namespace_methods.rb', line 186

def forget_all_instances
  __instances__.clear
  constants( false ).each { |sym|
    namespace.send :remove_const, sym if const_get( sym ).is_a? self
  }
end

#forget_nameless_instancesObject

Clears namespace-owned references to all the anonymous instances.



177
178
179
180
181
182
# File 'lib/y_support/name_magic/namespace_methods.rb', line 177

def forget_nameless_instances
  nameless_instances.each { |inst|
    __instances__.delete( inst )
    __avid_instances__.delete( inst ) # also from avid instances
  }
end

#instance(id, *args) ⇒ Object

Returns the instance identified by the argument, which can be typically a name (string/symbol). If a registered instance is supplied, it will be returned unchanged.



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/y_support/name_magic/namespace_methods.rb', line 117

def instance id, *args
  # puts "#instance( #{identifier} )" if DEBUG
  # In @instances hash, value 'nil' indicates a nameless instance!
  fail TypeError, "'nil' is not an instance identifier!" if id.nil?
  ii = instances
  return id if ii.include? id # return the instance back
  begin # identifier not a registered instance -- treat it as a name
    ary = [id, id.to_sym]
    ihsh = __instances__
    ii.find { |inst| ary.include? ihsh[ inst ] or ary.include? inst.name }
  rescue NoMethodError
  end or fail NameError, "No instance #{id} in #{self}."
end

#instance_namesObject

Deprecated method to get full names of the named instances.



91
92
93
94
# File 'lib/y_support/name_magic/namespace_methods.rb', line 91

def instance_names
  warn "Method #instance_names is deprecated. Use 'instances._names_' or 'instances.names' instead!"
  instances.names false
end

#instances(*args) ⇒ Object

Presents the instances registered in this namespace.



84
85
86
87
# File 'lib/y_support/name_magic/namespace_methods.rb', line 84

def instances *args
  const_magic
  __instances__.keys
end

#name_get_hook(&block) ⇒ Object

Registers a hook to execute whenever the instance is asked its name. The instance names are objects that are kept in a hash referred to by @instances variable owned by the namespace. Normally, NameMagic#name simply returns the name of the instance, as found in the @instances hash. When name_get_hook is defined, this name is transformed by it before being returned. Without a block, this method acts as a getter.



223
224
225
226
# File 'lib/y_support/name_magic/namespace_methods.rb', line 223

def name_get_hook &block
  @name_get_hook = block if block
  @name_get_hook ||= -> name { name }
end

#name_set_hook(&block) ⇒ Object

Registers a hook to execute upon instance naming. Expects a ternary block, with arguments name, instance and old_name, representing respectively the the requested name, the instance to be named, and the previous name of that instance (if any). The output of the block should be the name to actually be used. In other words, the hook can be used (among other things) to check and/or modify the requested name when christening the instance. It is the responsibility of this block to output a symbol that can be used as a Ruby constant name. Without a block, this method acts as a getter.



211
212
213
214
# File 'lib/y_support/name_magic/namespace_methods.rb', line 211

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

#nameless_instances(*args) ⇒ Object

Returns those instances, which are nameless (whose name is set to nil).



145
146
147
# File 'lib/y_support/name_magic/namespace_methods.rb', line 145

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

#new_instance_hook(&block) ⇒ Object

Registers a hook to execute upon instantiation. Expects a unary block, whose argument represents the new instance. It is called right after instantiation, but before naming the instance. Without a block, it acts as a getter.



197
198
199
200
# File 'lib/y_support/name_magic/namespace_methods.rb', line 197

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

#validate_name(name) ⇒ Object

Checks whether a name is acceptable as a constant name.



230
231
232
233
234
235
# File 'lib/y_support/name_magic/namespace_methods.rb', line 230

def validate_name name
  name.to_s.tap do |ɴ| # check if the name starts with 'A'..'Z'
    fail NameError, "#{self}-registered name must start with a capital " +
      " letter 'A'..'Z' ('#{ɴ}' given)!" unless ( ?A..?Z ) === ɴ[0]
  end
end