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
Check If String Contains Any Substring in an Array in Ruby
In Ruby/Rails, How to Encode/Escape Special Characters in Urls
How to Split a String into Only Two Parts, by the Last Occurrence of the Split Char
Spork 0.9.2 and Rspec 3.0.0 = Uninitialized Constant Rspec::Core::Commandline (Nameerror)
Active Admin Scopes for Each Instance of a Related Model
Ruby - Activerecord::Connectionnotestablished
What Does the Regular Expression [\W-] Mean
Rails: Pg::Insufficientprivilege: Error: Permission Denied for Relation Schema_Migrations
How to Install Ruby 1.9.3 on Ubuntu Without Rvm
If Else Statements in .Html.Erb in Views
Do Ruby 1.8 and 1.9 Have the Same Hash Code for a String
Use Global or Constant Variable in Ruby/Rails
Connecting Using Https to a Server with a Certificate Signed by a Ca I Created