3

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?

3 Answers 3

1

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"
Sign up to request clarification or add additional context in comments.

1 Comment

@description = self.class.name works with or without the parent class. That's a neat solution!
1

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

4 Comments

The description will be "Alpha" for all objects of class Alpha so it should be an attribute of the class.--Not necessarily...because changes to a class instance variable will affect what all instances see. On the other hand, if the variable is created inside initialize() then each instance will have its own variable, and it can be changed independently of the other instances.
True, but we're setting the description to the name of the class, so if that's the assertion the OP wants to enforce, it will be the same for all instances of the class. If the OP wants the flexibility to set the description of an instance to something else later on, then yes, your approach works better.
@mwp, True, but you don't ever need to set the name of an instance's class--that name is always available as self.class.name.
Maybe that's the real answer to the OP's question. :-)
0
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 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.