Wie definiere ich dynamisch eine Klasse in Ruby MIT einem Namen?
Ich weiß, wie man eine Klasse dynamisch ohne Namen erstellt, indem man Folgendes verwendet:
dynamic_class = Class.new do
def method1
end
end
Sie können jedoch keinen Klassennamen angeben. Ich möchte eine Klasse dynamisch mit einem Namen erstellen .
Hier ist ein Beispiel dafür, was ich tun möchte, aber natürlich funktioniert es nicht wirklich.
(Beachten Sie, dass ich keine Instanz einer Klasse erstelle, sondern eine Klassendefinition.)
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ächliche Ausgabe:
dummy: TestEval
dummy2:
Gewünschte Ausgabe:
dummy: TestEval
dummy2: TestEval2
================================================== ====
Antwort: Eine völlig dynamische Lösung mit der Methode von sepp2k
dynamic_name = "TestEval2"
Object.const_set(dynamic_name, Class.new) # If inheriting, use Class.new( superclass )
dummy2 = eval("#{dynamic_name}")
puts "dummy2: #{dummy2}"
ruby
class
dynamic
metaprogramming
Joshua Pinter
quelle
quelle
class A ... end
ist nicht auswerten zunil
, wertet sie aus dem Wert des letzten Ausdrucks in ihm ausgewertet, genau wie jede andere Verbindung , die Expression (Blöcke, Methoden Moduldefinitionen, die Expression Gruppen) in Ruby. Es kommt einfach so vor, dass in vielen Klassendefinitionskörpern der letzte Ausdruck ein Methodendefinitionsausdruck ist, der ausgewertet wirdnil
. Manchmal ist es jedoch nützlich, einen Klassendefinitionskörper auf einen bestimmten Wert auswerten zu lassen, zclass << self; self end
. B. in der Redewendung.Antworten:
Der Name einer Klasse ist einfach der Name der ersten Konstante, die darauf verweist.
Dh wenn ich das tue
myclass = Class.new
und dannMyClass = myclass
wird der Name der KlasseMyClass
. Ich kann es jedoch nicht tun,MyClass =
wenn ich den Namen der Klasse erst zur Laufzeit kenne.Sie können also stattdessen verwenden
Module#const_set
, wodurch der Wert einer Konstante dynamisch festgelegt wird. Beispiel:dynamic_name = "ClassName" Object.const_set(dynamic_name, Class.new { def method1() 42 end }) ClassName.new.method1 #=> 42
quelle
Ich habe auch damit rumgespielt. In meinem Fall habe ich versucht, Erweiterungen für ActiveRecord :: Base zu testen. Ich musste in der Lage sein, eine Klasse dynamisch 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 Ihrem Fall hilft, aber ich habe mir Folgendes ausgedacht:
test_model_class = Class.new(ActiveRecord::Base) do def self.name 'TestModel' end attr_accessor :foo, :bar end
Für ActiveRecord hat es gereicht, self.name zu definieren. Ich vermute, dass dies tatsächlich in allen Fällen funktioniert, in denen eine Klasse nicht anonym sein kann.
(Ich habe gerade die Antwort von sepp2k gelesen und denke, seine ist besser. Ich werde das trotzdem hier lassen.)
quelle
self.table_name = "my_things"
self.name
ist dies erforderlich, andernfalls schlägt Null#demodulize
fehl, wenn versucht wird, der Assoziation zu folgen :( Zumindest ist mir das mit Rails 5.2.3 passiert.Ich weiß, dass dies eine wirklich alte Frage ist, und einige andere Rubyisten meiden mich möglicherweise 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, da ich dies viele, viele Male in einem Projekt tun musste. Beachten Sie, dass ich diese Methoden mit einem Namespace versehen habe, damit sie keinen Namespace der obersten Ebene 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
So verwenden Sie diese Methoden (beachten Sie, dass dies 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??
Auf diese Weise kann ich ein JRuby-Juwel erstellen, das das Java-Projekt verwendet und es der Open Source-Community und mir ermöglicht, diese Klassen in Zukunft nach Bedarf zu dekorieren. Es wird auch ein benutzerfreundlicherer Namespace erstellt, in dem die Klassen verwendet werden können. 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 JD Power sagen, "ist dies eine entschuldigungsgetriebene Entwicklung: Es tut mir leid".
quelle
Wie wäre es mit dem 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 führt das Laufzeitklassenobjekt nicht erneut aus, zumindest auf meinem PC nicht. Verwenden Sie Object.const_get, um das Class-Objekt abzurufen.
quelle
eval
erreicht werden. Miteval
müssen Sie die Eingabe bereinigen, um sich gegen die Ausführung von bösartigem Code abzusichern.