2012-10-06 79 views
27
class Foo 
    attr_accessor :name, :age, :email, :gender, :height 

    def initalize params 
    @name = params[:name] 
    @age = params[:age] 
    @email = params[:email] 
    . 
    . 
    . 
    end 

Esto parece una manera tonta de hacerlo. ¿Cuál es una forma mejor/más idiomática de initalizar objetos en Ruby?¿Cómo inicializar limpiamente los atributos en Ruby con nuevo?

de Ruby 1.9.3

+0

De hecho, creo que la respuesta a esta pregunta es 'no' - podría haber mejores formas, pero si hubiera una manera mejor y más idiomática, habría una respuesta inequívocamente votada a continuación. –

Respuesta

15
def initialize(params) 
    params.each do |key, value| 
    instance_variable_set("@#{key}", value) 
    end 
end 
+4

No me gusta esto en general, pero si va a usarlo, al menos invoque setters en lugar de establecer variables de instancia. Si arruinas el nombre de la variable usando ivars, no lo sabrás, pero usando los métodos, obtendrás un NoMethodError. –

+1

@Joshua: Eso es perfectamente válido. Estoy de acuerdo en que su método es generalmente preferible, sin embargo, no siempre proporciono setters para todas las variables de instancia (por ejemplo, cuando no deberían cambiar después de la inicialización del objeto). –

+1

jajaja, ni siquiera me di cuenta de que había respondido en este hilo. De todos modos, casi siempre accedo a través de setters/getters. Si no desea que cambien, decídanlos como privados ([ejemplo] (https://github.com/JoshCheek/seeing_is_believing/blob/3161fb906d38ebb4300333f20d84bd58fa3e7652/lib/seeing_is_believing/binary/commentable_lines.rb#L32-37)) Pero en realidad, el código de inicialización como este solo debería usarse en estructuras de datos, por lo que este problema no debería surgir en la práctica. –

36

Usted sólo puede iterar sobre las claves e invocar los definidores. Prefiero esto porque atrapará si pasas una clave inválida.

class Foo 
    attr_accessor :name, :age, :email, :gender, :height 

    def initialize params = {} 
    params.each { |key, value| send "#{key}=", value } 
    end 
end 

foo = Foo.new name: 'Josh', age: 456 
foo.name # => "Josh" 
foo.age # => 456 
foo.email # => nil 
+1

También me gusta esto, pero parece que solo es útil en las clases donde todas las variables de instancia tienen definidores. –

1

Si recibe un hash como único argumento, ¿por qué no mantenerlo como una variable de instancia? Siempre que necesite un valor, llámelo desde el hash. Puede mantener corto el nombre de la variable de instancia para que pueda ser llamado fácilmente.

class Foo 
    attr_reader :p 
    def initalize p 
    @p = p 
    end 
    def foo 
    do_something_with(@p[:name]) 
    ... 
    end 
end 

Si @p[:name] sigue siendo demasiado largo para usted, entonces usted puede ahorrar un proc como una variable de instancia, y llame a la magnitud de salida como @p.(:name).

class Foo 
    attr_reader :p 
    def initialize p 
    @p = ->x{p[x]} 
    end 
    def foo 
    do_something_with(@p.(:name)) 
    ... 
    end 
end 

O, una forma alternativa es definir un método que llame al hash y aplique la clave.

class Foo 
    def initalize p 
    @p = p 
    end 
    def get key 
    @p[key] 
    end 
    def foo 
    do_something_with(get(:name)) 
    ... 
    end 
end 

Si desea establecer los valores, se puede definir un método de selección y comprobación adicional para las claves no válidas si lo desea.

class Foo 
    Keys = [:name, :age, :email, :gender, :height] 
    def initalize p 
    raise "Invalid key in argument" unless (p.keys - Keys).empty? 
    @p = p 
    end 
    def set key, value 
    raise "Invalid key" unless Keys.key?(key) 
    @p[key] = value 
    end 
    def get key 
    @p[key] 
    end 
    def foo 
    do_something_with(get(:name)) 
    ... 
    end 
end 
+0

Esto te deja con posibles referencias parásitas: 'h = {: a =>: b}; Foo.new (h); h [: a] =: sin sentido'. –

3

El uso de todas las teclas de params no es correcto, puede definir nombres poco convenientes. Creo que debería ser una especie de lista blanca de nombres

class Foo 
    @@attributes = [:name, :age, :email, :gender, :height] 

    @@attributes.each do |attr| 
    class_eval { attr_accessor "#{attr}" } 
    end 

    def initialize params 
    @@attributes.each do |attr| 
     instance_variable_set("@#{attr}", params[attr]) if params[attr] 
    end 
    end 
end 

Foo.new({:name => 'test'}).name #=> 'test' 
+0

No definir atributos no deseados: ese es un buen punto. –

+0

¿Cómo usarías 'get_attributes' en lugar de' @@ attributes = [: name,: age,: email,: gender,: height] '? Ya están definidos con 'attr_accessor: name,: age,: email,: gender,: height'. –

+0

También podría 'attr_accessor * @@ attributes'. La lista blanca aquí tiene todo tipo de sentido. –

5

¿Por qué no simplemente especificar explícitamente una lista real de argumentos?

class Foo 
    attr_accessor :name, :age, :email, :gender, :height 

    def initialize(name, age, email, gender, height) 
    @name = name 
    @age = age 
    @email = email 
    @gender = gender 
    @height = height 
    end 
end 

Esta versión puede haber más líneas de código que otros, pero lo hace más fácil para apalancar (por ejemplo, valores por defecto para los argumentos o errores que crían si initialize se llama con aridad incorrecta) incorporados en las características del lenguaje.

+3

Los parámetros posicionales pueden ser una molestia por muchas razones, difíciles de refactorizar, y demasiado fáciles para desajustar las variables que se pasan. –

+0

Sí. Prefiero ser explícito siempre que sea posible, así que me gusta este enfoque. Es, de lejos, el código más fácil de razonar. Los parámetros de posición hacen que sea más difícil cambiar la firma más tarde, pero es una compensación que estoy dispuesto a hacer. –

4
Foo = Struct.new(:name, :age, :email, :gender, :height) 

Esto es suficiente para una clase completamente funcional. Demostración:

p Foo.class # Class 

employee = Foo.new("smith", 29, "[email protected]", "m", 1.75) #create an instance 
p employee.class # Foo 
p employee.methods.sort # huge list which includes name, name=, age, age= etc 
+1

Tenga en cuenta que esto no funciona bien en combinación con 'include ActiveModel :: Model'. –

9

para sacar provecho de la respuesta de Joshua Cheek con un poco de generalización

module Initializable 
    def initialize(params = {}) 
    params.each do |key, value| 
     setter = "#{key}=" 
     send(setter, value) if respond_to?(setter.to_sym, false) 
    end 
    end 
end 

class Foo 
    include Initializable 

    attr_accessor :name, :age, :email, :gender, :height 
end 

Foo.new name: 'Josh', age: 456 
=> #<Foo:0x007fdeac02ecb0 @name="Josh", @age=456> 

NB Si la inicialización mix-in se ha utilizado y necesitamos inicialización de encargo, nos simplemente llame a super:

class Foo 
    include Initializable 

    attr_accessor :name, :age, :email, :gender, :height, :handler 

    def initialize(*) 
    super 

    self.handler = "#{self.name} #{self.age}" 
    end 
end 

Foo.new name: 'Josh', age: 45 
=> #<Foo:0x007fe94c0446f0 @name="Josh", @age=45, @handler="Josh 45"> 
Cuestiones relacionadas