Skip to content

Instantly share code, notes, and snippets.

@lucaspiller
Last active June 26, 2021 23:03
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lucaspiller/615f09bb525a14163921fd56b4b8e611 to your computer and use it in GitHub Desktop.
Save lucaspiller/615f09bb525a14163921fd56b4b8e611 to your computer and use it in GitHub Desktop.
Reform 2.2 nested form (with support for cocoon gem)
= f.simple_fields_for :inputs do |input|
= render 'input_fields', f: input
.links
= link_to_add_association f, :inputs, partial: 'input_fields', force_non_association_create: true do
Add
.nested-fields
= f.input :foo, as: :string
= f.input :bar, as: :string
= f.hidden_field :_destroy
= f.hidden_field :id
= link_to_remove_association f do
Delete
class IndicatorForm < Reform::Form
InputPrepopulator = -> (options) {
if inputs.size == 0
inputs << IndicatorInput.new
end
}
InputPopulator = -> (options) {
fragment, collection, index = options[:fragment], options[:model], options[:index]
if fragment["_destroy"] == "1"
# Marked for removal
collection.delete_at(index)
return skip!
else
(item = collection[index]) ? item : collection.insert(index, IndicatorInput.new).
end
}
collection :inputs, inherit: true, prepopulator: InputPrepopulator, populator: InputPopulator do
include NestedForm
property :foo
property :bar
end
end
module NestedForm
extend ActiveSupport::Concern
included do
property :id, writeable: false
property :_destroy, virtual: true
end
def new_record?
model.new_record?
end
def marked_for_destruction?
model.marked_for_destruction?
end
end
@pi-chan
Copy link

pi-chan commented Oct 18, 2016

I found that InputPopulator does not work on removing some items.
I've fixed this problem.

  InputPopulator = -> (options) {
    fragment, collection, index = options[:fragment], options[:model], options[:index]
    item = collection.find { |item| item.id.to_s == fragment[:id].to_s }

    if fragment["_destroy"] == "1"
      # Marked for removal
      collection.deletet(item)
      return skip!
    else
      item ? item : collection.insert(index, IndicatorInput.new)
    end
  }

@salza80
Copy link

salza80 commented Oct 21, 2016

I found problems with both. The last populator was overwriting the other new records when adding more than one new record at a time.
This works for me.

InputPopulator = -> (options) {
    fragment, collection, index = options[:fragment], options[:model], options[:index]

    if fragment[:id].to_s ==  "" 
      item=nil
    else
      item = collection.find { |item| item.id.to_s == fragment[:id].to_s }
    end

    if fragment["_destroy"] == "1"
      # Marked for removal
      collection.delete(item) if item
      return skip!
    else
      item ? item : collection.append(IndicatorInput.new)
    end
  }

@coatezy
Copy link

coatezy commented Feb 14, 2017

Hey! Have you experienced any issues with required fields the asterisk not showing on the simple_fields_for fields? It seem that cocoon is reflecting on the Models association and therefore building an object that has no validations on it as the validations are in Reform.

@ollieh-m
Copy link

I found the above very useful.

It may help others to share an issue I came across (using Cocoon and Rails with fields_for) if you have more than one level of nesting. That is, if within the _input_fields partial you want another link_to_add_association for a further has_many association.

The problem (here: https://github.com/nathanvda/cocoon/blob/master/lib/cocoon/view_helpers.rb#L95) is that cocoon bases the nested fields for an associated object on a pure instance of the model, rather than that model wrapped in the Reform form object. (Not a criticism, just the way it's designed.) That's fine until you want to render fields for an association on that nested object, because the setup for the nested attributes is defined in your Reform form object, not your model. Essentially f.object in _input_fields is the actual model, not the form object. Unless the model itself has accept_nested_attributes configured, fields_for won't treat the attribute as a nested_attributes_association and the html markup won't name the fields in such a way that reform-rails can parse them into properly nested parameters.

The solution for me was using wrap_object to ensure the record cocoon uses is the model wrapped in the relevant form object, like this:

<%= link_to_add_association 'Add Supporting Image', f, :images, partial: 'web_app/report/supporting_image_fields', force_non_association_create: true, wrap_object: Proc.new {|image| Report::WebAppCreation::ImageWithoutHotspotsForm.new(image) } %>

(There's probably a better way to get the form but this gives the idea).

@kvivek1115
Copy link

kvivek1115 commented May 26, 2021

This is really helpful, link_to_add_association raises undefined method reflect_on_association' for NilClass:Class`

in order to solve it

class IndicatorForm < Reform::Form
  ...
  def self.reflect_on_association(obj)
      Indicator.reflect_on_association(obj)
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment