Form element validation without database/changeset

Hi,

I could need a little help for validating input in a form, especially
showing errors in the form when the user enters wrong input.

The form elements are defined dynamically, and without connection to a DB.
So I cannot use a Changeset.
From what I have seen, :errors and :action is tightly coupled with Changesets,
so I will have to find another way.

A form is started like this:

fields = %{ "current" => 2.3, iterations => 22}
Phoenix.Component.to_form(fields)

In heex:

      <.simple_form for={@form} phx-change="validate" phx-submit="save">
        <%= for {n,v} <- @form.params do %>
        <.input name={n} id={n} label={n}  value={v}/>
        <% end %>

And the validation handler:

  def handle_event("validate", params, socket) do
    case validate(params) do
      :ok -> {:noreply, assign(socket, :form, Phoenix.Component.to_form(params))}
    
      :{error, field, cause} ->
         errorform = socket.assigns.form
         |> insert_error(field, cause)
      {:noreply, assign(socket, :form, errorform)}
    end
  end

The function

insert_error/3

is symbolic for my attempts to assign :errors and :action.

Does anybody have a working example of a form with validation and without the use of Changesets?

1 Like

I guess that nobody uses forms without the data coming from a database?

I do and I use Changesets for the purpose.

Changesets are simply a validation mechanism. There is much functionality in Ecto to make them work cleanly with databases, but a database is not a prerequisite for Changeset based validation.

I would suggest reading the following:

https://hexdocs.pm/ecto/embedded-schemas.html

and

https://hexdocs.pm/ecto/Ecto.Changeset.html#module-schemaless-changesets

Those articles will provide some of the concepts needed for standalone validation. Once those concepts are well understood I suspect there are likely Changeset based solutions that you could use if you want to use Ecto’s validation features.

This isn’t to say that it will be the most appropriate solution, but when I’m thinking about validation, the first answer I seek is, “Why not Ecto?” since it’s such a common solution and very well supported.

3 Likes

Indeed, I guess it would help if ecto would split their validation part as a separate library or at least the documentation should have a clear statement that the schemas can be used purely for validation without any DB.

On second thought this is a problem we are dealing with a huge number of libraries in the ecosystem, discoverability and understanding what and where to use.

1 Like

I actually think your first thought is more on the money. I speculate Ecto.Changeset features aren’t a separate library because of historical reasons related to its original development, but it feels like it should be.

The discoverability issues you cite and the implication of learning about what “first class libraries” exist is a real problem; but I think they’re better addressed head-on rather than making compromises on good, clean library design.

Just my two bits. And for the record, there are other issues with a split of Changesets into a separate library now (compatibility issues and such) which I expect to make the idea a non-starter… but that’s for people above my pay grade to ponder over and decide.

Indeed, however I saw a few libraries starting to sprout from the idea of how schemas and changesets work, and I think this is great because we can all acknowledge that the concept is great in itself. Slowly, if those libraries will get traction we will have an alternative without roots to databases.

We have countless problems, a lot of them are innovation, so it is really hard to know and understand what library you can find around, but I get the point that a lot of times you don’t need a library.

Isn’t it already the case with the split of ecto and ecto_sql?

You can use ecto without database.

And You can use changeset without schema.

Dynamic to which point? If You have a list of field_names and their types it should be ok, but if You have custom validation rules per field it can be more complicated

2 Likes

The stuff facilitating database vendor specific capability (e.g. Adapters) is split into ecto_sql, But Ecto proper still has a lot of general database related functionality, like Ecto.Repo and Ecto.Query.

Sure, you don’t have to use the database functionality in ecto, but because the database functionality dominates the library, it can be confusing to think about not using the majority of functionality to just get the validation piece.

Yes, even if it does not cost a lot to add ecto and only use Ecto.Changeset :slight_smile:

Absolutely dynamic.
The whole thing is a software test bench, which runs scripts to validate our hardware.
At some point, the script should stop and prompt the operator with a dialog like:

          Tseq.prompt(
            "Test 43, step 4", 
            "Please connect cable A1 to plug B2, then enter new limits", 
            [:ok, :cancel, :call_supervisor], 
            %{
              "Max current" => 10.0,
              "Max voltage" => 44.0,
              "Max iterations" => 300}),

The fields in the map define the input fields of the dialog, the type is deduced from the initial values.
From what I see, Changesets depend on structs, and those have to be defined statically.
Thanks to @sbuttgereit for the links!
I already worked with changesets, they are really powerful for statically defined data sets.

On the question whether this should be decoupled from Ecto:
My use case does not depends at all on Ecto, only on Phoenix and LiveView (for the Form).
A Phoenix-only form validation would be fine for me.

The schemaless Changesets allow for ad hoc, not statically defined data validation of both normal structs and plain maps; this is what the “schemaless” part is about. The process of validating a Changeset would need to be written to deal with validating dynamic data, which is a problem you’ll be solving anyway if the data needs to be validated with the form.

I will have dynamic forms as well which will be user defined and unknown until runtime and am still likely to stick with Changeset based form validation for those as well; Ecto.Changeset still comes with a lot of validation functions, a well defined workflow, and interoperability with things like Phoenix forms which in a dynamic context still feels better than trying to completely re-invent the wheel. For the dynamic forms, I’m not too worried about validation with Ecto, but much more worried about how well I can get a dynamic form working with the form rendering parts of Phoenix/LiveView. While I think it’s possible I’ve not worked on that problem yet and am anticipating issues with performance and things like the dynamism forcing full form updates when not really necessary, etc. I’m expecting getting it to work to be relatively straightforward, but getting it to work well to be problematic.

If you want to take advantage of Phoenix’s out-of-box handling of Changesets for things like displaying validation issues and such, you could just take advantage of the fact that a Changeset is just a struct that Phoenix can deal with. You could create your own “Changeset” struct, no Ecto, using whatever method you see fit and pass that to Phoenix; so long as your struct was well formed, Phoenix would be none the wiser: Of course, there’s a lot of code maintenance related downsides over time to doing that, but it ought to work.

Otherwise, I think you’re probably going to have to create bespoke solutions for any validation you need and for how those validations may need to render to users (customized components, etc). Definitely possible, but work. I should also state that there may be other validation libraries that cover the validation piece, the Phoenix rendering side, or both as well… but to this point I’m not anticipating a need to do that and so haven’t researched what’s out there.

1 Like

Thanks a lot for your elaborate answer!
I have to admit,when I first read the doc about schemaless changesets, I saw %User{}
and stopped right there. It didn’t trigger that there should be more if the title says schemaless…

Nevertheless, this gave me some good pointers where to go. As you say, there is a bit more work to do if one leaves the common path, but on the other hand, that saves my job :wink:

1 Like

I created a small project, where I tried to implement such a schemaless form.
I made it mainly for myself, but maybe someone else might find it useful.