Populating single property in Reform

322 Views Asked by At

I'm using Reform gem. Let's say I have some form to which I pass two dates as two separate attributes in params. Now in form I want to instantiate another attribute (which is also existing model attribute) called interval to calculate interval between these two dates and then save it to model.

# some_form.rb

class SomeForm < Reform::Form
   property: :starts_on
   property: :ends_on
   property: :interval
end

What is the best way to populate interval based on incoming dates, validate it and save it on model?

I tried different things, from overriding validate method and using populator/prepopulator options, but to no avail.

In best scenario, I would assume it would look like this:

class SomeForm < Reform::Form
   property: :starts_on
   property: :ends_on
   property: :interval, populate: :calculate_interval
end

def calcute_interval
    (starts_on..ends_on).count
end
1

There are 1 best solutions below

0
Eureton On BEST ANSWER

populator and prepopulator assume that the relevant properties are present in the incoming data. In your case, interval is not.

Do you want to validate the interval value? If not, calculate interval in an Operation step and don't involve reform.

If you want validation, your options are:

  • a populator on either timestamp:

    # some_form.rb
    class SomeForm < Reform::Form
      property :starts_on
      property :ends_on, populator: ->(model:, fragment:, **) do
        self.interval = fragment - starts_on
        self.ends_on = fragment
      end
      property :interval, readable: false
    
      validation do
        required(:starts_on).filled(:time?)
        required(:ends_on).filled(:time?)
        required(:interval).filled(gt?: 2.hours) # or whatever value you like
      end
    end
    
  • a custom predicate+rule combo:

    # some_form.rb
    class SomeForm < Reform::Form
      property :starts_on
      property :ends_on
    
      # NOTE: uses dry-validation v0.13 syntax
      validation do
          configure do
            config.messages_file = "/path/to/your/error_messages.yml"
    
            def gt?(interval, starts_on, ends_on)
              ends_on - starts_on > interval
            end
          end
        end
    
        required(:starts_on).filled(:time?)
        required(:ends_on).filled(:time?)
        rule(ends_on: [:ends_on, :starts_on]) do |ends_on, starts_on|
          ends_on.gt?(starts_on, 2.hours) # or whatever value you like
        end
      end
    end
    
    # error_messages.yml
    en:
      errors:
        rules:
          ends_on:
            gt?: "must be greater than starts_on by %{interval} seconds"