12

I have the following.

class Page < ActiveRecord::Base
  belongs_to :category
  serialize :fields
end

The value of fields will depend on the category. But as an example;

{"address" => "8 finance street, hong kong",
 "founded" => "1973"}

In this example the category has defined "address" and "founded" as the custom fields.

What I want is to say;

= simple_form_for(@page) do |f|
  = f.association :category
  - f.object.category.fields.each do |field|
    = f.input field.name

But I need to do something magic to deal with the fact that @page.founded is not valid

Instead I should be looking at @page.fields["founded"].

Any suggestions?


Update:

I've got slightly nearer

- if f.object.category
  - f.object.category.fields.each do |field|
    = f.input field.name do
      = text_field_tag "post[fields][#{field.name}]", f.object.fields[file.name]

Now need to make this DRYer (don't want to specify the name of the object).

I'll see if I can write a decent simple form extension for this.

Matthew Rudy
  • 16,724
  • 3
  • 46
  • 44

4 Answers4

26

I ran into a similar issue trying to use simple_fields_for on a Hash field type of a Mongoid model. The Mongoid version of your example I was dealing with looked like this:

class Page
  include Mongoid::Document
  field :fields, type: Hash
end

My situation might be slightly different though, as I already know the hash keys I am looking for ahead of time, and just needed simple_fields_for to work with the hash field. The naive method (basic fields_for usage) I was using looked like this:

= simple_form_for(@page) do |f|
  = f.simple_fields_for :fields do |ff|
    = ff.input :address
    = ff.input :founded

But that wasn't populating the form properly. nicholaides's solution of wrapping the hash in a struct worked for me:

- require 'ostruct'
= simple_form_for(@page) do |f|
  = f.simple_fields_for :fields, OpenStruct.new(@page.fields) do |ff|
    = ff.input :address
    = ff.input :founded

To avoid having to mix the OpenStruct stuff into my view, I created a monkey patch for simple_form to automatically wrap hash types in an OpenStruct and put it into an initializer:

require 'ostruct'

module SimpleForm::ActionViewExtensions::Builder
  def simple_fields_for_with_hash_support(*args, &block)
    if args[0] && !args[1]
      field = object.send(args[0])
      args << OpenStruct.new(field) if field.respond_to?(:has_key?)
    end
    simple_fields_for_without_hash_support(*args, &block)
  end
  alias simple_fields_for_without_hash_support simple_fields_for
  alias simple_fields_for simple_fields_for_with_hash_support
end

And now I can use my original naive solution without special view code.

Abe Voelker
  • 30,124
  • 14
  • 81
  • 98
  • +1 for the "f.simple_fields_for :fields, OpenStruct.new(@page.fields) do |ff|". I wrecked my brain many an hour to work out why it wasn't retrieving values properly until I added the OpenStruct bit. – gk0r Sep 23 '13 at 05:32
  • 1
    This is awesome. An added note for strong parameters users: you must define each field you add explicitly. For example, `params.require(:page).permit(:fields => [:address, :founded])` – MacKinley Smith Jan 16 '15 at 21:04
  • Thx for the solution, it works perfectly, with one exception: I do not know, how to add validation errors to `@page.fields`. – Tibor Nagy Dec 06 '16 at 09:20
3

Use OpenStruct. It works like this:

require 'ostruct'

struct = OStruct.new("address" => "8 finance street, hong kong", "founded" => "1973")
struct.address # => 8
struct.founded # => "1973"
nicholaides
  • 19,211
  • 12
  • 66
  • 82
  • 1
    that's fine for the "callability" (I remember using a `CallableHash` back in the day`). But my specific question is about SimpleForm, really. I want a neat solution. – Matthew Rudy Mar 08 '12 at 05:19
1

If only I´ve found this post earlier I would not have missed three days on this issue.

I was not able to comment on the answear so just to add, if anyone trying to fields_for a mongo array of hashes (has_many like) additionally you will need to supply the root OpenStruct object with a "#{key}_attributes=" (in this case fields_attributes=) method so fields_for would identify it is a has_many relation.

Hope it helps someone ;)

Ravi Dhoriya ツ
  • 4,435
  • 8
  • 37
  • 48
1

Here is a solution you could easily adapt to your problem :

https://gist.github.com/2157877

Keeps the form super-clean and keeps the normal behaviour of an active model (nothing to add in your controller).

Cheers !

Clément
  • 1,399
  • 13
  • 19