Ruby – Configuración de valores de propiedad al inicializar objeto

Dada la siguiente clase:

class Test attr_accessor :name end 

Cuando creo el objeto, quiero hacer lo siguiente:

 t = Test.new {Name = 'Some Test Object'} 

Por el momento, el nombre sigue siendo nil . es posible?

Nota: No quiero agregar un inicializador.

De acuerdo,

Se me ocurrió una solución. Utiliza el método de inicialización pero, por otro lado, hace exactamente lo que quieres.

 class Test attr_accessor :name def initialize(init) init.each_pair do |key, val| instance_variable_set('@' + key.to_s, val) end end def display puts @name end end t = Test.new :name => 'hello' t.display 

feliz ? 🙂


Solución alternativa mediante herencia. Tenga en cuenta que con esta solución, no necesita declarar explícitamente el attr_accessor!

 class CSharpStyle def initialize(init) init.each_pair do |key, val| instance_variable_set('@' + key.to_s, val) instance_eval "class << self; attr_accessor :#{key.to_s}; end" end end end class Test < CSharpStyle def initialize(arg1, arg2, *init) super(init.last) end end t = Test.new 'a val 1', 'a val 2', {:left => 'gauche', :right => 'droite'} puts "#{t.left} <=> #{t.right}" 

Como lo mencionaron otros, la forma más fácil de hacerlo sería definir un método de initialize . Si no quieres hacer eso, puedes hacer que tu clase herede de Struct .

 class Test < Struct.new(:name) end 

Y ahora:

 >> t = Test.new("Some Test Object") => # >> t.name => "Some Test Object" 

Hay una forma general de realizar la inicialización de objetos complejos: pasar el bloque con las acciones necesarias. Este bloque podría evaluarse en el contexto del objeto, por lo que puede acceder fácilmente a todas las variables de instancia, métodos, etc.

Continuando tu ejemplo

 class Test attr_accessor :name def initialize(&block) instance_eval(&block) end end 

y entonces

 t = Test.new { @name = 'name' } 

o

 t = Test.new do self.name = 'name' # other initialization, if needed end 

Tenga en cuenta que de esta manera no se requiere un cambio complejo del método de initialize (que, de hecho, es de una sola línea).

Como se mencionó anteriormente, la manera sensata de hacerlo es con un Struct o definiendo un método de Test#initialize . Esto es exactamente para lo que son las estructuras y los constructores. El uso de un hash de opciones correspondiente a los atributos es el equivalente más cercano a su ejemplo de C #, y es una convención de Ruby de aspecto normal:

 t = Test.new({:name => "something"}) t = Test.new(name: "something") # json-style or kwargs 

Pero en su ejemplo, está haciendo algo que se parece más a la asignación de variables usando = así que intentemos usar un bloque en lugar de un hash. (También estás usando Name que sería una constante en Ruby, cambiaremos eso).

 t = Test.new { @name = "something" } 

Genial, ahora hagamos que realmente funcione:

 class BlockInit def self.new(&block) super.tap { |obj| obj.instance_eval &block } end end class Test < BlockInit attr_accessor :name end t = Test.new { @name = "something" } # => # t.name # => "something" 

Hemos creado una clase con un constructor que acepta un argumento de bloque, que se ejecuta dentro del objeto creado recientemente.

Como dijiste que querías evitar el uso de la initialize , en lugar de eso estoy reemplazando new y llamando a super para obtener el comportamiento predeterminado de Object#new . Normalmente, en lugar de eso, definiríamos initialize , no se recomienda este enfoque, excepto para cumplir con la solicitud específica en su pregunta.

Cuando pasamos un bloque a una subclase de BlockInit podemos hacer algo más que establecer una variable … básicamente, estamos inyectando código en el método de initialize (que evitamos escribir). Si también desea un método de initialize que haga otras cosas (como mencionó en los comentarios), podría agregarlo a la Test y no tener que llamar a super (ya que nuestros cambios no están en BlockInit#initialize , en lugar de BlockInit.new )

Espero que sea una solución creativa para una solicitud muy específica e intrigante.

El código que está indicando está pasando parámetros a la función de initialize . Definitivamente tendrás que usar initialize o usar una syntax más aburrida:

 test = Test.new test.name = 'Some test object' 

Necesitaría realizar una subclase de prueba (aquí se muestra con su propio método e inicializador), por ejemplo:

 class Test attr_accessor :name, :some_var def initialize some_var @some_var = some_var end def some_function "#{some_var} calculation by #{name}" end end class SubClassedTest < Test def initialize some_var, attrbs attrbs.each_pair do |k,v| instance_variable_set('@' + k.to_s, v) end super(some_var) end end tester = SubClassedTest.new "some", name: "james" puts tester.some_function 

salidas: some calculation by james

Podrías hacer esto.

 class Test def not_called_initialize(but_act_like_one) but_act_like_one.each_pair do |variable,value| instance_variable_set('@' + variable.to_s, value) class << self self end.class_eval do attr_accessor variable end end end end (t = Test.new).not_called_initialize :name => "Ashish", :age => 33 puts t.name #=> Ashish puts t.age #=> 33 

Una ventaja es que ni siquiera tiene que definir sus variables de instancia por adelantado utilizando attr_accessor . Podría pasar todas las variables de instancia que necesita a través del método not_called_initialize y dejar que las cree, además de definir los captadores y definidores.

Si no desea anular la initialize , tendrá que subir la cadena y anular la new . Aquí hay un ejemplo:

 class Foo attr_accessor :bar, :baz def self.new(*args, &block) allocate.tap do |instance| if args.last.is_a?(Hash) args.last.each_pair do |k,v| instance.send "#{k}=", v end else instance.send :initialize, *args end end end def initialize(*args) puts "initialize called with #{args}" end end 

Si lo último que pasa es un Hash, omitirá la initialize y llamará a los configuradores de inmediato. Si pasas algo más, llamará a initialize con esos argumentos.