Creating a class dynamically

16,959

Solution 1

A class gains its name when it is assigned to a constant. So It's easy to do in a generic fashion with const_set.

For example, let's say you want to use Struct to build a class with some attributes, you can:

name = "Person"
attributes = [:name, :age]

klass = Object.const_set name, Struct.new(*attributes)
# Now use klass or Person or const_get(name) to refer to your class:
Person.new("John Doe", 42) # => #<struct Person name="John Doe", age=42>

To inherit from another class, replace the Struct.new by Class.new(MyBaseClass), say:

class MyBaseClass; end

klass = Class.new(MyBaseClass) do
  ATTRIBUTES = attributes
  attr_accessor *ATTRIBUTES
  def initialize(*args)
    raise ArgumentError, "Too many arguments" if args.size > ATTRIBUTES.size
    ATTRIBUTES.zip(args) do |attr, val|
      send "#{attr}=", val
    end
  end
end
Object.const_set name, klass
Person.new("John Doe", 42) # => #<Person:0x007f934a975830 @name="John Doe", @age=42> 

Solution 2

Your code would look something akin to this:

variable = "SomeClassName"
klass = Class.new(ParentClass)
# ...maybe evaluate some code in the context of the new, anonymous class
klass.class_eval {  }
# ...or define some methods
klass.send(:title, :Person)
klass.send(:attribute, :name, String)
# Finally, name that class!
ParentClass.send(:const_set, variable, klass)

...or you could just use eval:

eval <<DYNAMIC
  class #{name}
    title :Person
    attribute :name, String
    # ...or substitute other stuff in here.
  end
DYNAMIC
Share:
16,959
BSG
Author by

BSG

Django in-house developer.

Updated on June 01, 2022

Comments

  • BSG
    BSG about 2 years

    I'm trying to create a new class, without knowing the name of the class until it's supposed to be created.

    Something like this;

        variable = "ValidClassName"
        
            class variable
        
            end
    
    Test = ValidClassName.new
    

    If possible, i'd also appreciate som hints on how to dynamically add attributes (and methods) to this new class.

    I'll be retreiving 'settings' for the class, and they will look something like this:

    title :Person
    attribute :name, String
    attribute :age, Fixnum
    

    But should not be designed to accept only that explicit file, the attributes might differ in number end type.

    Which in the end will generate a class that should look something like:

    class Person
       def initialize(name, age)
     
           @name_out = name
           @age_out = age
       end
       
    end
    

    Help?

  • BSG
    BSG over 12 years
    Sorry, i'm really out of my depth here. Could you please try to explain it as if i was three years old..? :p
  • Daniel Pittman
    Daniel Pittman over 12 years
    I am not sure this gets that much easier. The second case is probably easier to understand: eval takes a string, and evaluates it at the time of the call as if it is Ruby code. So, you create a string with the source code for your dynamic class at runtime, then evaluate it. The first case creates a new class object, does things to it to create the desired methods, etc, and then gives it a name - which in Ruby is the same as assigning the class object to a constant.
  • Jorge de los Santos
    Jorge de los Santos almost 10 years
    I'm getting and error while declaring the klass, the name should be a constant, so I capitalized klass to Klass in 2, 3 lines and now it's working.