#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 yearmethod. 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.