How to edit a Rails serialized field in a form?

I have a data model in my Rails project that has a serialized field:

class Widget < ActiveRecord::Base
  serialize :options
end

The options field can have variable data info. For example, here is the options field for one record from the fixtures file:

  options:
    query_id: 2 
    axis_y: 'percent'
    axis_x: 'text'
    units: '%'
    css_class: 'occupancy'
    dom_hook: '#average-occupancy-by-day'
    table_scale: 1

My question is what is the proper way to let a user edit this info in a standard form view?

If you just use a simple text area field for the options field, you would just get a yaml dump representation and that data would just be sent back as a string.

What is the best/proper way to edit a serialized hash field like this in Rails?


If you know what the option keys are going to be in advance, you can declare special getters and setters for them like so:

class Widget < ActiveRecord::Base
  serialize :options

  def self.serialized_attr_accessor(*args)
    args.each do |method_name|
      eval "
        def #{method_name}
          (self.options || {})[:#{method_name}]
        end
        def #{method_name}=(value)
          self.options ||= {}
          self.options[:#{method_name}] = value
        end
        attr_accessible :#{method_name}
      "
    end
  end

  serialized_attr_accessor :query_id, :axis_y, :axis_x, :units
end

The nice thing about this is that it exposes the components of the options array as attributes, which allows you to use the Rails form helpers like so:

#haml
- form_for @widget do |f|
  = f.text_field :axis_y
  = f.text_field :axis_x
  = f.text_field :unit

Well, I had the same problem, and tried not to over-engineer it. The problem is, that although you can pass the serialized hash to fields_for, the fields for function will think, it is an option hash (and not your object), and set the form object to nil. This means, that although you can edit the values, they will not appear after editing. It might be a bug or unexpected behavior of rails and maybe fixed in the future.

However, for now, it is quite easy to get it working (though it took me the whole morning to figure out).

You can leave you model as is and in the view you need to give fields for the object as an open struct. That will properly set the record object (so f2.object will return your options) and secondly it lets the text_field builder access the value from your object/params.

Since I included " || {}", it will work with new/create forms, too.

= form_for @widget do |f|
  = f.fields_for :options, OpenStruct.new(f.object.options || {}) do |f2|
    = f2.text_field :axis_y
    = f2.text_field :axis_x
    = f2.text_field :unit

Have a great day