Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with anonymous modules in Ruby

Suppose I make a module as follows:

m = Module.new do
  class C
  end
end

Three questions:

  • Other than a reference to m, is there a way I can access C and other things inside m?

  • Can I give a name to the anonymous module after I've created it (just as if I'd typed "module ...")?

  • How do I delete the anonymous module when I'm done with it, such that the constants it defines are no longer present?

like image 470
Byron Park Avatar asked May 21 '10 15:05

Byron Park


2 Answers

Three answers:

  • Yes, using ObjectSpace. This code makes c refer to your class C without referencing m:

    c = nil  
    ObjectSpace.each_object { |obj|  
      c = obj if (Class === obj and obj.name =~ /::C$/)  
    }
    

    Of course this depends on there being no other classes named C anywhere in the program, but you get the idea.

  • Yes, sort of. If you just assign it to a constant, like M = m, then m.name will return "M" instead of nil, and references like M::C will work. Actually, when I do this and type M::C in irb, I get #<Module:0x9ed509c>::C, but maybe that's a bug.

  • I think it should be garbage collected once there are no references to it, i.e. when there are no instances or subtypes of m or C, and m is set to a different value or goes out of scope. If you assigned it to a constant as in the above point, you would need to change it to a different value too (though changing constants is generally ill-advised).
like image 132
wdebeaum Avatar answered Sep 22 '22 16:09

wdebeaum


Define a NamedModule

Once way to handle this is to define your own kind of module that can be initialized with a name.

class NamedModule < Module
  attr_accessor :name

  def initialize(name, &block)
    super(&block)
    self.name = name
  end

  def to_s
    [self.class.name, name, object_id].join(':')
  end
end

Then you can do this:

piracy = NamedModule.new("Piracy") do
  def berate
    puts "Yer a #{adjectives.sample} #{nouns.sample}!"
  end

  private

  def adjectives
    %w[yella-bellied landlubbing]
  end

  def nouns
    %w[scallywag bilge-drinker]
  end
end

Sailor = Class.new
Sailor.send(:include, piracy)
Sailor.new.berate #=> "Yer a yella-bellied scallywag!"

Defining to_s gives you nice output in ancestors:

Sailor.ancestors 
#=> [Sailor, NamedModule:Piracy:70169997844420, Object, Kernel, BasicObject]

Update - use the Named gem

After my colleague and I had experimented with this, he wrote a small gem implementation. Check out the Named gem - Rubygems and Github.

like image 34
Nathan Long Avatar answered Sep 21 '22 16:09

Nathan Long