¿Cómo cambiar el valor predeterminado de un atributo Struct?

De acuerdo con la documentación, los atributos de Struct se establecen en nil :

Parámetros unset por defecto a nil.

¿Es posible especificar el valor predeterminado para atributos particulares?

Por ejemplo, para el siguiente Struct

 Struct.new("Person", :name, :happy) 

Me gustaría que el atributo happy predeterminado en true lugar de nil . ¿Cómo puedo hacer esto? Si hago lo siguiente

 Struct.new("Person", :name, :happy = true) 

yo obtengo

 -:1: syntax error, unexpected '=', expecting ')' Struct.new("Person", :name, :happy = true) ^ -:1: warning: possibly useless use of true in void context 

Esto también se puede lograr creando su Struct como una subclase, y anulando la initialize con valores predeterminados como en el siguiente ejemplo:

 class Person < Struct.new(:name, :happy) def initialize(name, happy=true); super end end 

Por un lado, este método conduce a un poco de repetitivo; Por otro lado, hace lo que buscas agradable y sucintamente.

Un efecto secundario (que puede ser un beneficio o una molestia según sus preferencias / caso de uso) es que pierde el comportamiento Struct predeterminado de todos los atributos que se establecen de forma predeterminada en nil , a menos que los establezca explícitamente. En efecto, el ejemplo anterior haría que el name un parámetro requerido a menos que lo declare como name=nil

Siguiendo el ejemplo de @ rintaun, también puede hacer esto con argumentos de palabras clave en Ruby 2+

 A = Struct.new(:a, :b, :c) do def initialize(a:, b: 2, c: 3); super end end A.new # ArgumentError: missing keyword: a A.new a: 1 # => # A.new a: 1, c: 6 # => # 

ACTUALIZAR

El código ahora debe escribirse de la siguiente manera para que funcione.

 A = Struct.new(:a, :b, :c) do def initialize(a:, b: 2, c: 3) super(a, b, c) end end 

@Linuxios dio una respuesta que anula la búsqueda de miembros. Esto tiene un par de problemas: no puede establecer explícitamente un miembro en nulo y hay una sobrecarga adicional en cada referencia de miembro. Me parece que realmente solo desea proporcionar los valores predeterminados al inicializar un nuevo objeto de estructura con valores de miembro parciales suministrados a ::new o ::[] .

Aquí hay un módulo para extender Struct con un método de fábrica adicional que le permite describir su estructura deseada con un hash, donde las claves son los nombres de los miembros y los valores que los valores predeterminados deben completar cuando no se suministran en la inicialización:

 # Extend stdlib Struct with a factory method Struct::with_defaults # to allow StructClasses to be defined so omitted members of new structs # are initialized to a default instead of nil module StructWithDefaults # makes a new StructClass specified by spec hash. # keys are member names, values are defaults when not supplied to new # # examples: # MyStruct = Struct.with_defaults( a: 1, b: 2, c: 'xyz' ) # MyStruct.new #=> # # # MyStruct[-10, 3.5] #=> # def with_defaults(*spec) new_args = [] new_args << spec.shift if spec.size > 1 spec = spec.first raise ArgumentError, "expected Hash, got #{spec.class}" unless spec.is_a? Hash new_args.concat spec.keys new(*new_args) do class << self attr_reader :defaults end def initialize(*args) super self.class.defaults.drop(args.size).each {|k,v| self[k] = v } end end.tap {|s| s.instance_variable_set(:@defaults, spec.dup.freeze) } end end Struct.extend StructWithDefaults 

También encontré esto:

 Person = Struct.new "Person", :name, :happy do def initialize(*) super self.location ||= true end end 

Creo que la anulación del método #initialize es la mejor manera, con la llamada a #super(*required_args) .

Esto tiene una ventaja adicional de poder usar argumentos de estilo hash. Por favor vea el siguiente ejemplo completo y de comstackción:

Argumentos de estilo hash, valores predeterminados y Ruby Struct

 # This example demonstrates how to create Ruby Structs that use # newer hash-style parameters, as well as the default values for # some of the parameters, without loosing the benefits of struct's # implementation of #eql? #hash, #to_s, #inspect, and other # useful instance methods. # # Run this file as follows # # > gem install rspec # > rspec struct_optional_arguments.rb --format documentation # class StructWithOptionals < Struct.new( :encrypted_data, :cipher_name, :iv, :salt, :version ) VERSION = '1.0.1' def initialize( encrypted_data:, cipher_name:, iv: nil, salt: 'salty', version: VERSION ) super(encrypted_data, cipher_name, iv, salt, version) end end require 'rspec' RSpec.describe StructWithOptionals do let(:struct) { StructWithOptionals.new(encrypted_data: 'data', cipher_name: 'AES-256-CBC', iv: 'intravenous') } it 'should be initialized with default values' do expect(struct.version).to be(StructWithOptionals::VERSION) end context 'all fields must be not null' do %i(encrypted_data cipher_name salt iv version).each do |field| subject { struct.send(field) } it field do expect(subject).to_not be_nil end end end end 

Puedes hacer esto con un singleton:

 Person = Struct.new("Person", :name, :happy) do @@defaults = {:happy => true} @@vars = [:name, :happy] def [](i) return super if(super) return @@defaults[i] if(@@defaults[i]) return nil end @@vars.each do |v| define_method(v) {return self[v]} end end