Replace ActiveModel in form_for, form_with with dry family

Hi to all. Sorry for short question, it may seem frivolous to you. But i’ve really googled it and didn’t find any confident response or example.

Can I replace ActiveModel with dry-schema, dry-struct, dry-validation in rails constructions with form_for, form_with? Does they suggest such use? Are there any pitfalls?

Anyway I’ll try to dig deeper while waiting an answer :slight_smile:
Thank you

1 Like

I’ve made it with ActiveModel. But anyway I appreciate your answer if it ever will be given :slight_smile :slight_smile:

Sure, it’s certainly possible to do this. I’ve done it plenty of times with plain Ruby classes, so dry-struct would just be a shortcut for attribute definition and type-checking.

It’s important to understand the conceptual differences between ActiveModel and Dry-Struct. The Dry system is intended to work in a functional way, with immutable structs. The sort of mutation you typically do with activemodel objects is not supported, by design.

That means validation is an entirely separate step that takes place before instantiating the struct objects. You’ll need to stitch the two processes together in some way, which is easy with dry-monads. See also dry-rails for how you would integrate dry-schema into a Rails controller.

If you’re unfamiliar with all of this, I would recommend taking a piece at a time and solving a single problem with it. It will become clear over time how they work together, but in my experience trying to use it all at once is too hard.

The minimum-viable form object would look something like this:

module T
  include Dry::Types(default: :strict)

  Username = Strict::String.constrained(min_size: 3)
end

class Form < Dry::Struct
  extend ActiveModel::Naming
  include ActiveModel::AttributeMethods

  attribute :username, T::Username

  def persisted?
    false
  end
end
3 Likes

Oh, damn missed your post and found my question in google :slight_smile: will try your advice. Thank you )

I am bugged by the same question and wonder of the lack of documentation or blog posts in this regard, as I would expect this to be a quite common problem.

@woto, how did you solve it in the end? Was this enough or are there any learnings to share from your side?

Nope, it seems they are not interchangeable. Only hard way. With additional layer of code. For example as @alassek mentioned.

rom-rb has the notion of changesets, which are designed for the use case of validation, I wonder if something like Hanami has a reasonable alternative to form-for that integrates more natively with changesets.

I think you’re misunderstanding the use-case of changesets, I don’t think they have anything to do with validation.

They work on top of commands, and provide additional data mapping functionality and have support for associating data.

My understanding is that a changset is a data structure that assists in the update of immutable data objects (like structs) by providing a mapping between new and old values. At least in the case of Ecto, invalid updates and their reason for being invalid are included in the changeset.

The implementation of Changeset is just combining the output of Transproc with a Command, so it deals with transformation and persistence. Validation would happen before the Changeset, using dry-validation.

Perhaps you could integrate a validation contract in a Changeset, but it is not obvious to me how you would do that, because Changeset doesn’t use Result types consistently.

its seems its not an abstraction that has much meaning between data mapper implementations. Ecto.Changeset — Ecto v3.8.4

I think it is fair to say that ROM::ChangeSet is not yet a fully realized idea, and it would be nice for it to work more like Ecto.ChangeSet.

You can still achieve the same things, it just takes a little more effort right now.