2010-02-10 45 views
15

Tengo una clase y un hash. ¿Cómo puedo hacer que los miembros del hash se conviertan dinámicamente en métodos de la clase con la clave como nombre del método?¿Cómo uso las claves hash como métodos en una clase?

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

Por ejemplo, me gustaría ser capaz de tener la siguiente salida Doe:

u = User.new 
puts u.sn 
+4

Asegúrese de ver OpenStruct (struct.rb en la biblioteca estándar). Es un poco diferente de lo que está pidiendo: permite que cualquier llamada de método en el OpenStruct sea un acceso, ya sea que esté o no definido. Pero es un código que no tiene que escribir, que a veces puede ser un plus. –

Respuesta

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

Explicación: Si se llama a un método, que no existe, method_missing se invoca con el nombre del método como el primer parámetro, seguido de los argumentos dados al método y el bloque si se proporcionó uno.

En lo anterior decimos que si se llama a un método, que no se definió, sin argumentos y sin un bloque y el hash tiene una entrada con el nombre del método como clave, devolverá el valor de esa entrada. De lo contrario, continuará como siempre.

+0

Tuve que agregar para cambiarlo a: name.to_s en ambos lugares, ¡pero me llevó a donde quería! Gracias :) – Michael

+0

Oh, claro, no me di cuenta de que estabas usando cadenas como teclas hash. – sepp2k

+0

Además, también debe reemplazar 'responds_to?' Para que coincida, ya que su clase ahora también "responde" a ese mensaje en particular. –

4

La solución de sepp2k es el camino a seguir. Sin embargo, si sus @attributes nunca cambian después de la inicialización y que necesitan velocidad, entonces usted podría hacerlo de esta manera:

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" 

Esto genera todos los métodos de antelación ...

3

En realidad tiene un severin Mejor idea, solo porque el uso de method_missing es una mala práctica, no todo el tiempo, pero la mayor parte.

Un problema con el código proporcionado por severin: devuelve el valor que se ha pasado al inicializador, por lo que no puede cambiarlo. Te sugiero un enfoque un poco diferente:

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 

Permite comprobar que:

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

y también puede hacerlo con Struct:

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

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

Lets probarlo:

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

O incluso OpenStruct, pero se ca REFUL no creará método si ya lo tenemos en los métodos de instancia, se puede buscar que mediante el uso OpenStruct.instance_methods (a causa de tipo se utiliza, ahora estoy usando segundo enfoque):

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

Sí, tan fácil :

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

su explicación y ejemplo extendido es realmente genial. ¡Gracias! –

29

sólo tiene que utilizar OpenStruct:

require 'ostruct' 
class User < OpenStruct 
end 

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

Usted puede "tomar prestada" ActiveResource para esto. Incluso se maneja hashes anidados y asignación:

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

Uso:

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

Nota que u.job es User :: Job: esta clase se crea automáticamente. Hay un error al asignar a un valor anidado. No se puede simplemente asignar un hash, pero debe envolver en la clase apropiada:

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

Por desgracia, cuando se desea añadir un resumen anidada que no tiene una clase correspondiente, es más feo porque hay que forzar ARes para crear la clase desde el 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 
Cuestiones relacionadas