reform icon indicating copy to clipboard operation
reform copied to clipboard

Populator is not called when the form fragment is nil

Open shepmaster opened this issue 8 years ago • 7 comments

I have an existing API that passes nil for certain values that should actually be missing. To deal with this in a constrained manner, I'd like my form object to handle that.

After flailing around with skip_if, I was pointed towards populate. However, it appears that a populator is not called when the fragment is nil:

require 'reform'
require "reform/form/active_model/validations"

Reform::Form.class_eval do
  include Reform::Form::ActiveModel::Validations
end

Refinements = Struct.new(:name)

class RefinementsForm < Reform::Form
  property :name, default: "Alice", populator: ->(_) { skip! }
end

form = RefinementsForm.new(Refinements.new)

if form.validate("name" => nil)
  form.sync
else
  raise "Not validated"
end

puts "Name: #{form.name.inspect} (should be the default: Alice)"

This code will print

Name: nil (should be the default: Alice)

If I change the input to any other value: form.validate("name" => ""), then the populator is called, the skip! executed, and the output is correct:

Name: "Alice" (should be the default: Alice)

It feels incorrect that an explicit nil does not trigger the populator but it does set the value to nil.


Reform 2.2.1 with Ruby 2.3.1p112 on OS X

shepmaster avatar Sep 15 '16 17:09 shepmaster

I think it's likely to be related to OverwriteOnNil. This returns Representable::Pipeline::Stop, which is then returned from the pipeline iterator.

shepmaster avatar Sep 16 '16 16:09 shepmaster

The populator is not called for a nil fragment... that is indeed unexpected behavior.....

apotonick avatar Sep 18 '16 09:09 apotonick

Any suggested areas of Reform or it's dependencies you suggest I poke around in? Any kinds of test cases that would be useful to provide?

shepmaster avatar Sep 27 '16 18:09 shepmaster

I've encountered this issue trying to validate that a valid nested property (user) is set on a parent form. If I pass in 'user' => {email: '', name: ''} the parent form is correctly invalid because the user form requires email and name to be present. But if I pass in 'user' => nil, the user property does not get set to an instance of the user form, I assume because no populator is called for user. parent_form.user is nil. No validations run on user.

It would sort of make sense for the populator to be skipped if the input fragment was completely ignored and not written to the form. But skipping the user populator and still have user=() called, means I end up with an invalid user saved on the parent model.

(Note that I only get an invalid user saved if the form is initialized with a new user - that new user gets saved.)

ollieh-m avatar Oct 19 '17 15:10 ollieh-m

@ollieh-m you can solve your validation issue (if you're using dry-validation) but writing your validations on the parent: http://dry-rb.org/gems/dry-validation/nested-data/

Otherwise, you'd have to write a custom validator on the parent to check that :user is a hash and has two properties email, and name.

fran-worley avatar Oct 20 '17 06:10 fran-worley

Thanks @fran-worley. I'm not using dry-validation but I guess that's another reason to make the leap. I have got round the validation issue by just validating that user is present - so if user is set to nil, or is completely missing with no user form provided on initialization, the validation will fail...

ollieh-m avatar Oct 20 '17 07:10 ollieh-m

The populator is not called for a nil fragment... that is indeed unexpected behavior..... – @apotonick

Has there been any insight on this in the last few years? I came across this issue today because my populator wouldn't run (and the property was given a nil).

contentfree avatar Mar 21 '19 04:03 contentfree