Dynamic Class Definition WITH a Class Name

38,173

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.

Share:
38,173

Related videos on Youtube

Joshua Pinter
Author by

Joshua Pinter

Updated on July 05, 2022

Comments

  • Joshua Pinter
    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
      Philip over 13 years
      I 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
      Admin over 13 years
      It'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
      Jörg W Mittag over 13 years
      @Philip: class A ... end does not evaluate to nil, 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 to nil. But it is sometimes useful to have a class definition body evaluate to a specific value, e.g. in the class << self; self end idiom.
  • Admin
    Admin over 13 years
    Excellent! Thanks! That's exactly what I needed.
  • Isaac Betesh
    Isaac Betesh almost 10 years
  • Daniel Lubarov
    Daniel Lubarov over 9 years
    Wow. Seems very odd to me that (constant) assignment has this side effect.
  • Pistos
    Pistos almost 8 years
    It can be accomplished without eval, though. With eval, you have to sanitize the input to hedge against malicious code execution.
  • sandre89
    sandre89 over 7 years
    For some reason this works in development for me, but not in production
  • John Dvorak
    John Dvorak over 7 years
    I've tried to set the constant on the new class itself, to no avail. With Object it works. Thanks.
  • Earl Jenkins
    Earl Jenkins over 6 years
    Incidentally, you COULD just set the table name for the class explicitly, like this: self.table_name = "my_things"
  • mlt
    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.