- 
      
- 
        Save bkildow/0c1bd0db8d1e4acdbb1c to your computer and use it in GitHub Desktop. 
| # app/forms/graduation_form.rb | |
| require 'concerns/nested_form' | |
| class GraduationForm < Reform::Form | |
| # Needed for correct behavior of virtual attributes, see https://github.com/apotonick/reform/issues/75 | |
| reform_2_0! | |
| model :response | |
| collection :minor_responses, populate_if_empty: MinorResponse do | |
| include Reform::NestedForm | |
| property :minor_name | |
| # only save this if minor_name isn't blank | |
| reject_if_blank :minor_name | |
| end | |
| end | 
| # app/forms/concerns/nested_form.rb | |
| # Helps facilitates nested forms. loosely based on: https://github.com/apotonick/reform/issues/86#issuecomment-73918238 | |
| # Adds the ability to exclude saving if a particular field is blank, and helper methods to make cocoon gem work. | |
| module Reform | |
| module NestedForm | |
| extend ActiveSupport::Concern | |
| included do | |
| property :id, virtual: true | |
| property :_destroy, virtual: true | |
| @reject_field = [] | |
| end | |
| def sync_hash(options) | |
| if fields._destroy == '1' || reject_fields? | |
| model.mark_for_destruction | |
| end | |
| super(options) | |
| end | |
| def new_record? | |
| model.new_record? | |
| end | |
| def marked_for_destruction? | |
| model.marked_for_destruction? | |
| end | |
| def reject_fields? | |
| self.class.reject_field.any? { |f| fields[f].blank? } | |
| end | |
| class_methods do | |
| def reject_if_blank(field) | |
| @reject_field << field | |
| end | |
| def reject_field | |
| @reject_field | |
| end | |
| end | |
| end | |
| end | 
| # app/models/response.rb | |
| class Response < ActiveRecord::Base | |
| # autosave: true is needed to get mark_for_destruction | |
| has_many :minor_responses, dependent: :destroy, autosave: true | |
| end | 
@bkildow This worked for me with Trailblazer contracts, although I was getting undefined method 'build' for #<Disposable::Twin::Collection:...>
I found a workaround by overriding Cocoon::ViewHelpers#create_object_on_association. It expects the form collection to be ActiveRecord, with a build method, but Disposable::Twin::Collection doesn't have build. So I ask the model for the raw collection's buildinstead.
# via https://github.com/nathanvda/cocoon/blob/be59abd99027b0cce25dc4246c86d60b51c5e6f2/lib/cocoon/view_helpers.rb#L133-L136
module Cocoon
  module ViewHelpers
    def create_object_on_association(f, association, instance, force_non_association_create)
      if instance.class.name == "Mongoid::Relations::Metadata" || force_non_association_create
        create_object_with_conditions(instance)
      else
        assoc_obj = nil
        if instance.collection?
          if f.object.respond_to?(:model) && f.object.send(association).is_a?(Disposable::Twin::Collection)
            # HACK! Add Disposable::Twin::Collection support
            assoc_obj = f.object.model.send(association).build
            f.object.model.send(association).delete(assoc_obj)
          else
            # assume ActiveRecord or compatible
            assoc_obj = f.object.send(association).build
            f.object.send(association).delete(assoc_obj)
          end
        else
          assoc_obj = f.object.send("build_#{association}")
          f.object.send(association).delete
        end
        assoc_obj = assoc_obj.dup if assoc_obj.frozen?
        assoc_obj
      end
    end
  end
endAlso, if you get undefined method 'reflect_on_association', be sure in your contract you include Reform::Form::ActiveModel::ModelReflections.
One more Trailblazer note: sync_hash is no longer part of the Reform API. So I commented out sync_hash and instead followed the instructions in the Trailblazer book re: "Removing Collection Items".
So the collection's skip_if looks like:
def skip_item?(fragment, options)
  # don't process if it's getting destroyed!
  if fragment["_destroy"] == "1"
    items.delete(item.find { |x| x.id.to_s == fragment["id"] })
    return true
  end
endHere is an update that works with Reform 2.2.1:
https://gist.github.com/lucaspiller/615f09bb525a14163921fd56b4b8e611
@bkildow thanks for this just started refactoring an App that makes heavy use of cocoon to include reform - How Cocoon and reform would interact concerned me but evidently it is do able