2010-06-25 12 views
9

Estoy buscando una manera de añadir propiedades a mi clase ya definida en tiempo de ejecución, o mejor:Ruby - dinámicamente añadir una propiedad a la clase (en tiempo de ejecución)

class Client 
    attr_accessor :login, :password 
    def initialize args = {} 
     self.login = args[:login] 
     self.password = args[:password] 
    end 
end 

Pero entonces, tengo este hash

{:swift_bic=>"XXXX", :account_name=>"XXXX", :id=>"123", :iban=>"XXXX"} 

y quiero que este hash para convertirse en parte de mi instancia de cliente como

client = Client.new :login => 'user', :password => 'xxxxx' 

luego con una magia milagrosa

client @@%$%PLIM!!! {:swift_bic=>"XXXX", :account_name=>"XXXX", :id=>"123", :iban=>"XXXX"} 

yo sería capaz de acceder a la

client.swift_bic => 'XXXX' 
client.account_name => 'XXXX' 
client.id => 123 

y también me gustaría mantener una estructura de objeto propio como:

Client.new(:login => 'user', :password => 'xxxxx').inspect 
#<Client:0x1033c4818 @password='xxxxx', @login='user'> 

después de la magia

client.inspect 
#<Client:0x1033c4818 @password='xxxxx', @login='user', @swift_bic='XXXX', @account_name='XXXX' @id => '123', @iban => 'XXXX'> 

que me daría una ni ce y json formateado bien después de eso

¿Es posible en absoluto?

Obtengo este hash de un servicio web, por lo que no sabría si hay una nueva propiedad allí, y luego tendría que actualizar mi aplicación cada vez que realicen una actualización en su servicio. Por lo tanto, estoy tratando de evitar esto:/

Gracias griegos.

:)

+2

Si toda esta clase está haciendo es almacenar datos, entonces solo usaría OpenStruct. Existe exactamente para este propósito. http://www.ruby-doc.org/core/classes/OpenStruct.html – thorncp

Respuesta

15

El enfoque method_missing quiere trabajar, pero si usted va a utilizar los descriptores de acceso mucho después de la adición de ellos, es posible que así añadirlos como métodos reales, así:

class Client 
    def add_attrs(attrs) 
    attrs.each do |var, value| 
     class_eval { attr_accessor var } 
     instance_variable_set "@#{var}", value 
    end 
    end 
end 

Esto hará que sean funcionan como las variables de instancia normales, pero se limitan a una sola client.

+1

HAHAAA !!, y existe la magia milagrosa, muchas gracias, exactamente lo que estaba buscando. cheers – zanona

+3

No funciona para mí de forma predeterminada (probé 1.8.7 y 1.9.1) Lo hice funcionar invocando el class_eval en la clase singleton del self. todo es lo mismo, excepto ** ** se convierte en la línea 3: '(clase << yo; yo; final) .class_eval {var}' attr_accessor Una forma alternativa de escribir que sería '(clase << self; self; end) .send: attr_accessor, var' –

+1

Dispara, buena llamada. Esto requiere ActiveSupport como lo escribí. – mckeed

0

Tome un vistazo a Object.method_missing. Se llamará a esto siempre que se invoque su objeto con un método que no está definido. Puede definir esta función y usarla para verificar el método indefinido con el nombre de uno de sus valores hash. Si coincide, devuelve el valor hash.

También puede definir su propia función inspect y generar una cadena de salida que contenga lo que quiera que contenga.

0

Voy a recomendar contra method_missing para esto - eso crea una gran cantidad de funcionalidad 'mágica' que no se puede documentar fácilmente o entender sin trabajar a través del cuerpo method_missing. En su lugar, mira en OpenStruct como se sugiere - incluso se podría hacer su propia clase que hereda de ella, como:

class Client < OpenStruct 
    ... 
end 

y usted será capaz de inicializar un cliente con cualquier hash que recibe.

1

Creo que la mejor solución sería la de

mckeed Pero aquí es otra idea que pensar. Podría subclase OpenStruct si desea:

require 'ostruct' 

class Client < OpenStruct 
    def initialize args = {} 
    super 
    end 
    def add_methods(args = Hash.new) 
    args.each do |name,initial_value| 
     new_ostruct_member name 
     send "#{name}=" , initial_value 
    end 
    end 
end 

client = Client.new :login => 'user', :password => 'xxxxx' 
client.add_methods :swift_bic=>"XXXX", :account_name=>"XXXX", :iban=>"XXXX" , :to_s => 5 
client # => #<Client login="user", password="xxxxx", swift_bic="XXXX", account_name="XXXX", iban="XXXX", to_s=5> 

client.swift_bic  # => "XXXX" 
client.account_name # => "XXXX" 

Sin embargo, existen dos problemas con esta solución. OpenStruct usa method_missing, así que si defines un método como id, en 1.8 buscará el object_id en lugar de encontrar tu método.

El segundo problema es que utiliza algunos conocimientos privados sobre cómo se implementa OpenStruct. Entonces podría cambiarse en el futuro, rompiendo este código (para el registro, revisé 1.8.7 - 1.9.2 y esto era compatible)

Cuestiones relacionadas