Building attribute structures with Ruby.
The origin story
I’m a big fan of JSON serialization and Hash/Array collection types in general. Awhile back, I was writing an LWRP for the lxc cookbook and decided I wanted to do “sub-resources”. Basically nest resources within a “parent” resource. Yet, these nested resources I also wanted to be properly standalone. To facilitate this, I simply defined a method instead of an attribute within my resource file, and cached the name and proc into an instance variable:
1 | # resources/thing.rb |
Now with this subresources
reader, in the provider I can cycle
the defined resources and provide them within my parent resource:
1 | # providers/thing.rb |
Cool, this provides an easy way including the definition of a standalone
resource within a resource (though the real implementation is a little
different than this example). But this sub-resource stuff isn’t really
the point here. It got me thinking about this “nesting”, and if I wanted
to nest n
number of resources, with y
depth of resources nested, what
would that look like? Could it be done in a generic way? This is what led
to the creation of the attribute_struct
library.
Let make some magic
At this point, I was still messing around in a resource file using #method_missing
and making a complete mess. But, in the mess was something that was starting
to work. The #method_missing
could provide some of what I wanted, but it was
flat, which meant only a single subresource (no infinite nesting). Time to
break out to a clean slate.
The attribute_struct
library is mostly just a single file. In the beginning it
was very very small, consisting of just the #method_missing
definition. The
approach was pretty simple. Underneath the hood, it’s all just Hashes. Everything
else is just sugar to make building them, and working with them easier. The
behavior that was defined within an AttributeStruct
instance is as follows:
- A method call with no argument: request for the value
- A method call with an argument: request to set the value
- A method call with block: set new
AttributeStruct
and execute block within
Pretty simple behavior that results in some pretty great things. This behavior
allows for building deeply nested hashes very easily. Lets look at a simple
example of building a Hash
directly, and build it using AttributeStruct
:
1 | # direct hash |
1 | # via attribute_struct |
Under the hood AttributeStruct
is using the #method_missing
magic
to see that a
is not defined within it’s underlying data hash, so it
sets an entry into the data hash with the value being a new instance
of AttributeStruct
(which when empty .nil? == true
). This allows
us to automatically create the required nested “hashes” as we traverse
to the final set.
Cool!
Re-entry
What we end up with is a large (perhaps nested) hash getting created. There is no “compile” or anything like that happening behind the scenes. The underlying data hashes are being built in real time, which means that you can easily update existing state:
1 | AttributeStruct.new do |
Which results in:
1 | {'my' => {'configuration' => {'value' => 22, 'other_value' => 23}}} |
We can also grab the parent of the depth we are currently at:
1 | AttributeStruct.new do |
Which results in:
1 | {'my' => {'configuration' => {'value' => 22, 'other_value' => 22}}} |
Results
The result of the attribute_struct
library is a slim DSL for building
hashes. It includes other features, like deep merging two AttributeStruct
objects, automatically camel or snake casing keys, and folding values
to produce an array when the same key is set multiple times. All of these
things provide a very powerful toolkit for building complex hashes.
Why do we care about Hashes, or even building complex Hashes? Because in the end, everything is a Hash.