33

私はRubyにかなり慣れていないので、これが明らかな質問である場合は申し訳ありません.

Struct をインスタンス化するときに名前付きパラメーターを使用したいと考えています。つまり、Struct 内のどのアイテムがどの値を取得するかを指定し、残りをデフォルトで nil に設定できるようにしたいと考えています。

たとえば、私はやりたい:

Movie = Struct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'

これはうまくいきません。

だから私は次のことを思いついた:

class MyStruct < Struct
  # Override the initialize to handle hashes of named parameters
  def initialize *args
    if (args.length == 1 and args.first.instance_of? Hash) then
      args.first.each_pair do |k, v|
        if members.include? k then
          self[k] = v
        end
      end
    else
      super *args
    end
  end
end

Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'

これは問題なく動作しているように見えますが、これを行うためのより良い方法があるかどうか、またはかなり非常識なことを行っているかどうかはわかりません。誰かがこのアプローチを検証/引き裂くことができれば、私は最も感謝しています.

アップデート

これを最初に 1.9.2 で実行しましたが、問題なく動作します。ただし、他のバージョンの Ruby で試してみたところ (rvm に感謝)、次のように機能する/機能しない:

  • 1.8.7: 動かない
  • 1.9.1: 作業中
  • 1.9.2: 作業中
  • JRuby (1.9.2 として実行するように設定): 動作しない

JRuby との互換性を維持したいので、JRuby は私にとって問題です。

さらに別の更新

このますます増え続けるとりとめのない質問で、私はさまざまなバージョンの Ruby で実験し、1.9.x の構造体はメンバーをシンボルとして格納することを発見しましたが、1.8.7 と JRuby ではそれらは文字列として格納されるため、コードを次のように更新しました。次のようになります(すでに親切に提供された提案を取り入れて):

class MyStruct < Struct
  # Override the initialize to handle hashes of named parameters
  def initialize *args
    return super unless (args.length == 1 and args.first.instance_of? Hash)
    args.first.each_pair do |k, v|
      self[k] = v if members.map {|x| x.intern}.include? k
    end
  end
end

Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'

これは、私が試したすべての種類の Ruby で機能するようになりました。

4

12 に答える 12

20

既存の回答を統合すると、Ruby 2.0+ のより単純なオプションが明らかになります。

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

使用法は既存の と同じStructで、指定されていない引数はデフォルトで次のようになります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"> 

Ruby 2.1+ の required kwargs のような引数を要求したい場合、それは非常に小さな変更です:

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

その時点で、initialize特定の kwargs のデフォルト値を指定するオーバーライドも実行可能です。

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">
于 2016-08-07T05:19:56.930 に答える
13

あなたが知っていることは少ないほど良いです。基になるデータ構造がシンボルまたは文字列を使用しているかどうか、または としてアドレス指定できるかどうかさえ知る必要はありませんHash。属性セッターを使用するだけです:

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

位置引数とキーワード引数の両方を取ります。

> KwStruct.new "q", :zxcv => "z"
 => #<struct KwStruct qwer="q", asdf=nil, zxcv="z">
于 2013-06-12T09:25:22.980 に答える
9

Ruby キーワード引数のみを許可するソリューション(Ruby >=2.0)。

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

使用法:

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

nilこの種の構造体は、値オブジェクトとして非常に便利です。特に、 (OpenStruct のように)返す代わりに実際にエラーになるより厳密なメソッド アクセサーが必要な場合に便利です。

于 2015-03-17T21:55:13.400 に答える
5

OpenStructを検討しましたか?

require 'ostruct'

person = OpenStruct.new(:name => "John", :age => 20)
p person               # #<OpenStruct name="John", age=20>
p person.name          # "John"
p person.adress        # nil
于 2011-03-23T16:38:35.590 に答える
4

sを並べ替えることができifます。

class MyStruct < Struct
  # Override the initialize to handle hashes of named parameters
  def initialize *args
    # I think this is called a guard clause
    # I suspect the *args is redundant but I'm not certain
    return super *args unless (args.length == 1 and args.first.instance_of? Hash)
    args.first.each_pair do |k, v|
      # I can't remember what having the conditional on the same line is called
      self[k] = v if members.include? k
    end
  end
end
于 2011-03-23T22:48:27.613 に答える
2

ハッシュキーが整っている場合は、splat オペレーターを呼び出して救助することができます。

NavLink = Struct.new(:name, :url, :title)
link = { 
  name: 'Stack Overflow', 
  url: 'https://stackoverflow.com', 
  title: 'Sure whatever' 
}
actual_link = NavLink.new(*link.values) 
#<struct NavLink name="Stack Overflow", url="https://stackoverflow.com", title="Sure whatever"> 
于 2016-02-09T14:27:38.207 に答える
2

@Andrew Grimmの回答に基づいていますが、Ruby 2.0のキーワード引数を使用しています:

class Struct

  # allow keyword arguments for Structs
  def initialize(*args, **kwargs)
    param_hash = kwargs.any? ? kwargs : Hash[ members.zip(args) ]
    param_hash.each { |k,v| self[k] = v }
  end

end

これは、通常の引数とキーワード引数の混合を許可しないことに注意してください。どちらか一方しか使用できません。

于 2013-03-25T21:04:52.947 に答える
1

Struct の動作 (必要な引数が指定されていない場合はレイズ) と 1 対 1 で同等の場合、私は時々これを使用します (Ruby 2+):

def Struct.keyed(*attribute_names)
  Struct.new(*attribute_names) do
    def initialize(**kwargs)
      attr_values = attribute_names.map{|a| kwargs.fetch(a) }
      super(*attr_values)
    end
  end
end

そしてそこから

class SimpleExecutor < Struct.keyed :foo, :bar
  ...
end

これはKeyError、引数を逃した場合に a を発生させるため、より厳密なコンストラクターや、多くの引数、データ転送オブジェクトなどを持つコンストラクターには非常に適しています。

于 2015-04-25T09:25:46.450 に答える
0

Ruby 2.x のみ (必須のキーワード引数が必要な場合は 2.1)。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
)

使用法:

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

マイナーな欠点は、ラムダが正しい順序で値を返すことを確認する必要があることです。大きな利点は、Ruby 2 のキーワード引数をフルに活用できることです。

于 2016-02-04T23:14:44.017 に答える
0

これは質問に正確に答えるものではありませんが、構造化したい値のハッシュがある場合はうまく機能することがわかりました。構造体をサブクラス化する必要がない一方で、属性の順序を覚えておく必要性をオフロードするという利点があります。

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

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

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

于 2015-11-03T18:18:29.270 に答える