2010-02-10 3 views
15

J'ai une classe et un hachage. Comment puis-je obtenir les membres du hachage dynamiquement devenir des méthodes sur la classe avec la clé comme nom de la méthode?Comment utiliser les clés de hachage comme méthodes sur une classe?

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

Par exemple, je voudrais être en mesure d'avoir la sortie suivante Doe:

u = User.new 
puts u.sn 
+4

Assurez-vous de regarder OpenStruct (struct.rb dans la bibliothèque standard). C'est un peu différent de ce que vous demandez: cela permet à tout appel de méthode sur OpenStruct d'être un accesseur, qu'il soit déjà défini ou non. Mais c'est un code que vous n'avez pas à écrire, ce qui peut parfois être un plus. –

Répondre

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

Explication: Si vous appelez une méthode qui n'existe pas, method_missing est invoqué avec le nom de la méthode comme premier paramètre, suivi des arguments donnés à la méthode et du bloc si un a été donné. Dans ce qui précède nous disons que si une méthode, qui n'a pas été définie, est appelée sans arguments et sans un bloc et que le hachage a une entrée avec le nom de la méthode comme clé, elle renverra la valeur de cette entrée. Sinon, il procédera comme d'habitude.

+0

Je devais ajouter pour le changer à: name.to_s dans les deux endroits, mais il m'a eu où je voulais! Merci :) – Michael

+0

Oh, c'est vrai, je n'ai pas remarqué que vous utilisiez des chaînes comme des clés de hachage. – sepp2k

+0

En outre, vous devez également remplacer 'responds_to?' Par match, car votre classe "répond" à ce message particulier. –

4

La solution de sepp2k est la solution. Cependant, si votre @attributes ne changent jamais après l'initialisation et vous avez besoin de vitesse, vous pouvez le faire de cette façon:

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" 

Cela génère toutes les méthodes à l'avance ...

3

En fait severin avez une meilleure idée, juste parce que l'utilisation de method_missing est une mauvaise pratique, pas tout le temps, mais la plupart du temps.

Un problème avec ce code fourni par severin: il renvoie la valeur qui a été transmise à l'initialiseur, donc vous ne pouvez pas le changer. Je vous propose une petite approche différente:

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 

permet de vérifier:

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

Et vous pouvez aussi le faire avec Struct:

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

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

Lets tester:

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

Ou même OpenStruct, mais soyez ca REFUL il ne crée pas la méthode si elle a déjà dans les méthodes d'exemple, vous pouvez chercher qu'en utilisant OpenStruct.instance_methods (à cause du type est utilisé, j'utilise maintenant la deuxième approche):

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

Eh oui, si facile :

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

votre explication et l'exemple étendu est vraiment génial. Merci! –

29

utiliser juste OpenStruct:

require 'ostruct' 
class User < OpenStruct 
end 

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

Vous pouvez "emprunter" ActiveResource pour cela. Il gère même hash imbriqués et l'attribution:

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

Utilisation:

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') 

Notez que u.Le travail est un User :: Job - cette classe est créée automatiquement. Il y a un gotcha lors de l'affectation à une valeur imbriquée. Vous ne pouvez pas simplement attribuer un hachage, mais vous devez l'envelopper dans la classe appropriée:

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

Malheureusement, lorsque vous souhaitez ajouter un hachage imbriqué qui ne dispose pas d'une classe correspondante, il est plus laid parce que vous devez forcer ARes à créer la classe à partir du hachage:

# 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 
Questions connexes