Dynamic Class Definition WITH a Class Name
Solution 1
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
Solution 2
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.)
Solution 3
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".
Solution 4
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.
Related videos on Youtube
Joshua Pinter
Updated on July 05, 2022Comments
-
Joshua Pinter almost 2 years
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}"
-
Philip over 13 yearsI don't really get what you want to accomplish. There is a class TestEval2, you can do test_eval2 = TestEval2.new afterwards. And: class A ... end always yields nil, so your output is ok I guess ;-)
-
Admin over 13 yearsIt's for a TDD test step. I need to create a test class dynamically and then reference its name because that's how it will be used in the wild. sepp2K got it right.
-
Jörg W Mittag over 13 years@Philip:
class A ... end
does not evaluate tonil
, it evaluates to the value of the last expression evaluated inside it, just like every other compound expression (blocks, methods, module definitions, expression groups) in Ruby. It just so happens that in many class definition bodies, the last expression is a method definition expression, which evaluates tonil
. But it is sometimes useful to have a class definition body evaluate to a specific value, e.g. in theclass << self; self end
idiom.
-
-
Admin over 13 yearsExcellent! Thanks! That's exactly what I needed.
-
Isaac Betesh almost 10 yearsThank you. This helped me here: github.com/validates-email-format-of/validates_email_format_of/…
-
Daniel Lubarov over 9 yearsWow. Seems very odd to me that (constant) assignment has this side effect.
-
Pistos almost 8 yearsIt can be accomplished without
eval
, though. Witheval
, you have to sanitize the input to hedge against malicious code execution. -
sandre89 over 7 yearsFor some reason this works in development for me, but not in production
-
John Dvorak over 7 yearsI've tried to set the constant on the new class itself, to no avail. With Object it works. Thanks.
-
Earl Jenkins over 6 yearsIncidentally, you COULD just set the table name for the class explicitly, like this:
self.table_name = "my_things"
-
mlt over 4 years@EarlJenkins If there are associations defined within such class (e.g. belongs_to),
self.name
is necessary otherwise#demodulize
will fail on nil while trying to follow association :( At least that is what happened to me with Rails 5.2.3.