Intro to OpenStruct
(Cause every article needs a little gif)
Last week I had a task to start working with some CSV data pulled in from the database. There was already existing code working with the data, so I needed to make sure whatever changes were applied, that they were backwards compatible. The data was an csv representation of an array. The current code was hard coded to go through the array in a fairly rigid process, so to add some flexibility, and allow better access the data in a controller, it was suggested to use OpenStruct.
First a quick overview on what is OpenStruct, it is a data structure that is similar to a hashe. It applies arbitrary attributes with accompanying values. It seems like magic, but it is just Ruby’s metaprogramming and it defines the methods on the classes themselves. A basic example of using OpenStruct on an object might be:
require "ostruct"
candy = OpenStruct.new
candy.name = "Snickers"
candy.type = "Chocolate"
candy.amount = 5
Then if I needed to call and figure out the candy name, or type, I can call a method on the object itself (instead of having to do the Ruby hash way which would’ve been candy[:name] # => “Snickers”)
candy.name
> "Snickers"
candy.type
> "Chocolate"
So the code went from using:
@csv = CSV.parse(parsed_text)
To the following with OpenStruct:
@csv = CSV.parse(parsed_text, headers: true).map { |row| OpenStruct.new(row.to_h) }
Making use of the csv data now went from:
@list = @csv[1..-1] || []
@count = @list.count
And now:
@list = @csv.map { |row| row.to_h.values } || []
@count = @csv.count
Accessing the values was much easier, but it is important to note with OpenStruct:
- It is not performant, which wasn’t something that needed to be kept in mind for this piece of code but if it is something that you do need to keep in mind, go for a hash or struct.
- OpenStruct != Struct , struct is similar to a custom created class that you don’t need to initialize or have any attr for. Struct is more performant, OpenStruct more flexible. Struct has to have the attributes defined, while it is dynamic with OpenStruct. For the ex. we have name, type and amount but couldn’t add on later an listing for populatarity.
candy = Struct.new(:name, :type, :amount)
snickers = candy.new("Snickers", "Chocolate", 5)
snickers.type
> "Snickers"
Top comments (5)
Great post. I'll sometimes use OpenStruct in tests if I need a more complex mock. I think it reads better than some of the other mechanisms and you can do a lot with it. Recently, we needed to mock an API response which comes back as an object, so I used OpenStruct to create just the parts that were needed.
I know it's easy to reach for gems and libraries to help us, but sometimes you can get by just fine with PORO and Ruby standard library.
Awesome use-case, I might give that a try out too.
Why is it called OpenStruct? I see implementations of it in Ruby and Python. But I wonder if there is an open source specification to allow it to be defined in other languages?
From my understanding of it pertaining to Ruby, that it is similar to a Struct data structure type, but more open and flexible. So to me the naming works for that definition, OpenStruct. I am not totally sure if it it is open source specification for it. But you can always check out the Ruby source code definition for it, which would get you on your way for programming it in another language. github.com/ruby/ruby/blob/trunk/li...
Awesome thanks! I'll check it out.