OpenStructs for Rails Forms Instead of Models

July 11, 2007     

One little gem from the standard library that I only discovered a few months ago is the OpenStruct. An OpenStruct is just a hash with a method interface. That is open_struct.attribute is functionally similar to hash.attribute.

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."
      flash[:notice] = "Your message was sent."
      redirect_to ""

This allows you to use form_for on @contact, which gives you form persistence much more elegantly than manually cramming values back into raw text_fields.

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 ActionMailer.

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.