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.
For a 1-to-1 equivalent with the Struct behavior (raise when the required argument is not given) I use this sometimes (Ruby 2+):
and from there on
This will raise a
KeyError
if you missed an argument, so real nice for stricter constructors and constructors with lots of arguments, data transfer objects and the like.Have you considered OpenStruct?
If you do need to mix regular and keyword arguments, you can always construct the initializer by hand...
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!).
Ruby 2.x only (2.1 if you want required keyword args). Only tested in MRI.
Usage:
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.
You could rearrange the
if
s.