Dynamically Creating Class in Ruby

Creating a class dynamically

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>

Dynamically add a class to a module in Ruby

You may add a class for dynamically named module like in the following example:

app = Object.const_set('CoolModule', Module.new)

Object.const_get('CoolModule').
const_set('Application', Class.new(Rails::Application) do
config.blabla = 2
end)

If you have access to app object at point of call, it may replace Object.const_get('CoolModule') expression, of course.

Module.module_eval suggests itself but it unfortunately does not do scoped lookup in its block form. It does it in the string argument form, but I'd discourage use of evals on strings.

Create dynamically named class within module namespace

Object.const_set explicitly sets the constant in the Object namespace, which is the root namespace. If you use const_set without Object, it will set the constant in whatever the current namespace is, which is MyModule::SubModule in your example.

Dynamically creating class in Ruby

First off, part of the reason your example code isn't working for you is that you have two different @people variables - one is an instance variable and the other is a class instance variable.

class Example
# we're in the context of the Example class, so
# instance variables used here belong to the actual class object,
# not instances of that class
self.class #=> Class
self == Example #=> true
@iv = "I'm a class instance variable"

def initialize
# within instance methods, we're in the context
# of an _instance_ of the Example class, so
# instance variables used here belong to that instance.
self.class #=> Example
self == Example #=> false
@iv = "I'm an instance variable"
end
def iv
# another instance method uses the context of the instance
@iv #=> "I'm an instance variable"
end
def self.iv
# a class method, uses the context of the class
@iv #=> "I'm a class instance variable"
end
end

If you want to create variables one time in a class to use in instance methods of that class, use constants or class variables.

class Example
# ruby constants start with a capital letter. Ruby prints warnings if you
# try to assign a different object to an already-defined constant
CONSTANT_VARIABLE = "i'm a constant"
# though it's legit to modify the current object
CONSTANT_VARIABLE.capitalize!
CONSTANT_VARIABLE #=> "I'm a constant"

# class variables start with a @@
@@class_variable = "I'm a class variable"

def c_and_c
[ @@class_variable, CONSTANT_VARIABLE ] #=> [ "I'm a class variable", "I'm a constant" ]
end
end

Even so, in the context of your code, you probably don't want all your instances of Family_Type1 to refer to the same Policemen and Accountants right? Or do you?

If we switch to using class variables:

class Family_Type1
# since we're initializing @@people one time, that means
# all the Family_Type1 objects will share the same people
@@people = [ Policeman.new('Peter', 0), Accountant.new('Paul', 0), Policeman.new('Mary', 0) ]

def initialize(*ages)
@@people.zip(ages).each { |person, age| person.age = age }
end
# just an accessor method
def [](person_index)
@@people[person_index]
end
end
fam = Family_Type1.new( 12, 13, 14 )
fam[0].age == 12 #=> true
# this can lead to unexpected side-effects
fam2 = Family_Type1.new( 31, 32, 29 )
fam[0].age == 12 #=> false
fam2[0].age == 31 #=> true
fam[0].age == 31 #=> true

The runtime initialization can be done with metaprogramming, as Chirantan said, but if you are only initializing a few classes, and you know what their name is, you can also do it just by using whatever you read from the file:

PARAMS = File.read('params.csv').split("\n").map { |line| line.split(',') }
make_people = proc do |klasses, params|
klasses.zip(params).map { |klass,name| klass.new(name, 0) }
end
class Example0
@@people = make_people([ Fireman, Accountant, Fireman ], PARAMS[0])
end
class Example1
@@people = make_people([ Butcher, Baker, Candlestickmaker ], PARAMS[0])
end

Dynamically define named classes in Ruby

If you want to create a class with a dynamic name, you'll have to do almost exactly what you said. However, you do not need to use define_method. You can just pass a block to Class.new in which you initialize the class. This is semantically identical to the contents of class/end.

Remember with const_set, to be conscientious of the receiver (self) in that scope. If you want the class defined globally you will need to call const_set on the TopLevel module (which varies in name and detail by Ruby).

a_new_class = Class.new(Object) do
attr_accessor :x

def initialize(x)
print #{self.class} initialized with #{x}"
@x = x
end
end

SomeModule.const_set("ClassName", a_new_class)

c = ClassName.new(10)

...

Dynamic Class Definition WITH a Class Name

The name of a class is simply the name of the first constant that refers to it.

I.e. if I do myclass = Class.new and then MyClass = myclass, the name of the class will become MyClass. However I can't do MyClass = if I don't know the name of the class until runtime.

So instead you can use Module#const_set, which dynamically sets the value of a const. Example:

dynamic_name = "ClassName"
Object.const_set(dynamic_name, Class.new { def method1() 42 end })
ClassName.new.method1 #=> 42

Ruby: Dynamically create new classes

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"

Ruby: How to dynamically create a subclass of an existing class?

Class.new accepts a parameter, which will be the superclass.

Documentation: Class.new.

Dynamically defining a Object#initialize for a Ruby class

You were so close! instance_variable_set takes two arguments, first is the instance variable and second is the value you want to set it to. You also need to get the value of the variable, which you can do using send.

instance_variable_set(@#{arg.to_s}, send(arg.to_s))


Related Topics



Leave a reply



Submit