Ure Gem
#I call it ure, because it follows Struct
I recently published Ure, a Ruby Gem that attempts to improve on Ruby’s Struct Class.
##A short introduction to the problems with Struct
Ruby’s Struct is used as a data object. It holds values, but doesn’t have logic. This is especially useful when you have a method or a class that is a factory, that you use to build a bunch of data objects.
For example, let’s say we want to have a bunch of Car
data objects, that are all going to have a paint
method and a year
method. We could do that with a Ruby Class, and we probably would in a lot of cases, but sometimes we only want a few simple methods like to_s
, to_a
and the paint
and year
. Making a class can be overkill in these circumstances.
There is always the option of just using a hash, but then we don’t get the nice dot methods of a Class. In Ruby, the middle ground is Struct.
Let’s build a Car with Struct in pry or irb.
Car = Struct.new(:paint, :year)
=> Car
mystery_machine = Car.new(:mural, 1965)
=> #<struct Car paint={:paint=>:mural, :year=>1965}
###Problem One: No required keyword arguments
Already we notice something weird about Struct. In modern ruby, we have required keyword arguments. What happens if we try to use them on struct?
Car = Struct.new(paint:, year:)
SyntaxError: unexpect ','
Car = Struct.new(paint:, year:)
That’s not cool at all.
###Problem Two: Default attr_accessors
I love me some attr_readers in Ruby, but I almost never run into situations where I write attr_accessors. Ruby’s Struct
comes with them built in.
Car = Struct.new(:paint, :year)
mystery_machine = Car.new(:mural, 1965)
mystery_machine.paint
=> :mural
mystery_machine.paint = "Pink Floyd Album Cover"
mystery_machine.paint
=> "Pink Floyd Album Cover"
Boo.
###Problem Three: Evil nils
The longer I write ruby code the less I like nil. I want my code to explode when and where it gets something unexpected. Ruby’s nil often shows up in unexpected places and causes frustrating NoMethodErrors. Struct
is a terrible offender in these regards. Because it behaves like a hash, it lets you initialize Structs with default nil values. For example:
Car = Struct.new(:paint, :year)
van = Car.new
van.paint
=> nil
van.year
=> nil
For the love of…double boo.
Fortunately, Ure can fix all of these for us.
###Required keyword arguments
Car = Ure.new(:paint, :year)
=> Car
mystery_machine = Car.new(paint: "mural", year: 1965)
=> #<Car:0x2af83a7368a8>
mystery_machine.paint
=> "mural"
mystery_machine.year
=> 1965
You’ll notice that I wasn’t able to get the exact behavior of the required keyword arguments. The initial setup looks like a standard Struct
object, and not a class, but when you build the Ure
object it requires you to treak the :paint
and :year
as keywords. Anyway, it gets the behavior I want.
###attr_readers only
mystery_machine.year = 2016
RuntimeError: can't modify frozen #<Ure Car {:paint=>"mural", :year=>1965}
That’s more like it. Let’s keep those data objects immutable.
###No more nils
Car = Ure.new(:paint, :year)
van = Car.new
ArgumentError: missing keyword: paint
van = Car.new(paint: :mural)
ArgumentError: missing keyword: year
Sweet.
I’m not saying this is the best way to build data objects, but it’s very close to the kind of data object I want, and it acts very similarly to how I would expect a data object to behave in Ruby.