Creazione di oggetti idiomatici in ruby

Nel ruby, mi trovo spesso a scrivere quanto segue:

class Foo def initialize(bar, baz) @bar = bar @baz = baz end <> end 

o anche

 class Foo attr_accessor :bar, :baz def initialize(bar, baz) @bar = bar @baz = baz end <> end 

Sono sempre pronto a minimizzare il più ansible il boilerplate – quindi c’è un modo più idiomatico di creare oggetti nel ruby?

Un’opzione è che puoi ereditare la definizione della tua class da Struct:

 class Foo < Struct.new(:bar, :baz) # << more stuff >> end f = Foo.new("bar value","baz value") f.bar #=> "bar value" f.baz #=> "baz value" 

Ho fatto una domanda doppia e ho suggerito la mia risposta, aspettandomi per una migliore, ma una soddisfacente non è apparsa. Posterò il mio.

Definisci un metodo di class come il seguente seguendo lo spirito dei attr_accessor , attr_reader , attr_writer .

 class Class def attr_constructor *vars define_method("initialize") do |*vals| vars.zip(vals){|var, val| instance_variable_set("@#{var}", val)} end end end 

Quindi, puoi usarlo in questo modo:

 class Foo attr_constructor :foo, :bar, :buz end p Foo.new('a', 'b', 'c') # => # p Foo.new('a', 'b', 'c', 'd') # => # p Foo.new('a', 'b') # => # 

struct

Struct oggetti di Struct sono classi che fanno quasi quello che vuoi. L’unica differenza è che il metodo di inizializzazione ha nil come valore predefinito per tutti i suoi argomenti. Lo usi in questo modo

 A= Struct.new(:a, :b, :c) 

o

 class A < Struc.new(:a, :b, :c) end 

Struct ha un grosso inconveniente. Non puoi ereditare da un'altra class.

Scrivi il tuo specificatore di attributo

Potresti scrivere il tuo metodo per specificare gli attributi

 def attributes(*attr) self.class_eval do attr.each { |a| attr_accessor a } class_variable_set(:@@attributes, attr) def self.get_attributes class_variable_get(:@@attributes) end def initialize(*vars) attr= self.class.get_attributes raise ArgumentError unless vars.size == attr.size attr.each_with_index { |a, ind| send(:"#{a}=", vars[ind]) } super() end end end class A end class B < A attributes :a, :b, :c end 

Ora la tua class può ereditare da altre classi. L'unico inconveniente è che non è ansible ottenere il numero di argomenti per l'inizializzazione. Questo è lo stesso per Struct .

 B.method(:initialize).arity # => -1 

Potresti usare Virtus , non penso che sia il modo idiomatico per farlo, ma fa tutto il piatto della caldaia per te.

 require 'Virtus' class Foo include 'Virtus' attribute :bar, Object attribute :baz, Object end 

Quindi puoi fare cose come

 foo = Foo.new(:bar => "bar") foo.bar # => bar 

Se non ti piace passare un hash all’inizializzatore, aggiungi:

 def initialize(bar, baz) super(:bar => bar, :baz => baz) end 

Se non pensi che sia abbastanza ASCIUTTO, puoi anche farlo

 def initialize(*args) super(self.class.attributes.map(&:name).zip(args)]) end 

A volte lo faccio

 @bar, @baz = bar, baz 

Ancora standard, ma occupa solo una riga.

Immagino che potresti anche fare

 ["bar", "baz"].each do |variable_name| instance_variable_set(:"@#{variable_name}", eval(variable_name)) end 

(Sono sicuro che c’è un modo meno pericoloso per farlo, intendiamoci)

https://bugs.ruby-lang.org/issues/5825 è una proposta per rendere meno dettagliata la formula.

Potresti usare un object come param.

 class Foo attr_accessor :param def initialize(p) @param = p end end f = Foo.new f.param.bar = 1 f.param.bax = 2 

Questo non risparmia molte righe in questo caso, ma lo farà se la tua class deve gestire un gran numero di parametri. Potresti anche implementare un metodo set_param e get_param se vuoi mantenere private le tue @param var.