2012-02-22 11 views
9

Próbuję utworzyć nową klasę, nie znając nazwy klasy, dopóki nie zostanie utworzona.Tworzenie klasy dynamicznie

Coś takiego;

variable = "ValidClassName" 

     class variable 

     end 

Test = ValidClassName.new 

Jeśli to możliwe, chciałbym docenić kilka wskazówek, jak dynamicznie dodawać atrybuty (i metody) do tej nowej klasy.

będę retreiving „Ustawienia” dla klasy i będą wyglądać następująco:

title :Person 
attribute :name, String 
attribute :age, Fixnum 

Ale nie powinno być tak zaprojektowane, aby zaakceptować tylko to wyraźny pliku, atrybuty mogą się różnić w końcu numer rodzaj.

co w końcu będzie generować klasy, który powinien wyglądać tak:

class Person 
    def initialize(name, age) 

     @name_out = name 
     @age_out = age 
    end 

end 

Pomoc?

+1

chcesz tylko utworzyć kod źródłowy dla klasy? lub chcesz wygenerować źródło i powiedzieć ruby, aby skompilował/załadował klasę w czasie wykonywania? – ardnew

+1

Z ciekawości, z jakim problemem rozwiązujesz? W jaki sposób planujesz używać tych dynamicznie tworzonych klas? – ctcherry

+0

Moja klasa ma działać jako "ramka", niegdyś stworzona do niewłaściwego użycia w tworzeniu instancji (tak długo, jak spełniają wymagania określone przez klasę) z pliku yaml. YAML zawiera grupę "ludzi", a niektóre z nich mają atrybuty pasujące do wymagań. – BSG

Odpowiedz

23

klasa otrzymuje swoją nazwę, gdy jest on przypisany do stałej. Więc jest to łatwe do zrobienia w sposób ogólny z const_set.

Na przykład, powiedzmy, że chcesz użyć Struct zbudować klasę z pewnymi atrybutami można:

name = "Person" 
attributes = [:name, :age] 

klass = Object.const_set name, Struct.new(*attributes) 
# Now use klass or Person or const_get(name) to refer to your class: 
Person.new("John Doe", 42) # => #<struct Person name="John Doe", age=42> 

dziedziczyć z innej klasy, wymień Struct.new przez Class.new(MyBaseClass), powiedzieć:

class MyBaseClass; end 

klass = Class.new(MyBaseClass) do 
    ATTRIBUTES = attributes 
    attr_accessor *ATTRIBUTES 
    def initialize(*args) 
    raise ArgumentError, "Too many arguments" if args.size > ATTRIBUTES.size 
    ATTRIBUTES.zip(args) do |attr, val| 
     send "#{attr}=", val 
    end 
    end 
end 
Object.const_set name, klass 
Person.new("John Doe", 42) # => #<Person:0x007f934a975830 @name="John Doe", @age=42> 
+0

Otrzymuję komunikat o błędzie i deklaruję klass, nazwa powinna być stała, więc kapitalizowałem klass do Klassa w 2, 3 linijkach, a teraz działa. – tebayoso

+0

Och, racja. Przykład zmodyfikowany. –

6

Twój kod będzie wyglądać mniej więcej podobny do tego:

variable = "SomeClassName" 
klass = Class.new(ParentClass) 
# ...maybe evaluate some code in the context of the new, anonymous class 
klass.class_eval { } 
# ...or define some methods 
klass.send(:title, :Person) 
klass.send(:attribute, :name, String) 
# Finally, name that class! 
ParentClass.send(:const_set, variable, klass) 

... lub może po prostu użyć eval:

eval <<DYNAMIC 
    class #{name} 
    title :Person 
    attribute :name, String 
    # ...or substitute other stuff in here. 
    end 
DYNAMIC 
+1

Przepraszam, jestem naprawdę nie na miejscu. Czy mógłbyś spróbować wyjaśnić to tak, jakbym miał trzy lata ...? : p – BSG

+1

Nie jestem pewien, czy to jest o wiele łatwiejsze. Drugi przypadek jest prawdopodobnie łatwiejszy do zrozumienia: 'eval' pobiera ciąg i ocenia go * w czasie połączenia * tak, jakby był to kod Ruby. Tak więc, tworzysz łańcuch znaków z kodem źródłowym dla klasy dynamicznej w środowisku wykonawczym, a następnie oceniasz. Pierwszy przypadek tworzy nowy obiekt klasy, robi rzeczy, aby utworzyć pożądane metody, itd., A następnie nadaje mu nazwę - która w Ruby jest tym samym, co przypisanie obiektu klasy do stałej. –