ruby struct named-parameters

Parámetros con nombre en Ruby Structs



named-parameters (11)

Soy bastante nuevo para Ruby así que me disculpo si esta es una pregunta obvia.

Me gustaría usar parámetros con nombre al crear una instancia de Struct, es decir, ser capaz de especificar qué elementos de Struct obtienen qué valores, y el resto es nulo por defecto.

Por ejemplo, quiero hacer:

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

Esto no funciona

Así que se me ocurrió lo siguiente:

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

Esto parece funcionar bien, pero no estoy seguro de si hay una forma mejor de hacerlo, o si estoy haciendo algo bastante loco. Si alguien puede validar / desmantelar este enfoque, estaría muy agradecido.

ACTUALIZAR

Ejecuté esto inicialmente en 1.9.2 y funciona bien; sin embargo, después de haberlo probado en otras versiones de Ruby (gracias rvm), funciona / no funciona de la siguiente manera:

  • 1.8.7: No funciona
  • 1.9.1: Trabajando
  • 1.9.2: Trabajando
  • JRuby (configurado para ejecutarse como 1.9.2): no funciona

JRuby es un problema para mí, ya que me gustaría mantenerlo compatible con eso para el despliegue.

AÚN OTRA ACTUALIZACIÓN

En esta pregunta en constante aumento, experimenté con las diversas versiones de Ruby y descubrí que Structs en 1.9.x almacena sus miembros como símbolos, pero en 1.8.7 y JRuby, se almacenan como cadenas, así que actualicé el código para ser el siguiente (teniendo en cuenta las sugerencias ya amablemente dadas):

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

Esto ahora parece funcionar para todos los sabores de Ruby que he probado.


¿Has considerado 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


Basado en la respuesta de @Andrew Grimm, pero usando los argumentos de la palabra clave de 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

Tenga en cuenta que esto no permite la mezcla de argumentos regulares y de palabra clave; solo puede usar uno u otro.


Entre menos sepas, mejor. No es necesario saber si la estructura de datos subyacente utiliza símbolos o cadenas, o incluso si puede abordarse como Hash . Solo usa los setters de atributos:

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

Toma tanto argumentos posicionales como palabras clave:

> KwStruct.new "q", :zxcv => "z" => #<struct KwStruct qwer="q", asdf=nil, zxcv="z">


Para un equivalente de 1 a 1 con el comportamiento de Struct (aumentar cuando no se da el argumento requerido) lo uso a veces (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

y de allí en adelante

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

Esto generará un KeyError si se perdió un argumento, tan agradable para constructores y constructores más estrictos con muchos argumentos, objetos de transferencia de datos y similares.


Podría reorganizar el if s.

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


Si necesita mezclar argumentos regulares y de palabras clave, siempre puede construir el inicializador a mano ...

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

Esto tiene el título como un primer argumento obligatorio solo para ilustración. También tiene la ventaja de que puede establecer los valores predeterminados para cada argumento de palabra clave (¡aunque es poco probable que sea útil si se trata de películas!).


Si sus claves hash están en orden, puede llamar al operador splat para rescatarlas:

NavLink = Struct.new(:name, :url, :title) link = { name: '''', url: ''https://.com'', title: ''Sure whatever'' } actual_link = NavLink.new(*link.values) #<struct NavLink name="", url="https://.com", title="Sure whatever">


Sintetizar las respuestas existentes revela una opción mucho más simple para Ruby 2.0+:

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

El uso es idéntico al Struct existente, donde cualquier argumento no proporcionado será predeterminado a 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">

Si desea requerir los argumentos como los kwargs necesarios de Ruby 2.1 +, es un cambio muy pequeño:

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

En ese punto, la initialize anulación para dar ciertos valores predeterminados de kwargs también es factible:

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


Solo Ruby 2.x (2.1 si desea los argumentos args obligatorios). Solo probado en 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 )

Uso:

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

La desventaja menor es que debe asegurarse de que la lambda devuelve los valores en el orden correcto; la gran ventaja es que tienes todo el poder de las args de palabras clave de ruby ​​2.


Una solución que solo permite los argumentos de palabras clave de Ruby (Ruby> = 2.0).

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

Uso:

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

Este tipo de estructura puede ser realmente útil como un objeto de valor, especialmente si le gustan los accesadores de métodos más estrictos, que realmente generarán un error en lugar de devolver nil (al estilo de OpenStruct).


esto no responde exactamente a la pregunta, pero encontré que funciona bien si tiene una cantidad de valores que desea estructurar. Tiene el beneficio de descargar la necesidad de recordar el orden de los atributos sin necesidad de subClass Struct.

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

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

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