-
-
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 |
In my use case, I want the field to show up, but not be required. When saving, I don't want to create the associated record if it is blank.
@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
@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 build
instead.
# 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
end
Also, 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
end
Here is an update that works with Reform 2.2.1:
https://gist.github.com/lucaspiller/615f09bb525a14163921fd56b4b8e611
Hi @bkildow, Maybe what you've done with the rejection part of your script can be accomplished with validations. I don't see the difference between the
reject_if_blank :minor_name
line and avalidates :minor_name, presence: true