Search code examples
ruby-on-railsrubyformsrubygemsreform

Populating single property in Reform


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

Solution

  • 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"