Struct from Hash
Video transcript & code
In Episode #20, we learned about Struct
. And in Episode #25, we learned about OpenStruct
. One of the differences you might have noted between Struct
and OpenStruct
is that Structs
are always instantiated with positional arguments, whereas OpenStructs
are instantiated with a hash of keys and values. Considering all the extra features Structs
have over OpenStructs
, it seems almost like an oversight that we can't initialize a Struct
from a hash. Let's see if we can fix that in our own Structs
.
We'll start with a Struct
definition for beer stats. We'll write a new class-level method called .from_hash
. This method first creates a new, empty instance of the struct. Then it goes through the keys and values of the given hash, setting each one on the new instance. To do this it takes advantage of the hash-style subscript operator provided by Struct
Now we can instantiate a new Beer
instance using a hash of attributes.
Beer = Struct.new(:brewery, :name, :abv, :ibu) do
def self.from_hash(attributes)
instance = self.new
attributes.each do |key, value|
instance[key] = value
end
instance
end
end
hopback = Beer.from_hash(
brewery: "Tröegs",
name: "Hopback Amber Ale",
abv: 6.0,
ibu: 55)
hopback
# => #<struct Beer brewery="Tröegs", name="Hopback Amber Ale", abv=6.0, ibu=55>
It seems only logical that if we can get a new Beer
instance from a Hash, we ought to be able to convert one back to a hash of attributes as well. So we define an #attributes
method as well. Here, the introspection features of Struct
come in handy. We can use the #members method to get a list of the names of all the attributes, and set a value in the result hash for each one.
Beer = Struct.new(:brewery, :name, :abv, :ibu) do
def self.from_hash(attributes)
instance = self.new
attributes.each do |key, value|
instance[key] = value
end
instance
end
def attributes
result = {}
members.each do |name|
result[name] = self[name]
end
result
end
end
hopback = Beer.from_hash(
brewery: "Tröegs",
name: "Hopback Amber Ale",
abv: 6.0,
ibu: 55)
hopback.attributes
# => {:brewery=>"Tröegs", :name=>"Hopback Amber Ale", :abv=>6.0, :ibu=>55}
One of the questions I received in response to the Struct
episode was whether making use of the introspection and enumerability features of a Struct
would couple code too closely to the implementation of the object, and make it harder to migrate to a non-struct class later on. I think this is an excellent question. As I've shown here, the introspective capabilities of Struct
are quite useful internally, as part of the implementation details of methods. However, I would generally refrain from using Struct
-specific methods like the #members method from outside of the class. If you only use these facilities internally, it's easy to graduate your structs to an ActiveRecord model or some other kind of non-struct later on.
That's it for today. Happy hacking!
Responses