2010-11-06 13 views
57

Wie definiere ich dynamisch eine Klasse in Ruby mit einem Namen?Dynamische Klassendefinition mit einem Klassennamen

Ich weiß, wie eine Klasse dynamisch ohne einen Namen wie etwas mit erstellen:

dynamic_class = Class.new do 
    def method1 
    end 
end 

Aber man kann nicht einen Klassennamen angeben. Ich möchte eine Klasse dynamisch mit einen Namen erstellen.

Hier ist ein Beispiel, was ich tun möchte, aber natürlich funktioniert es nicht wirklich.
(Beachten Sie, dass ich nicht eine Instanz einer Klasse, sondern eine Klassendefinition erschaffe)

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 

tatsächlichen Ausgang:

dummy: TestEval 
dummy2: 

gewünschte Ausgabe:

dummy: TestEval 
dummy2: TestEval2 

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

Antwort: Eine völlig dynamische Lösung unter Verwendung der Methode sepp2k

dynamic_name = "TestEval2" 

Object.const_set(dynamic_name, Class.new) 
dummy2 = eval("#{dynamic_name}") 
puts "dummy2: #{dummy2}" 
+1

Ich verstehe nicht wirklich, was Sie erreichen möchten. Es gibt eine Klasse TestEval2, danach können Sie test_eval2 = TestEval2.new durchführen. Und: Klasse A ... end liefert immer nil, also ist deine Ausgabe ok, denke ich ;-) – Philip

+0

Das ist für einen TDD-Testschritt. Ich muss eine Testklasse dynamisch erstellen und dann auf ihren Namen verweisen, weil sie so in der freien Wildbahn verwendet wird. Sepp2K hat es richtig gemacht. –

+2

@Philip: 'class A ... end' bewertet * nicht * zu' nil', wertet den Wert des zuletzt ausgewerteten Ausdrucks aus, genau wie jeder andere zusammengesetzte Ausdruck (Blöcke, Methoden, Moduldefinitionen, Ausdruck) Gruppen) in Ruby. Es ist einfach so, dass in vielen Klassendefinitionskörpern der letzte Ausdruck ein Methodendefinitionsausdruck ist, der zu "Null" auswertet. Manchmal ist es jedoch nützlich, wenn ein Klassendefinitionskörper einen bestimmten Wert, z. in der Klasse selbst; Selbstend-Idiom. –

Antwort

106

Der Name einer Klasse ist einfach der Name des ersten Konstante, die auf sie Bezug nimmt.

I.e. Wenn ich myclass = Class.new und dann MyClass = myclass mache, wird der Name der Klasse MyClass. Allerdings kann ich MyClass = nicht tun, wenn ich den Namen der Klasse bis zur Laufzeit nicht kenne.

Stattdessen können Sie stattdessen Module#const_set verwenden, die den Wert eines const dynamisch festlegt. Beispiel:

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

Ausgezeichnet! Vielen Dank! Genau das habe ich gebraucht. –

+1

Danke. Dies half mir hier: https://github.com/validates-email-format-of/validates_email_format_of/blob/5ba707476162aefc66453df1a5abb1cb1f12eb1c/spec/validates_email_format_of_spec.rb#L11 –

+2

Wow. Es erscheint mir sehr merkwürdig, dass diese (konstante) Aufgabe diesen Nebeneffekt hat. –

27

Ich habe mich auch damit beschäftigt. In meinem Fall habe ich versucht, Erweiterungen von ActiveRecord :: Base zu testen. Ich musste in der Lage sein, dynamisch eine Klasse zu erstellen, und da der aktive Datensatz eine Tabelle basierend auf einem Klassennamen nachschlägt, konnte diese Klasse nicht anonym sein.

Ich bin mir nicht sicher, ob dies der Fall hilft, aber hier ist, was ich kam mit:

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

    attr_accessible :foo, :bar 
end 

Soweit Active betrifft, definieren self.name war genug. Ich schätze, das wird in allen Fällen funktionieren, in denen eine Klasse nicht anonym sein kann.

(. Ich habe gerade sepp2k Antwort gelesen und ich denke, seine besser ist, werde ich dies hier lassen sowieso.)

+0

Übrigens, Sie könnten nur den Tabellennamen für die Klasse explizit wie folgt setzen: 'self.table_name =" my_things "' –

0

Wie aboutthe folgenden 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 das Laufzeitklassenobjekt, zumindest auf meinem PC nicht. Verwenden Sie Object.const_get, um das Klassenobjekt abzurufen.

+0

Es kann jedoch ohne 'eval' durchgeführt werden. Mit 'eval' müssen Sie die Eingabe bereinigen, um die Ausführung von bösartigem Code zu verhindern. – Pistos

1

Ich weiß, dass dies eine wirklich alte Frage ist und einige andere Rubyisten meiden mich aus der Community, aber ich arbeite daran, ein sehr dünnes Wrapper-Juwel zu erstellen, das ein beliebtes Java-Projekt mit Ruby-Klassen umschließt. Basierend auf der Antwort von @ sepp2k habe ich einige Hilfsmethoden erstellt, weil ich dies viele, viele Male in einem Projekt machen musste. Beachten Sie, dass ich diese Methoden mit einem Namespace versehen habe, sodass sie keinen Top-Level-Namespace wie Object oder Kernel verschmutzen.

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 

diese Methoden verwenden (beachten Sie, dass diese JRuby ist):

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 

WARUM ??

Dies ermöglicht mir, ein JRuby-Juwel zu erstellen, das das Java-Projekt verwendet und es der Open-Source-Community und I ermöglichen würde, diese Klassen in Zukunft nach Bedarf zu dekorieren. Es erstellt auch einen freundlicheren Namespace, um die Klassen zu verwenden. Da mein Juwel ein sehr, sehr dünner Wrapper ist, musste ich viele, viele Unterklassen und Module erstellen, um andere Module zu erweitern.

Wie wir bei J. D. Power sagen, "dies ist Entschuldigung-getriebene Entwicklung: Es tut mir leid".

Verwandte Themen