Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Named Parameters in Ruby Structs

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 items in the Struct get what values, and default the rest to nil.

For example I want to do:

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

This doesn't work.

So I came up with the following:

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' 

This seems to work just fine, but I'm not sure if there's a better way of doing this, or if I'm doing something pretty insane. If anyone can validate/rip apart this approach, I'd be most grateful.

UPDATE

I ran this initially in 1.9.2 and it works fine; however having tried it in other versions of Ruby (thank you rvm), it works/doesn't work as follows:

  • 1.8.7: Not working
  • 1.9.1: Working
  • 1.9.2: Working
  • JRuby (set to run as 1.9.2): not working

JRuby is a problem for me, as I'd like to keep it compatible with that for deployment purposes.

YET ANOTHER UPDATE

In this ever-increasing rambling question, I experimented with the various versions of Ruby and discovered that Structs in 1.9.x store their members as symbols, but in 1.8.7 and JRuby, they are stored as strings, so I updated the code to be the following (taking in the suggestions already kindly given):

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' 

This now appears to work for all the flavours of Ruby that I've tried.

like image 853
Matt S. Avatar asked Mar 23 '11 15:03

Matt S.


1 Answers

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"> 
like image 126
indirect Avatar answered Oct 06 '22 23:10

indirect