Именованные параметры в Ruby Structs - PullRequest
30 голосов
/ 23 марта 2011

Я довольно новичок в 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 - проблема для меня, так как яхотелось бы, чтобы он был совместим с этим для целей развертывания.

ДАЛЕЕ ДРУГОЕ ОБНОВЛЕНИЕ

В этом постоянно растущем вопросе я экспериментировал с различными версиями Ruby иобнаружил, что Structs в 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, которые я пробовал.

Ответы [ 11 ]

19 голосов
/ 07 августа 2016

Синтезирование существующих ответов открывает намного более простой вариант для 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"> 

Если вы хотите потребовать аргументы, такие как требуемые kwargs для Ruby 2.1 +, это очень небольшое изменение:

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">
12 голосов
/ 12 июня 2013

Чем меньше знаешь, тем лучше. Нет необходимости знать, использует ли базовая структура данных символы или строку, или даже может ли она рассматриваться как 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">
9 голосов
/ 18 марта 2015

Решение, которое только допускает аргументы ключевых слов 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).

5 голосов
/ 23 марта 2011

Рассматривали ли вы 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
4 голосов
/ 24 марта 2011

Вы можете переставить 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
2 голосов
/ 09 февраля 2016

Если ваши хеш-ключи в порядке, вы можете вызвать оператора спасения:

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"> 
2 голосов
/ 26 марта 2013

На основании ответа @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

Обратите внимание, что это не позволяет смешивать обычные и ключевые аргументы - вы можете использовать только один или другой.

1 голос
/ 25 апреля 2015

Для эквивалента 1-к-1 с поведением Struct (повышать, когда требуемый аргумент не указан), я иногда использую это (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, если вы пропустили аргумент, что очень хорошо для более строгих конструкторов и конструкторов с большим количеством аргументов, объектов передачи данных и тому подобного.

1 голос
/ 29 мая 2014

Если вам нужно смешать обычные и ключевые аргументы, вы всегда можете создать инициализатор вручную ...

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">

Это имеет название в качестве обязательного первого аргумента только для иллюстрации. Он также имеет то преимущество, что вы можете установить значения по умолчанию для каждого ключевого аргумента (хотя это вряд ли будет полезно при работе с фильмами!).

0 голосов
/ 05 февраля 2016

Ruby 2.x только (2.1, если вы хотите требуемые аргументы ключевых слов).Проверено только в МРТ.

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...