web-dev-qa-db-ja.com

クラス名を使用した動的クラス定義

名前を使用してRubyのクラスを動的に定義するにはどうすればよいですか?

私はクラスを動的に作成する方法を知っていますなし

dynamic_class = Class.new do
  def method1
  end
end

ただし、クラス名を指定することはできません。クラスを動的に作成したいwith名前。

ここに私がやりたいことの例がありますが、もちろん実際には機能しません。
(クラスのインスタンスではなく、クラス定義を作成していることに注意してください)

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

実際の出力:

dummy: TestEval
dummy2: 

望ましい出力:

dummy: TestEval
dummy2: TestEval2

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

回答:sepp2kの方法を使用した完全に動的なソリューション

dynamic_name = "TestEval2"

Object.const_set(dynamic_name, Class.new) # If inheriting, use Class.new( superclass )
dummy2 = eval("#{dynamic_name}")
puts "dummy2: #{dummy2}"
67
Mike Bethany

クラスの名前は、単にそれを参照する最初の定数の名前です。

つまりmyclass = Class.newを実行してからMyClass = myclassを実行すると、クラスの名前はMyClassになります。ただし、実行時までクラスの名前がわからない場合は、MyClass =を実行できません。

したがって、代わりにModule#const_setを使用して、constの値を動的に設定できます。例:

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

私もこれをいじっています。私の場合、ActiveRecord :: Baseの拡張機能をテストしようとしていました。クラスを動的に作成できるようにする必要がありました。アクティブレコードはクラス名に基づいてテーブルを検索するため、そのクラスを匿名にすることはできませんでした。

これがあなたのケースに役立つかどうかはわかりませんが、ここに私が思いついたものがあります:

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

  attr_accessor :foo, :bar
end

ActiveRecordに関する限り、self.nameを定義するだけで十分です。これは、クラスが匿名にできないすべての場合に実際に機能すると推測しています。

(sep2kの答えを読んだばかりで、彼の方がいいと思っています。とにかくここに置いておきます。)

31
Will Tomlins

これは本当に古い質問であり、他のRubyistがこのためにコミュニティから遠ざけるかもしれませんが、人気のあるJavaプロジェクトを=でラップする非常に薄いラッパーgemの作成に取り組んでいますRubyクラス。@ sepp2kの答えに基づいて、1つのプロジェクトで何度も何度もやらなければならなかったので、いくつかのヘルパーメソッドを作成しました。オブジェクトやカーネルなどのトップレベルの名前空間。

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

これらのメソッドを使用するには(これは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

なぜ??

これにより、Javaプロジェクトを使用するJRuby gemを作成することができ、必要に応じてオープンソースコミュニティとこれらのクラスを装飾することができます。私のgemは非常に薄いラッパーなので、他のモジュールを拡張するために多くのサブクラスとモジュールを作成する必要がありました。

J.D. Powerで言うように、「これは謝罪による開発です。ごめんなさい」。

3
WattsInABox

次のコードはどうですか:

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は、ランタイムPCオブジェクトを返しません。少なくとも私のPCでは返されません。 Object.const_getを使用して、Classオブジェクトを取得します。

0
wuyunxia