簡體   English   中英

如何更改Struct屬性的默認值?

[英]How to change the default value of a Struct attribute?

根據文檔,未設置的Struct屬性設置為nil

unset參數默認為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

這也可以通過將Struct創建為子類,並使用默認值覆蓋initialize來實現,如下例所示:

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

一方面,這種方法確實導致了一點點樣板; 另一方面,它做你正在尋找的好看和簡潔。

一個副作用(可能是一個好處或煩惱,取決於您的偏好/用例)是您丟失默認為nil的所有屬性的默認Struct行為 - 除非您明確地將它們設置為如此。 實際上,除非您將name聲明為name=nil否則上面的示例將使name成為必需參數

在@ 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

@Linuxios給出了一個覆蓋成員查找的答案。 這有幾個問題:您無法將成員顯式設置為nil,並且每個成員引用都有額外的開銷。 在我看來,你真的只想在初始化一個新的struct對象時提供默認值,其中部分成員值提供給::new::[]

這是一個使用附加工廠方法擴展Struct的模塊,該方法允許您使用散列描述所需的結構,其中鍵是成員名稱和初始化時未提供時默認填充的值:

# 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

我也發現了這個:

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

我認為通過調用#super(*required_args)來覆蓋#initialize方法是最好的方法。

這具有能夠使用散列樣式參數的額外優點。 請參閱以下完整和編譯示例:

散列樣式參數,默認值和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

你可以用單身人士做到這一點:

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

只需添加其他變體:

class Result < Struct.new(:success, :errors)
  def initialize(*)
    super
    self.errors ||= []
  end
end

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM