Perhaps you have already learned about mocking and stubbing in tests. Ruby has a secret tool that will unlock the Ruby magic in ways you never thought possible. The tool is called OpenStruct and it can do anything. It can replace anything.
In other words, it’s a Struct object that is implemented as an OpenStruct. When you create an OpenStruct, you are creating a dummy object.
In Ruby, if it looks like a duck, walks like a duck, and quacks like a duck, then it is probably a duck. That’s why we call it “duck typing.”
Now imagine having a duck in your application that can act like anything else. That’s what OpenStruct is. OpenStruct will create a dummy object, like a mock in a test, that will behave as if it is a real object. You create an OpenStruct passing it a hash. The keys of the hash should be symbols, and those will become the method names on your dummy object.
Now imagine having a duck in your application that can act like anything else. That’s what OpenStruct is.
Normally, I might have a User object:
user = User.create({email: "dummy@example.com"})
With an OpenStruct, you can create an object that would stand in place of that object without having to make a real object. You can skip object instantiation and also skip creating a database record, if in the context of ActiveRecord
person = OpenStruct.new({email: "noone@gmail.com"})
However, OpenStruct is even more powerful than that, because it can stand in place of any object, including any of Ruby or Rails’ objects.
Let’s say we have a user object with a timezone. The timezone is stored in the database as an integer in number hours offset from UTC (+0000). Our form to update a user might look like so:
<%= form_with @user do |f| %>
<%= f.text_field :first_name %>
<%= f.collection_select(:timezone, UsTimezone.all, :value, :label, {:prompt => true, value: account.try(:timezone) } ) %>
<% end %>
Here, we wan to use a simple Rails collection_select
to display a list of timezones for the user to choose from:
Parameters #3 and #4 For Collection Select
In this example, let’s assume that we want to skip the overhead of creating a database table for timezones. Here’s a quick hack to use collection select with “nothing.”
I will use collection select with an ephemeral list that does not get saved into the database. It exists only in memory. Enter: OpenStruct.
In this pattern, we tell collection_select to use the methods value
and label
(or whatever you specify as a hash) on the returned collection. That’s what parameters #3 and #4 mean when you pass :value
and :label
as symbols to collection_select.
Normally, collection_select calls back to the ActiveRecord object, fetching the values and label for the list display. In this technique, I’ve passed a fake-out object (an array) to the collection_select
. The fake-out is an array of OpenStructs , which behave like objects that the collection select can call the “value” and “label” methods on. You do this when you make the OpenStruct. Specify the name of the “dummy” method name :value
and :label
as symbols.
class UsTimezone @@_US_TIMEZONES = { -5 => 'Eastern', -6 => 'Central', -7 => 'Mountain', -8 => 'Pacific', -10 =>'Hawaii–Aleutian' } def self.all @@_US_TIMEZONES.collect{|k,v| OpenStruct.new({label: v, value: k})} end def self.utc_to_name(input) # in hours utc = input[0...-2].to_i return @@_US_TIMEZONES[utc] endend
And just like that our fake-out collection select object can show the timezones in the United States without you having to store them in the database.