Reform 2.1 With Dry-Validation and Grouping

Just in time for Christmas, Reform 2.1 is ready for you. It has two great new additions: we now support the awesome Piotr Solnica’s dry-validation gem, and I introduced validation groups.

Reform is a form object gem that decouples validation of data from models. Its full documentation can be found on the Trailblazer website.

Validation Groups

Traditional validation gems like ActiveModel::Validations only allow a linear flow of validations. All defined validations will be run, even though in specific cases it doesn’t make sense. We get around that limitation now in Reform with validation groups.

Here’s a very simplified example.

 
class SessionForm < Reform::Form
  property :username
  property :email

  validation :default do
    validates :username, :email, presence: true
  end

  validation :email_format, if: :default do
    validates :email, email: true
  end
end

You can now group sets of validation and name them using validation.

Those can then be chained using :after and, in this example, :if. The second group :email_format is only executed if the :default group was valid, saving you any conditionals in the following validations.

Validation still happens by calling form.validate(params).

This opens the way to a completely new understanding of validations as predicates and results.

Dry-validation

Speaking of predicates and all those logic terms: We now support Dry-validation as another validation backend. Since this is a relatively new, fast and very strict implementation, we will use it as default in future Reforms.

 
class LoginForm < Reform::Form
  property :password
  property :confirm_password

  validation :default do
    key(:password, &:filled?) # :password required.
    key(:confirm_password) do |str|
      str.filled? & str.correct?
    end

    def correct?(str)
      str == form.password
    end
  end

Without going into dry-validation’s API details too much: In a validation group you can use the exact same API as in a Dry::Validation::Schema, with chaining, predicates, custom validations, and so on.

Error messages in dry-validation are generated via YAML file that can easily be extended, ending the age of ActiveModel’s translation logic madness.

Populator API

In Reform 2.1, all populators now receive one options hash, which allows using Ruby’s keyword arguments.

 
class SessionForm (fragment:, **) do
      self.user = fragment["id"] ? User.find(1) : User.new
    end, # ..

The old API still works, but is deprecated.

Skip!

If you ever had the need to make Reform suppress deserialization of a fragment, this is simpler now with the new skip! method.

 
  property :user,
    populator: ->(fragment:, **) do
      return skip! if fragment["id"]
      # more code
    end, # ..

What used to be a combination of :populator and :skip_if can now be combined. Once skip! has returned, Reform will ignore the currently processed fragment, as if it hadn’t been in the incoming hash at all.

Documentation for skip and populators is here.

Good Bye, ActiveModel!

What sounds bewildering to many of you is a consequent step in tidying up the Ruby world: We will drop support for ActiveModel::Validations in Reform 2.2. Don’t you worry, everything will still work the way it did before, we just don’t want to waste time with AM:V and its prehistoric implementation anymore.

Most trouble we had with the way AM:V computes error messages. It gets worse when those have to be translated. AM:V has an extremely complex implementation, jumps between instance and class context, and makes wild assumptions about object interfaces. Since Rails core seems uninterested in changing anything, because it might break Basecamp, for us it’s easiest to just let it be and move on with an alternative.

Also, when using validators like confirm or acceptance values in the form suddenly were changed because those implementations write to the validated object – a very wrong thing to do. You might also have a look into how AM:V finds validators: a cryptic, magic class traversal happens here and it is a nightmare to make AM:V use custom validators in a non-Rails environment.

We ended up with too many patches and hacks – very frustrating for the maintainers. Since there’s better, less constraining alternatives, we all will benefit from a better validation workflow.

Advertisement

3 thoughts on “Reform 2.1 With Dry-Validation and Grouping

  1. I’ll would like to use Trailblazer in my Rails apps. Hence some questions.

    There are other solutions of “fat models” problem described in “Growing rails applications in practice” and “Fearless Refactoring” books. However Trailblazer seems to be most complete solution to apply DDD to Rails apps [1].

    Currently the Cells 4 gem depends on beta version of HAML. Reform just depreciated ActiveModel::Validations. Dry-validation gem seems to be in alpha stage.

    The whole point of DDD is to make apps cheaper to maintain and develop. The idea of rewriting [2] all validations in a new DSL doesn’t make maintenance cheaper.

    Anyway do you recommend to use Trailblazer now or to wait for its stabilization?

    ___
    [1] There is Lotus framework but it is completely new framework. The switch cost is too big to refactor old Rails apps.
    [2] I far I know the “validate uniqueness” has no clean solution in Dry-validations.

    Like

  2. I am trying to find a way to use Trailblazer in my app in most sane way. Without rewriting all models validations in a new DSL. In a DSL which doesn’t solve all corner cases.

    And the more I think about dry-validation and other validation gems I am close to conclusion that they are all “leaky abstractions”. It just doesn’t feel good when one have to copy that amount of functionality from a AR model to a form model.

    I really like Cells [1] and Operation gems approach. It seems that it should be possibile to integrate https://github.com/makandra/active_type with Operation classes. One model sub class per Operation class. The final code organization should be similar to what can be achieved with the Reform gem approach.

    [1] It is really great that Cells doesn’t depend on Rails AP any more. So much easier to upgrade and maintain.

    Like

  3. Hi Greg, don’t panic about the AM::Validation support. We only drop actively supporting ActiveModel, which has been more than a nightmare for me. It will still work. And in a year or two, you will never want to go back to AM:V.

    Trailblazer is stable and used in many production apps. I will soon post about how to use Trailblazer with Lotus. Not sure if you’ve seen that: http://trailblazer.to/guides/sinatra/getting-started.html TRB works with all frameworks, there’s no coupling to any specific code.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s