Named Parameters in Ruby Structs

后端 未结 12 1626
小蘑菇
小蘑菇 2020-12-29 20:12

I\'m pretty new to Ruby so apologies if this is an obvious question.

I\'d like to use named parameters when instantiating a Struct, i.e. be able to specify which ite

相关标签:
12条回答
  • 2020-12-29 20:38

    Synthesizing the existing answers reveals a much simpler option for Ruby 2.0+:

    class KeywordStruct < Struct
      def initialize(**kwargs)
        super(*members.map{|k| kwargs[k] })
      end
    end
    

    Usage is identical to the existing Struct, where any argument not given will default to nil:

    Pet = KeywordStruct.new(:animal, :name)
    Pet.new(animal: "Horse", name: "Bucephalus") # => #<struct Pet animal="Horse", name="Bucephalus">  
    Pet.new(name: "Bob") # => #<struct Pet animal=nil, name="Bob"> 
    

    If you want to require the arguments like Ruby 2.1+'s required kwargs, it's a very small change:

    class RequiredKeywordStruct < Struct
      def initialize(**kwargs)
        super(*members.map{|k| kwargs.fetch(k) })
      end
    end
    

    At that point, overriding initialize to give certain kwargs default values is also doable:

    Pet = RequiredKeywordStruct.new(:animal, :name) do
      def initialize(animal: "Cat", **args)
        super(**args.merge(animal: animal))
      end
    end
    
    Pet.new(name: "Bob") # => #<struct Pet animal="Cat", name="Bob">
    
    0 讨论(0)
  • 2020-12-29 20:39

    If you do need to mix regular and keyword arguments, you can always construct the initializer by hand...

    Movie = Struct.new(:title, :length, :rating) do
      def initialize(title, length: 0, rating: 'PG13')
        self.title = title
        self.length = length
        self.rating = rating
      end
    end
    
    m = Movie.new('Star Wars', length: 'too long')
    => #<struct Movie title="Star Wars", length="too long", rating="PG13">
    

    This has the title as a mandatory first argument just for illustration. It also has the advantage that you can set defaults for each keyword argument (though that's unlikely to be helpful if dealing with Movies!).

    0 讨论(0)
  • 2020-12-29 20:40

    The less you know, the better. No need to know whether the underlying data structure uses symbols or string, or even whether it can be addressed as a Hash. Just use the attribute setters:

    class KwStruct < Struct.new(:qwer, :asdf, :zxcv)
      def initialize *args
        opts = args.last.is_a?(Hash) ? args.pop : Hash.new
        super *args
        opts.each_pair do |k, v|
          self.send "#{k}=", v
        end
      end
    end
    

    It takes both positional and keyword arguments:

    > KwStruct.new "q", :zxcv => "z"
     => #<struct KwStruct qwer="q", asdf=nil, zxcv="z">
    
    0 讨论(0)
  • 2020-12-29 20:40

    A solution that only allows Ruby keyword arguments (Ruby >=2.0).

    class KeywordStruct < Struct
      def initialize(**kwargs)
        super(kwargs.keys)
        kwargs.each { |k, v| self[k] = v }
      end
    end
    

    Usage:

    class Foo < KeywordStruct.new(:bar, :baz, :qux)
    end
    
    
    foo = Foo.new(bar: 123, baz: true)
    foo.bar  # --> 123
    foo.baz  # --> true
    foo.qux  # --> nil
    foo.fake # --> NoMethodError
    

    This kind of structure can be really useful as a value object especially if you like more strict method accessors which will actually error instead of returning nil (a la OpenStruct).

    0 讨论(0)
  • 2020-12-29 20:43

    this doesn't exactly answer the question but I found it to work well if you have say a hash of values you wish to structify. It has the benefit of offloading the need to remember the order of attributes while also not needing to subClass Struct.

    MyStruct = Struct.new(:height, :width, :length)

    hash = {height: 10, width: 111, length: 20}

    MyStruct.new(*MyStruct.members.map {|key| hash[key] })

    0 讨论(0)
  • 2020-12-29 20:44

    Ruby 2.x only (2.1 if you want required keyword args). Only tested in MRI.

    def Struct.new_with_kwargs(lamb)
      members = lamb.parameters.map(&:last)
      Struct.new(*members) do
        define_method(:initialize) do |*args|
          super(* lamb.(*args))
        end
      end
    end
    
    Foo = Struct.new_with_kwargs(
      ->(a, b=1, *splat, c:, d: 2, **kwargs) do
        # must return an array with values in the same order as lambda args
        [a, b, splat, c, d, kwargs]
      end
    )
    

    Usage:

    > Foo.new(-1, 3, 4, c: 5, other: 'foo')
    => #<struct Foo a=-1, b=3, splat=[4], c=5, d=2, kwargs={:other=>"foo"}>
    

    The minor downside is that you have to ensure the lambda returns the values in the correct order; the big upside is that you have the full power of ruby 2's keyword args.

    0 讨论(0)
提交回复
热议问题