OpenStructs for Rails Forms Instead of Models
One little gem from the standard library that I only discovered a few months ago is the
OpenStruct is just a hash with a method interface. That is
open_struct.attribute is functionally similar to
First things first, you gotta require it:
require 'ostruct' #In environment.rb or maybe a shiny initializer
Now why would I want anything but a hash? After all, I come from PHP where almost everything is an (ordered) hash, and it’s one of the few features of the language that I actually find useful, language purism aside.
For me the killer app of
OpenStruct is for Rails forms that aren’t backed by models. You should already know that you don’t need a database table behind every model, but sometimes even a model is overkill. An example would be a simple contact form. Your action might look like this:
def contact if request.post? @contact = OpenStruct.new(params[:contact]) if @contact.email.blank? flash.now[:error] = "Please fill out your email address." else Notifier.deliver_contact(@contact) flash[:notice] = "Your message was sent." redirect_to "" end end end
This allows you to use
@contact, which gives you form persistence much more elegantly than manually cramming values back into raw
Some might argue that the form validation criteria should be in a model, which I would agree with in more complex cases. But here the condition is so simple that moving it into a model would only add to the complexity of the architecture. If the validation criteria do get more complex we can easily refactor later.
If I were to refactor, I’d subclass my model from
OpenStruct. That makes the form more flexible, because I don’t need to explicitly add fields to the model. I can just stick them in the form as necessary;
OpenStruct doesn’t cry over missing methods. The one weakness is that
OpenStruct doesn’t provide a public method to return its keys. If you want to make a generic form processor, I recommend only using the
OpenStruct for the controller and view, but passing the params hash into your
If you’re really in a pinch you can illegally dip into Ruby internals with
@contact.send(:table).keys, but you didn’t hear that from me.