Как изменить значение по умолчанию для атрибута Struct?
В соответствии с документация отменить атрибуты Struct установлены на nil
:
отключить параметры по умолчанию на nil.
Можно ли указать значение по умолчанию для определенных атрибутов?
Например, для следующего Struct
Struct.new("Person", :name, :happy)
Я бы хотел, чтобы атрибут happy
по умолчанию был true
, а не nil
. Как я могу это сделать? Если я сделаю следующее:
Struct.new("Person", :name, :happy = true)
Я получаю
-:1: syntax error, unexpected '=', expecting ')'
Struct.new("Person", :name, :happy = true)
^
-:1: warning: possibly useless use of true in void context
Ответы
Ответ 1
Это также может быть достигнуто путем создания вашего Struct в качестве подкласса и переопределения
initialize
со значениями по умолчанию, как в следующем примере:
class Person < Struct.new(:name, :happy)
def initialize(name, happy=true); super end
end
С одной стороны, этот метод приводит к небольшому количеству шаблонов; с другой стороны, он делает то, что вы ищете красиво и лаконично.
Один побочный эффект (который может быть либо преимуществом, либо раздражением в зависимости от ваших предпочтений/вариантов использования) заключается в том, что вы теряете поведение по умолчанию Struct
всех атрибутов, по умолчанию на nil
- если вы явно их не задали быть таким. Фактически, приведенный выше пример сделает name
обязательным параметром, если вы не объявите его как name=nil
Ответ 2
Следуя примеру @rintaun, вы также можете сделать это с помощью аргументов ключевого слова в 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
# => #<struct A a=1, b=2, c=3>
A.new a: 1, c: 6
# => #<struct A a=1, b=2, c=6>
UPDATE
Теперь код должен быть записан следующим образом.
A = Struct.new(:a, :b, :c) do
def initialize(a:, b: 2, c: 3)
super(a, b, c)
end
end
Ответ 3
@Linuxios дал ответ, который переопределяет поиск членов. У этого есть пара проблем: вы не можете явно установить член в nil и там дополнительные накладные расходы для каждой ссылки на ссылки. Мне кажется, вы просто хотите предоставить значения по умолчанию при инициализации нового объекта структуры с частичными значениями элементов, указанными в ::new
или ::[]
.
Здесь модуль для расширения Struct с дополнительным factory методом, который позволяет вам описать вашу желаемую структуру с помощью хэша, где ключи являются именами участников и значениями по умолчанию для заполнения, если они не указаны при инициализации:
# 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 #=> #<struct MyStruct a=1, b=2, c="xyz"
# MyStruct.new(99) #=> #<struct MyStruct a=99, b=2, c="xyz">
# MyStruct[-10, 3.5] #=> #<struct MyStruct a=-10, b=3.5, c="xyz">
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
Ответ 4
Я также нашел это:
Person = Struct.new "Person", :name, :happy do
def initialize(*)
super
self.location ||= true
end
end
Ответ 5
Я думаю, что лучше всего использовать метод #initialize
с вызовом #super(*required_args)
.
Это имеет дополнительное преимущество, заключающееся в возможности использования аргументов хэш-стиля. См. Следующий полный и компиляционный пример:
Аргументы типа Hash, значения по умолчанию и структура Ruby
# 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
Ответ 6
Вы можете сделать это с помощью 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
Ответ 7
Просто добавьте другой вариант:
class Result < Struct.new(:success, :errors)
def initialize(*)
super
self.errors ||= []
end
end