How to change the default value of a Struct attribute?

后端 未结 6 1374
孤城傲影
孤城傲影 2020-12-17 07:43

According to the documentation unset attributes of Struct are set to nil:

unset parameters default to nil.

Is it po

相关标签:
6条回答
  • 2020-12-17 08:16

    Just add another variation:

    class Result < Struct.new(:success, :errors)
      def initialize(*)
        super
        self.errors ||= []
      end
    end
    
    0 讨论(0)
  • 2020-12-17 08:20

    This can also be accomplished by creating your Struct as a subclass, and overriding initialize with default values as in the following example:

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

    On one hand, this method does lead to a little bit of boilerplate; on the other, it does what you're looking for nice and succinctly.

    One side-effect (which may be either a benefit or an annoyance depending on your preferences/use case) is that you lose the default Struct behavior of all attributes defaulting to nil -- unless you explicitly set them to be so. In effect, the above example would make name a required parameter unless you declare it as name=nil

    0 讨论(0)
  • 2020-12-17 08:22

    Following @rintaun's example you can also do this with keyword arguments in 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

    The code now needs to be written as follows to work.

    A = Struct.new(:a, :b, :c) do
      def initialize(a:, b: 2, c: 3)
        super(a, b, c)
      end
    end
    
    0 讨论(0)
  • 2020-12-17 08:32

    @Linuxios gave an answer that overrides member lookup. This has a couple problems: you can't explicitly set a member to nil and there's extra overhead on every member reference. It seems to me you really just want to supply the defaults when initializing a new struct object with partial member values supplied to ::new or ::[].

    Here's a module to extend Struct with an additional factory method that lets you describe your desired structure with a hash, where the keys are the member names and the values the defaults to fill in when not supplied at initialization:

    # 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
    
    0 讨论(0)
  • 2020-12-17 08:34

    I think that the override of the #initialize method is the best way, with call to #super(*required_args).

    This has an additional advantage of being able to use hash-style arguments. Please see the following complete and compiling example:

    Hash-Style Arguments, Default Values, and 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
    
    0 讨论(0)
  • 2020-12-17 08:35

    I also found this:

    Person = Struct.new "Person", :name, :happy do
      def initialize(*)
        super
        self.location ||= true
      end
    end
    
    0 讨论(0)
提交回复
热议问题