2010-02-10 11 views
15

Ho una classe e un hash. Come posso ottenere che i membri dell'hash diventino dinamicamente metodi sulla classe con la chiave come nome del metodo?Come utilizzare le chiavi di hash come metodi su una classe?

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

Per esempio, mi piacerebbe essere in grado di avere il seguente output Doe:

u = User.new 
puts u.sn 
+4

Assicuratevi di guardare OpenStruct (struct.rb nella libreria standard). È un po 'diverso da quello che stai chiedendo: consente a qualsiasi chiamata di metodo su OpenStruct di essere un accessor, indipendentemente dal fatto che sia già stato definito. Ma è un codice che non devi scrivere, che a volte può essere un vantaggio. –

risposta

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

Spiegazione: se si chiama un metodo, che non esiste, method_missing viene richiamato con il nome del metodo come primo parametro, seguito dagli argomenti dati al metodo e dal blocco se ne è stato fornito uno.

In quanto sopra diciamo che se un metodo, che non è stato definito, viene chiamato senza argomenti e senza un blocco e l'hash ha una voce con il nome del metodo come chiave, restituirà il valore di quella voce. Altrimenti procederà come al solito.

+0

Ho dovuto aggiungere per cambiarlo in: name.to_s in entrambi i posti, ma mi ha portato dove volevo! Grazie :) – Michael

+0

Oh, giusto, non ho notato che stavi usando le stringhe come chiavi hash. – sepp2k

+0

Inoltre, dovresti anche eseguire l'override di 'responds_to?', Poiché la tua classe ora "risponde" anche a quel particolare messaggio. –

4

La soluzione di sepp2k è la soluzione. Tuttavia, se i @attributes non cambiano mai dopo l'inizializzazione e avete bisogno di velocità, allora si potrebbe farlo in questo modo:

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" 

Questo genera tutti i metodi di anticipo ...

3

realtà severin dispone di un migliore idea, solo perché l'uso di method_missing è una cattiva pratica, non sempre, ma soprattutto.

Un problema con quel codice fornito da severin: restituisce il valore che è stato passato all'inizializzatore, quindi non è possibile cambiarlo. Vi suggerisco un po 'diverso approccio:

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 

permette di controllare:

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

e inoltre è possibile farlo con Struct:

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

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

Lets testarlo:

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

O anche OpenStruct, ma essere ca REFUL non creerà metodo se già hanno in metodi di istanza, si può cercare che utilizzando OpenStruct.instance_methods (a causa del tipo viene utilizzato, ora sto usando secondo approccio):

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

Sì, così facile :

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

la tua spiegazione e l'esempio esteso sono davvero grandiosi. Grazie! –

29

Basta usare OpenStruct:

require 'ostruct' 
class User < OpenStruct 
end 

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

Si può "prendere in prestito" ActiveResource per questo. Gestisce anche gli hash nidificati e l'assegnazione:

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

Usage:

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

noti che u.job è un utente :: Job: questa classe viene creata automaticamente. C'è un trucco quando si assegna ad un valore annidato. Non si può semplicemente assegnare un hash, ma bisogna avvolgerla nella classe appropriata:

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

Purtroppo, quando si desidera aggiungere un hash nidificato che non ha un corrispondente classe, è più brutto perché si deve forzare ARes per creare la classe dall'hash:

# 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 
Problemi correlati