2009-11-04 33 views
5

¿Hay una manera simple de enumerar los accesores/lectores que se han establecido en una clase de Ruby?Inicializar una clase de Ruby desde un hash arbitrario, pero solo las claves con accesadores coincidentes

class Test 
    attr_reader :one, :two 

    def initialize 
    # Do something 
    end 

    def three 
    end 
end 

Test.new 
=> [one,two] 

Lo que realmente estoy tratando de hacer es permitir inicializar a aceptar un hash con cualquier número de atributos en, pero sólo se comprometan los que tienen los lectores ya definidos. Algo como:

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

¿Alguna otra sugerencia?

Respuesta

9

Esto es lo que uso (lo llamo hash-init idiom).

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

Si usted está en Ruby 1.9 puede hacerlo aún más limpio (enviar permite métodos privados):

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

Esto elevará un NoMethodError si intenta asignar a foo y el método "foo = " no existe. Si desea hacerlo limpia (asignar attrs para los que existen escritores) se debe hacer una verificación

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 

sin embargo, esto podría dar lugar a situaciones en las que alimentan las llaves equivocadas objeto (digamos de un formulario) y en lugar de fallar en voz alta, simplemente se los tragará, depuración dolorosa por delante. Entonces, en mi libro, un NoMethodError es una mejor opción (significa una violación del contrato).

Si lo que desea es una lista de todos los escritores (no hay manera de hacerlo para los lectores) lo hace

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

que es "conseguir una serie de nombres de métodos y utilice grep para las entradas que terminan con un solo signo igual después de un carácter de palabra ".

Si lo hace

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

y val proviene de un formulario web - felicitaciones, sólo cuentan con una aplicación muy abiertos explotar.

4

Puede anular attr_reader, attr_writer y attr_accessor para proporcionar algún tipo de mecanismo de seguimiento para su clase para que pueda tener una mejor capacidad de reflexión como esta.

Por ejemplo:

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 

Estos se pueden demostrar de manera bastante simple:

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

Actualizada para usar Array # concat en lugar de Array # << para hacer arreglos planos. – tadman

+0

¡Gran idea! Mantendré este en una biblioteca en alguna parte, ¡estoy seguro de que será muy útil! –

0

descriptores de acceso son sólo métodos ordinarios que suceden acceder a alguna pieza de datos. Aquí hay un código que hará más o menos lo que quieras. Se comprueba si hay un método llamado de la clave hash y establece una variable de instancia de acompañamiento en caso afirmativo:

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

Tenga en cuenta que esto va a tropezarte con eso si una clave tiene el mismo nombre que un método, pero ese método no es un acceso variable de instancia simple (por ejemplo, {:object_id => 42}). Pero no todos los accesores necesariamente estarán definidos por attr_accessor, por lo que no hay una mejor manera de saberlo. También lo cambié para usar instance_variable_set, que es mucho más eficiente y seguro, es ridículo.

0

No hay una manera incorporada de obtener una lista. Las funciones attr_* básicamente solo agregan métodos, crean una variable de instancia y nada más. Podrías escribir wrappers para que hagan lo que quieras, pero eso podría ser excesivo. Dependiendo de sus circunstancias particulares, puede hacer uso de Object#instance_variable_defined? y Module#public_method_defined?.

Además, evite el uso de eval cuando sea posible:

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

intentar algo como esto:

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) 

Esto es básicamente lo que hace rieles cuando se pasa de un hash params a nuevo. Ignorará todos los parámetros que desconoce, y le permitirá configurar cosas que no están necesariamente definidas por attr_accessor, pero que aún tienen un setter apropiado.

El único inconveniente es que esto realmente requiere que tenga un setter definido (en lugar del solo accesorio) que puede no ser lo que está buscando.

0

Puede ver qué métodos están definidos (con Object#methods), y de esos identifica los setters (el último carácter de ellos es =), pero no hay manera 100% segura de saber que esos métodos no se implementaron de una manera no obvia que involucra diferentes variables de instancia.

Sin embargo, Foo.new.methods.grep(/=$/) le dará una lista imprimible de establecedores de propiedades.O, como ya tiene un hash, puede probar:

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

Esto también enumera los métodos equals y triquals así que agrego un \ w antes de eso – Julik

Cuestiones relacionadas