I'm trying to save a hash of options in a single DB field. The form is able to save the data to the DB but not able to retrieve it again when I go to edit it (e.g. all the other fields are prepopulated except for the wp_options fields).
class Profile < ActiveRecord::Base
serialize :wp_options
end
This is my custom class:
class WP_Options
attr_accessor :wp_name, :wp_desc, :wp_limit
end
In my form:
<%= form_for(@profile, :remote => true) do |f| %>
...
<%= f.fields_for :wp_options do |wp_options| %>
<%= wp_options.text_field :wp_name %>
<% end %>
...
In my controller:
@profile = Profile.new(:wp_options => WP_Options.new)
In my DB column 'wp_options':
--- !map:ActiveSupport::HashWithIndifferentAccess
wp_name: Test
Any advice would be really appreciated.
Actually it's easy. You need to use class with reader
methods. You can create it with a different ways, but the easiest one is to use OpenStruct
class (notice, that it will be unable to see fields that are in the OpenStruct's instance methods... this class cannot redefine methods).
In your form you should add:
<%= f.fields_for :wp_options, @profile.wp_options do |wp_options| %>
Instead of @profile (if you have dynamic variable) you can use f.object.wp_options
.
And to the model Profile
you should add wp_options
method.
def wp_options
OpenStruct.new(self.attributes['wp_options'])
end
In that case it will only work if your serialized wp_options is a Hash class.
Hope that helps.
PS. I used same technique, but because I had type
hash keys, OpenStruct were unable to create it, so I used simple Struct class. I had data
column:
def data
keys = current_data.keys
data = attributes[:data]
Struct.new(*keys).new(*keys.map { |k| data[k] })
end
A little less trivial, but anyway the same approach (before that I've created special class, but now I know that Struct is the better way to creating of that kind stuff. More ideas you can find here: How do I use hash keys as methods on a class?)
For those using Dmitry's solution, on rails 3.0.5 (not sure about prev/next versions so try before), add
require 'ostruct'
to your environment.rb file in order to have OpenStruct included.
If you're going to serialize with JSON, in your model add (oh and I'm quite new to RoR, so please feel free to scrutinize and suggest a better, "working" solution then this one) (my variable names changed to match the above example):
before_save :json_serialize
after_save :json_deserialize
after_find :json_deserialize
def json_serialize
self.wp_options = ActiveSupport::JSON.encode(self.attributes['wp_options'])
end
def json_deserialize
unless (self.attributes['wp_options'].nil?)
self.wp_options = ActiveSupport::JSON.decode(self.attributes['wp_options'].to_s)
end
end
On a final note: I'm honestly not sure why it has to be stored into self.wp_options and not self.attributes['wp_options'], if someone could explain it would be cool.
I encountered the same problem. My solution was the following:
Define a method in your helper:
def wp_options_text_field(profile, wp_options, name)
wp_options.text_field name, :value => profile.wp_options[name]
end
In your view:
<%= form_for(@profile, :remote => true) do |f| %>
...
<%= f.fields_for :wp_options do |wp_options| %>
<%= wp_options_text_field :wp_name %>
<% end %>
...
The only problem here is that you'll need to define methods for each helper method you'll use. In my case I had only 2 methods used and it wasn't painful.