Dynamic Class Definition WITH a Class Name

How do I dynamically define a class in Ruby WITH a name?

I know how to create a class dynamically without a name using something like:

dynamic_class = Class.new do
def method1
end
end

But you can't specify a class name. I want to create a class dynamically with a name.

Here's an example of what I want to do but of course it doesn't actually work.
(Note that I am not creating an instance of a class but a class definition)

class TestEval
def method1
puts "name: #{self.name}"
end
end


class_name = "TestEval"
dummy = eval("#{class_name}")


puts "dummy: #{dummy}"


dynamic_name = "TestEval2"
class_string = """
class #{dynamic_name}
def method1
end
end
"""
dummy2 = eval(class_string)
puts "dummy2: #{dummy2}" # doesn't work

Actual output:

dummy: TestEval
dummy2:

Desired output:

dummy: TestEval
dummy2: TestEval2

======================================================

Answer: A totally dynamic solution using sepp2k's method

dynamic_name = "TestEval2"


Object.const_set(dynamic_name, Class.new) # If inheriting, use Class.new( superclass )
dummy2 = eval("#{dynamic_name}")
puts "dummy2: #{dummy2}"
39895 次浏览

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

I've been messing around with this too. In my case I was trying to test extensions to ActiveRecord::Base. I needed to be able to dynamically create a class, and because active record looks up a table based on a class name, that class couldn't be anonymous.

I'm not sure if this helps your case, but here's what I came up with:

test_model_class = Class.new(ActiveRecord::Base) do
def self.name
'TestModel'
end


attr_accessor :foo, :bar
end

As far as ActiveRecord is concerned, defining self.name was enough. I'm guessing this will actually work in all cases where a class cannot be anonymous.

(I've just read sepp2k's answer and I'm thinking his is better. I'll leave this here anyway.)

How aboutthe following code:

dynamic_name = "TestEval2"
class_string = """
class #{dynamic_name}
def method1
end
end
"""
eval(class_string)
dummy2 = Object.const_get(dynamic_name)
puts "dummy2: #{dummy2}"

Eval doesn' retun the runtime Class object, at least on my PC it doesn't. Use Object.const_get to get the Class object.

I know this is a really old question, and some other Rubyists might shun me from the community for this, but I am working on creating a very thin wrapper gem that wraps a popular java project with ruby classes. Based on @sepp2k's answer, I created a couple helper methods because I had to do this many, many times in one project. Note that I namespaced these methods so that they were not polluting some top-level namespace like Object or Kernel.

module Redbeam
# helper method to create thin class wrappers easily within the given namespace
#
# @param  parent_klass [Class] parent class of the klasses
# @param  klasses [Array[String, Class]] 2D array of [class, superclass]
#   where each class is a String name of the class to create and superclass
#   is the class the new class will inherit from
def self.create_klasses(parent_klass, klasses)
parent_klass.instance_eval do
klasses.each do |klass, superklass|
parent_klass.const_set klass, Class.new(superklass)
end
end
end


# helper method to create thin module wrappers easily within the given namespace
#
# @param parent_klass [Class] parent class of the modules
# @param modules [Array[String, Module]] 2D array of [module, supermodule]
#   where each module is a String name of the module to create and supermodule
#   is the module the new module will extend
def self.create_modules(parent_klass, modules)
parent_klass.instance_eval do
modules.each do |new_module, supermodule|
parent_klass.const_set new_module, Module.new { extend supermodule }
end
end
end
end

To use these methods (note that this is JRuby):

module Redbeam::Options
Redbeam.create_klasses(self, [
['PipelineOptionsFactory', org.apache.beam.sdk.options.PipelineOptionsFactory]
])
Redbeam.create_modules(self, [
['PipelineOptions', org.apache.beam.sdk.options.PipelineOptions]
])
end

WHY??

This allows me to create a JRuby gem that uses the Java project and would allow the open source community and I to decorate these classes in the future, as necessary. It also creates a more friendly namespace to use the classes in. Since my gem is a very, very thin wrapper, I had to create many, many subclasses and modules to extend other modules.

As we say at J.D. Power, "this is apology-driven development: I'm sorry".