Monday, 9 November 2015

How Do I Create a Struct from a Hash?

How Do I Create a Struct from a Hash?

I have a hash of values and I want to create a Struct object from them, how would you do that? Structs and hashes are structurally very similar, however there's not a very simple route to get from A to B. But with a bit of magic, you can get the job done.
A Struct is a bit of an odd thing in Ruby. Ruby is all about dynamic programming. You don't have to define rigid structs as in C or C++, you just make a hash and store values indexed by keys.
The fact that some hashes only have certain keys is merely a convention. If it has more keys (such as if you implemented a new feature), it doesn't break anything. Adding new elements to a structure in C or C++ could be disastrous if any code relies of the exact offset of some elements. So why would you want to go back to rigid structs?
One word: speed. Structs can be much faster, and perhaps more importantly they have predictable performance characteristics. One struct object isn't going to be slower that another, because unlike hashes they have a set, static set of elements.
To create a struct, you use the Struct.new method while gives you a new class definition. You can also give it a block which will be class evaled to allow you to define methods that will work on this struct. Typically, you see definitions like this.

Address = Struct.new(:street, :number)

Remember that class names are in fact just constant variables in disguise. So what this is really doing is producing a Class object using the Struct.new method and assigning it to the Address constant.
Later on you can construct a new Address object like so.

a = Address.new("Nowhere Rd", 998)

So, now that you know how structs normally work, say we have a hash and we just want to make it a struct. The hash, for example, is something like this: { street:"Nowhere Rd", number:998 } and we want a hash like the above example.
Well there's two options here. First we can use the above method to create a new class and then create a new object from that new named class. Note the use of the splat operator here. We're splatting both the keys and the values of the hash to create individual method parameters.

address_hash = { street:"Nowhere Rd", number:998 } 
Address = Struct.new(*address_hash.keys) 
address = Address.new(*address_hash.values) 

Great, how you have a new address struct with the same keys and values as the hash. However, what if you don't care about the class name? What if you don't even want to come up with a name for the class at all? Well, classes don't need to be named in Ruby. Names are just a convenience for the programmer. Ruby classes can exist without names, the object simply holds a reference to the Class object so it can tell what type it is. So we'll forgo the assignment to a const. Note that you won't be able to easily create a more instances of this type of struct and struct equality relies of two structs being of the same type and having the same values. Two struct objects with the same keys and the same value are not equal unless they're both the same class as well. This method only works if the structs don't need to be compared.

address_hash = { street:"Nowhere Rd", number:998 } 
address = Struct.new(*address_hash.keys).new(*address_hash.values) 

It can seem a bit strange, chaining the new method calls like that. Just remember that to create a struct dynamically you need to first create a new class then create a new instance of that class. Struct really is an oddball in the Ruby world, but it does have its uses.

No comments:

Post a Comment