How to edit a Rails serialized field in a form?

Viewed 27129

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?

8 Answers

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

emh is almost there. I would think that Rails would return the values to the form fields but it does not. So you can just put it in there manually in the ":value =>" parameter for each field. It doesn't look slick, but it works.

Here it is from top to bottom:

class Widget < ActiveRecord::Base
    serialize :options, Hash
end

<%= form_for :widget, @widget, :url => {:action => "update"}, :html => {:method => :put} do |f| %>
<%= f.error_messages %>
    <%= f.fields_for :options do |o| %>
        <%= o.text_field :axis_x, :size => 10, :value => @widget.options["axis_x"] %>
        <%= o.text_field :axis_y, :size => 10, :value => @widget.options["axis_y"] %>
    <% end %>
<% end %>

Any field you add in the "fields_for" will show up in the serialized hash. You can add or remove fields at will. They will be passed as attributes to the "options" hash and stored as YAML.

I was facing the same issue, after some research i found a solution using Rails' store_accessor to make keys of a serialized column accessible as attributes.

With this we can access "nested" attributes of a serialized column …

# post.rb
class Post < ApplicationRecord
  serialize :options
  store_accessor :options, :value1, :value2, :value3
end

# set / get values
post = Post.new
post.value1 = "foo"
post.value1
#=> "foo"
post.options['value1']
#=> "foo"

# strong parameters in posts_controller.rb
params.require(:post).permit(:value1, :value2, :value3)

# form.html.erb
<%= form_with model: @post, local: true do |f| %>
  <%= f.label :value1 %>
  <%= f.text_field :value1 %>
  # …
<% end %>

I'm trying to do something similar and I found this sort of works:

<%= form_for @search do |f| %>
    <%= f.fields_for :params, @search.params do |p| %>
        <%= p.select "property_id", [[ "All", 0 ]] + PropertyType.all.collect { |pt| [ pt.value, pt.id ] } %>

        <%= p.text_field :min_square_footage, :size => 10, :placeholder => "Min" %>
        <%= p.text_field :max_square_footage, :size => 10, :placeholder => "Max" %>
    <% end %>
<% end %>

except that the form fields aren't populated when the form is rendered. when the form is submitted the values come through just fine and i can do:

@search = Search.new(params[:search])

so its "half" working...

Related