A few months ago I created a custom class to enhance the base Ruby Struct object. What I needed for my Whois library was the ability to create a new instance from a Hash.
Loser = Struct.new(:id, :name)
Winner = SuperStruct.new(:id, :name)
# The default Struct accepts up to N arguments
# where N is the total number of elements defined in the Struct.
Loser.new(:id => "1", :name => "John Doe")
# => #<struct Loser id={:name=>"John Doe", :id=>"1"}, name=nil>
Winner.new(:id => "1", :name => "John Doe")
# => #<struct Winner id="1", name="John Doe">
Because the first argument is mapped to the :id element, the Hash is stored as value for the id attribute.
In a standard Ruby Struct I have to map each Hash value to the specific position in the Struct, loosing the flexibility of passing a Hash with optional keys.
hash = { :name => "John Doe" }
Loser.new(hash)
# => #<struct Loser id={:name=>"John Doe"}, name=nil>
Loser.new(nil, hash[:name])
# => #<struct Loser id=nil, name="John Doe">
Winner.new(hash)
# => #<struct Winner id=nil, name="John Doe">
Also, I'd like to be able to pass a block to the initialize method and yield on self.
Loser.new do |c|
c.name = "John Doe"
end
# => #<struct Loser id=nil, name=nil>
# Name is not set
Winner.new do |c|
c.name = "John Doe"
end
# => #<struct Winner id=nil, name="John Doe">
# Hurra! Name is set!
I called it RubyStruct. It was created for the Ruby Whois, but ultimately I found myself using it quite often in my projects and I decided to package it in a single file along with a basic test suite.
Here's the library.
| # | |
| # = SuperStruct | |
| # | |
| # SuperStruct is an enhanced version of the Ruby Standard library <tt>Struct</tt>. | |
| # | |
| # Compared with the original version, it provides the following additional features: | |
| # * ability to initialize an instance from Hash | |
| # * ability to pass a block on creation | |
| # | |
| # You can read more at http://www.simonecarletti.com/blog/2010/01/ruby-superstruct/ | |
| # | |
| # Category:: Standard | |
| # Package:: SuperStruct | |
| # Author:: Simone Carletti <weppos@weppos.net> | |
| # License:: MIT License | |
| # Source:: http://gist.github.com/271214 | |
| # | |
| require 'ostruct' | |
| class SuperStruct < Struct | |
| # Overwrites the standard Struct initializer | |
| # to add the ability to create an instance from a Hash of parameters | |
| # and pass a block to yield on self. | |
| # | |
| # SuperEroe = SuperStruct.new(:name, :nickname) | |
| # | |
| # attributes = { :name => "Pippo", :nickname => "SuperPippo" } | |
| # SuperEroe.new(attributes) | |
| # # => #<struct SuperEroe name="Pippo", nickname="SuperPippo"> | |
| # | |
| # SuperEroe.new do |s| | |
| # s.name = "Pippo" | |
| # s.nickname = "SuperPippo" | |
| # end | |
| # # => #<struct SuperEroe name="Pippo", nickname="SuperPippo"> | |
| # | |
| def initialize(*args, &block) | |
| if args.first.is_a? Hash | |
| initialize_with_hash(args.first) | |
| else | |
| super | |
| end | |
| yield(self) if block_given? | |
| end | |
| private | |
| def initialize_with_hash(attributes = {}) | |
| attributes.each do |key, value| | |
| self[key] = value | |
| end | |
| end | |
| end | |
| if $0 == __FILE__ | |
| require 'test/unit' | |
| class SuperStructTest < Test::Unit::TestCase | |
| SuperEroe = Class.new(SuperStruct.new(:name, :supername)) | |
| def setup | |
| @klass = SuperEroe | |
| end | |
| def test_initialize_with_block | |
| @klass.new do |instance| | |
| assert_instance_of SuperEroe, instance | |
| assert_kind_of SuperStruct, instance | |
| end | |
| end | |
| def test_initialize_with_hash | |
| instance = @klass.new(:name => "Pippo", :supername => "SuperPippo") | |
| assert_equal "Pippo", instance.name | |
| assert_equal "SuperPippo", instance.supername | |
| end | |
| end | |
| end |