2009-11-04 10 views
5

Esiste un modo semplice per elencare gli accessor/lettori che sono stati impostati in una Ruby Class?Inizializza una classe Ruby da un hash arbitrario, ma solo le chiavi con gli accessor corrispondenti

class Test 
    attr_reader :one, :two 

    def initialize 
    # Do something 
    end 

    def three 
    end 
end 

Test.new 
=> [one,two] 

Quello che sto davvero cercando di fare è quello di permettere l'inizializzazione di accettare un hash con qualsiasi numero di attributi in, ma impegnarsi solo quelli che hanno i lettori già definiti. Qualcosa di simile:

def initialize(opts) 
    opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val| 
    eval("@#{opt} = \"#{val}\"") 
    end 
end 

Altri suggerimenti?

risposta

9

Questo è quello che uso (chiamo questo idioma hash-init).

def initialize(object_attribute_hash = {}) 
    object_attribute_hash.map { |(k, v)| send("#{k}=", v) } 
end 

Se siete su Ruby 1.9 si può fare ancora più pulito (inviare permette metodi privati):

def initialize(object_attribute_hash = {}) 
    object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) } 
end 

Ciò consentirà di aumentare di un NoMethodError se si tenta di assegnare a foo e il metodo "foo = " non esiste. Se si vuole fare pulito (assegnare attrs per cui esistono scrittori) si dovrebbe fare un controllo

def initialize(object_attribute_hash = {}) 
    object_attribute_hash.map do |(k, v)| 
    writer_m = "#{k}=" 
    send(writer_m, v) if respond_to?(writer_m) } 
    end 
end 

tuttavia questo potrebbe portare a situazioni in cui si alimentare il vostro oggetto tasti sbagliati (per esempio da un modulo) e, invece di fallire a voce alta li inghiottirà - doloroso debugging avanti. Quindi nel mio libro un NoMethodError è un'opzione migliore (indica una violazione del contratto).

Se si desidera solo un elenco di tutti gli scrittori (non c'è modo di farlo per i lettori) che fate

some_object.methods.grep(/\w=$/) 

che è "ottenere un array di nomi dei metodi e grep per le voci che terminano con un singolo segno uguale dopo un carattere di parola ".

Se fai

eval("@#{opt} = \"#{val}\"") 

e val proviene da un modulo web - congratulazioni, basta dotate tua app con un spalancata sfruttare.

4

È possibile eseguire l'override di attr_reader, attr_writer e attr_accessor per fornire un tipo di meccanismo di tracciamento per la classe in modo da poter disporre di una migliore capacità di riflessione come questa.

Ad esempio:

class Class 
    alias_method :attr_reader_without_tracking, :attr_reader 
    def attr_reader(*names) 
    attr_readers.concat(names) 
    attr_reader_without_tracking(*names) 
    end 

    def attr_readers 
    @attr_readers ||= [ ] 
    end 

    alias_method :attr_writer_without_tracking, :attr_writer 
    def attr_writer(*names) 
    attr_writers.concat(names) 
    attr_writer_without_tracking(*names) 
    end 

    def attr_writers 
    @attr_writers ||= [ ] 
    end 

    alias_method :attr_accessor_without_tracking, :attr_accessor 
    def attr_accessor(*names) 
    attr_readers.concat(names) 
    attr_writers.concat(names) 
    attr_accessor_without_tracking(*names) 
    end 
end 

Questi possono essere dimostrati abbastanza semplice:

class Foo 
    attr_reader :foo, :bar 
    attr_writer :baz 
    attr_accessor :foobar 
end 

puts "Readers: " + Foo.attr_readers.join(', ') 
# => Readers: foo, bar, foobar 
puts "Writers: " + Foo.attr_writers.join(', ') 
# => Writers: baz, foobar 
+0

Aggiornato per utilizzare l'array # concat anziché l'array # << per creare array flat. – tadman

+0

Ottima idea! Terrò questo in una libreria da qualche parte, sono sicuro che sarà molto utile! –

0

accessori sono solo metodi ordinari che capita di accedere a qualche pezzo di dati. Ecco il codice che farà all'incirca quello che vuoi. Esso controlla se c'è un metodo chiamato per la chiave hash e imposta una variabile di istanza di accompagnamento in caso affermativo:

def initialize(opts) 
    opts.each do |opt,val| 
    instance_variable_set("@#{opt}", val.to_s) if respond_to? opt 
    end 
end 

Si noti che questo avrà scattato in su se una chiave ha lo stesso nome di un metodo, ma che il metodo non è un semplice accesso alle variabili di istanza (ad esempio, {:object_id => 42}). Ma non tutti gli accessor saranno necessariamente definiti da attr_accessor, quindi non c'è davvero un modo migliore per dire. L'ho modificato anche per usare instance_variable_set, che è molto più efficiente e sicuro è ridicolo.

0

Non esiste un modo integrato per ottenere tale elenco. Le funzioni attr_* essenzialmente aggiungono solo metodi, creano una variabile di istanza e nient'altro. Potresti scrivere wrapper per fare ciò che vuoi, ma potrebbe essere eccessivo. A seconda delle circostanze particolari, potresti essere in grado di utilizzare Object#instance_variable_defined? e Module#public_method_defined?.

Inoltre, evitare di utilizzare eval quando possibile:

def initialize(opts) 
    opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val| 
    instance_variable_set "@#{opt}", val 
    end 
end 
2

provare qualcosa di simile:

class Test 
    attr_accessor :foo, :bar 

    def initialize(opts = {}) 
    opts.each do |opt, val| 
     send("#{opt}=", val) if respond_to? "#{opt}=" 
    end 
    end 
end 

test = Test.new(:foo => "a", :bar => "b", :baz => "c") 

p test.foo # => nil 
p test.bar # => nil 
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 @bar="b", @foo="a"> (NoMethodError) 

Questo è fondamentalmente ciò che Rails fa quando si passa in un hash params alla nuova. Ignorerà tutti i parametri che non conosce e ti consentirà di impostare cose che non sono necessariamente definite da attr_accessor, ma che hanno ancora un setter appropriato.

L'unico svantaggio è che questo richiede veramente di avere un setter definito (rispetto solo all'accessorio) che potrebbe non essere quello che stai cercando.

0

Si può guardare per vedere quali metodi sono definiti (con Object#methods), e da quelli identificare i setter (l'ultimo carattere di queste è =), ma non c'è al 100% modo sicuro per sapere che questi metodi non sono stati attuati in un modo non ovvio che coinvolge diverse variabili di istanza.

Tuttavia Foo.new.methods.grep(/=$/) ti fornirà un elenco stampabile di setter di proprietà.Oppure, dato che hai già un hash, puoi provare:

def initialize(opts) 
    opts.each do |opt,val| 
    instance_variable_set("@#{opt}", val.to_s) if respond_to? "#{opt}=" 
    end 
end 
+0

Questo elenca anche i metodi equals e triquals, quindi aggiungo un \ w prima di quello – Julik

Problemi correlati