2010-02-10 11 views
15

Mam klasę i skrót. W jaki sposób uzyskać członów mieszania dynamicznie stają się metody na klasie z kluczem jako nazwa metody?Jak używać klawiszy skrótu jako metod na klasie?

class User 
    def initialize 
    @attributes = {"sn" => "Doe", "givenName" => "John"} 
    end 
end 

Na przykład, chciałbym móc mieć następujący wynik Doe:

u = User.new 
puts u.sn 
+4

Zobacz, co należy napisać w OpenStruct (struct.rb w standardowej bibliotece). Jest trochę inny niż to, o co prosisz: pozwala na wywołanie metody na OpenStruct jako akcesor, bez względu na to, czy zostało już zdefiniowane, czy nie. Ale to kod, którego nie musisz pisać, co czasem może być plusem. –

Odpowiedz

14
def method_missing(name, *args, &blk) 
    if args.empty? && blk.nil? && @attributes.has_key?(name) 
    @attributes[name] 
    else 
    super 
    end 
end 

Objaśnienie: Jeśli wywołać metodę, która nie istnieje, method_missing jest wywoływana z nazwa metody jako pierwszy parametr, a następnie argumenty podane dla metody i bloku, jeśli został podany.

W powyższym powiedzmy, że jeśli metoda, która nie została zdefiniowana, jest wywoływana bez argumentów i bez bloku, a hash ma wpis z nazwą metody jako kluczem, zwróci wartość tego wpisu. W przeciwnym razie będzie to wyglądało normalnie.

+0

Musiałem dodać, aby zmienić go na: name.to_s w obu miejscach, ale dostałem mnie tam, gdzie chciałem! Dzięki :) – Michael

+0

No dobra, nie zauważyłem, że używasz strun jako kluczy hash. – sepp2k

+0

Powinieneś także zastąpić 'responss_to?', Aby dopasować, ponieważ twoja klasa "odpowiada" również na ten konkretny komunikat. –

4

Rozwiązaniem sepp2k jest droga. Jeśli jednak @attributes nigdy nie zmienia się po inicjalizacji i trzeba prędkości, to można to zrobić w ten sposób:

class User 
    def initialize 
    @attributes = {"sn" => "Doe", "givenName" => "John"} 
    @attributes.each do |k,v| 
     self.class.send :define_method, k do v end 
    end 
    end 
end 

User.new.givenName # => "John" 

Generuje wszystkie metody wyprzedzeniem ...

3

faktycznie severin mieć lepszy pomysł, tylko dlatego, że użycie metody method_missing to zła praktyka, nie cały czas, ale większość.

Jeden problem z tym kodem dostarczony przez severin: zwraca wartość, która została przekazana do inicjatora, więc nie można go zmienić. Proponuję wam trochę innego podejścia:

class User < Hash 
    def initialize(attrs) 
    attrs.each do |k, v| 
     self[k] = v 
    end 
    end 

    def []=(k, v) 
    unless respond_to?(k) 
     self.class.send :define_method, k do 
     self[k] 
     end 
    end 

    super 
    end 
end 

Pozwala to sprawdzić:

u = User.new(:name => 'John') 
p u.name 
u[:name] = 'Maria' 
p u.name 

A także można zrobić z Struct:

attrs = {:name => 'John', :age => 22, :position => 'developer'} 
keys = attrs.keys 

user = Struct.new(*keys).new(*keys.map { |k| attrs[k] }) 

Lets go przetestować:

p user 
p user.name 
user[:name] = 'Maria' 
p user.name 
user.name = 'Vlad' 
p user[:name] 

Lub nawet OpenStruct, ale bądź ca reful nie stworzy metodę, jeśli już to w metodach instancji, można spojrzeć na to przez zastosowanie OpenStruct.instance_methods (ze względu na typ jest używany, jestem teraz za pomocą drugiego podejścia):

attrs = {:name => 'John', :age => 22, :position => 'developer'} 
user = OpenStruct.new(attrs) 

Tak, tak łatwo :

user.name 
user[:name] # will give you an error, because OpenStruct isn't a Enumerable or Hash 
+0

Twoje wyjaśnienie i rozszerzony przykład są naprawdę świetne. Dzięki! –

29

Wystarczy użyć OpenStruct:

require 'ostruct' 
class User < OpenStruct 
end 

u = User.new :sn => 222 
u.sn 
1

można "pożyczyć" ActiveResource do tego. To nawet obsługuje zagnieżdżone mieszań i przypisania:

require 'active_resource' 
class User < ActiveResource::Base 
    self.site = '' # must be a string 
end 

Zastosowanie:

u = User.new "sn" => "Doe", "givenName" => "John", 'job'=>{'description'=>'Engineer'} 
u.sn # => "Doe" 
u.sn = 'Deere' 
u.job.description # => "Engineer" 
# deletion 
u.attributes.delete('givenName') 

pamiętać, że u.job to User :: Job - ta klasa jest tworzona automatycznie. Podczas przypisywania do zagnieżdżonej wartości występuje błąd. Nie można po prostu przypisać skrót, ale należy owinąć je w odpowiedniej klasie:

u.job = User::Job.new 'foo' => 'bar' 
u.job.foo # => 'bar 

Niestety, gdy chcemy dodać zagnieżdżone hash który nie posiada odpowiedniej klasy, to brzydsze, bo trzeba wymuś ARES, aby utworzyć klasę z skrótu:

# assign the hash first 
u.car = {'make' => 'Ford'} 
# force refresh - this can be put into a method 
u = User.new Hash.from_xml(u.to_xml).values.first 
Powiązane problemy