DreamWalker
DreamWalker

Reputation: 1387

Ruby: Dynamically create new classes

I'm trying to dynamically create a set of classes as follows.

class Foo
    attr_reader :description
end

['Alpha', 'Beta', 'Gamma'].each do |i|
  klass = Class.new(Foo) do |i|
    def initialize
      @description = i
    end
  end
  Object.const_set(i, klass)
end

rather than creating each class manually, e. g.:

class Alpha < Foo
  def initialize 
    @description = 'Alpha'
  end
end

What is the right way to do such a thing and how do I pass an iterator to a nested block?

Upvotes: 3

Views: 120

Answers (3)

7stud
7stud

Reputation: 48589

how do I pass an iterator to a nested block?

By using a nested block. A def is not a block. A def cuts off the visibility of variables outside the def. A block on the other hand can see the variables outside the block:

class Foo
    attr_reader :description
end

['Alpha', 'Beta', 'Gamma'].each do |class_name|
  klass = Class.new(Foo) do 
    define_method(:initialize) do
      @description = class_name
    end
  end

  Object.const_set(class_name, klass)
end

a = Alpha.new
p a.description

--output:--
"Alpha"

You can also do what you want without having to create a nested block or the class Foo:

['Alpha', 'Beta', 'Gamma'].each do |class_name|
  klass = Class.new() do
    def initialize
      @description = self.class.name
    end

    attr_reader :description

  end

  Object.const_set(class_name, klass)
end

--output:--
"Alpha"
"Gamma"

Upvotes: 1

Shiva
Shiva

Reputation: 12514

class Foo
  attr_reader :description
end

['Alpha', 'Beta', 'Gamma'].each do |class_name|
  eval %Q{
    class #{class_name} < Foo
      def initialize
        @description = #{class_name}
      end
    end
    }
end

On Execution:

Gamma.new.description
 => Gamma 

Upvotes: 0

mwp
mwp

Reputation: 8467

You're close. I think you want to make description a class instance variable (or possibly a class variable), rather than an instance variable. The description will be "Alpha" for all objects of class Alpha so it should be an attribute of the class. You would access it as Alpha.description (or Alpha.new.class.description). Here's a solution using a class instance variable:

class Foo
  class << self
    attr_reader :description
  end
end

['Alpha', 'Beta', 'Gamma'].each do |i|
  klass = Class.new(Foo)
  klass.instance_variable_set(:@description, i)

  Object.const_set(i, klass)
end

Upvotes: 1

Related Questions