Old Ruby hands will know that you can use the splat operator * to turn an array into a list of arguments:
def fred(x, y, z)
puts x
puts y
puts z
end
argy_bargy = [1, 2, 42]
fred(*argy_bargy)
=> 1
=> 2
=> 42
There are also another trick you can do with this:
a = 1
puts [*a]
=> [1]
a = [1]
puts [*a]
=> [1]
Where the dereferencing lets you ensure you are always using an array. This is thought to be a bit old fashioned now and Rubyists now use the Array() method that does the same thing but with less syntactic magic. Back in the days before Ruby 2 (or so) it was the only way to do it.
I’m a great believer in trying to avoid the cardinal sin of Primitive Obsession – in Ruby this usually manifests itself as storing hashes everywhere and ending up with lots of code full of several levels of square brackets that manipulates them that ends up being parked in the wrong place. Ironically Rails is full of this and you often find yourself peering through documentation looking for the options hash to see what you can ask an object to do.
So in an ideal world you might want to be able to create object on the fly from hashes (say ones stored as JSONB in a Postgres database for instance 😉) that have some intelligence in them beyond the simple, somewhat cumbersome, data carrier that Hash offers. So you could try something like this:
Thing = Struct.new(:x, :y)
harsh = { x: 1, y: 2 }
wild_thing = Thing.new(*harsh.values)
The only problem here is you lose the useful semantics of the Hash, if it is initialised in a different order, or from a merge, then you’re screwed. The hash keys and the Struct’s keys having the same names is a mere semantic accident. This isn’t what you want. Instead let’s use the double splat:
class Thing
attr_reader :x, :y
def initialize(x:, y:)
@x, @y = x, y
end
end
harsh = { x: 1, y: 2 }
wild_thing = Thing.new(**harsh)
You now have a way to create an object you can start adding methods to from your hash source that is isolated from argument order changes. There is one last wrinkle, which is that JSONB keys are strings, so you need:
hash_from_json = { "x" => 1, "y" => 2 }
wild_thing = Thing.new(**hash_from_json.symbolize_keys)
Now you’re almost ready to be able to take chunks of structured JSON and quickly and reliably turn it into usable objects with things like the <=> operator in them
instead["of"]["quoted"]["nightmare"]
That’s a Hash and nothing more. You can even add methods that implement [] and []= to keep Hash-like capabilities with a bit of meta programming, perhaps for backward compatibility, if you really have to.
I have arrays of these, that map to diary entries in an app I’m working on. I’m also very very lazy when I expect the computer to do the work, I want to solve problems once and they stay solved with as little fiddly RSI creating pressing shift to get brackets and things as possible. So:
class DiaryEntry
attr_reader :name, :start_time, :finish_time
def initialize(name:, start_time:, finish_time:)
@name, @start_time, @finish_time = name, start_time, finish_time
end
end
def apply_schedule(schedule, offset:)
make_entry = -> (entry_hash) {
DiaryEntry.new(**entry_hash.symbolize_keys)
}
all_entries = diary_entries.map(&make_entry)
# ...
end
Code has been split across multiple lines to ease understanding. I now have a nice array of proper objects that I can filter and add to the map using methods instead of clumsy brackets everywhere. We can sort them and so on if we wish too.
This last example also shows one of my favourite techniques, which is to remap arrays of things using simple lambda functions, or use them with methods like filter inherited from Enumerable, a named lambda isn’t always necessary but it follows the useful pattern intention revealing name that can make you code much clearer. Using lambdas like this means then can also be passed as arguments to methods for filtering or formatting, that can be very functional-style without having to go all object-oriented.
For extra marks, dear reader, there’s a post on stack overflow that uses a technique like this to create structs from hashes on the fly. See if you can find it and come up with some uses for it, I’m sure there are many.