What is the correct way to re-execute validation on a Ecto Schema?

Hi there!
I’m in the situation where I’m receiving a struct which is also an Ecto Schema (wich embedded_one/many other schemas). To be more precise it is a Commanded command.
Sometimes I may not be in control of how the command was built before dispatching it so I would implement a middleware that validate the command before and pass to the next layer only if everything is ok.

Passing the command to the changeset function doesn’t work since it is not a map (itself or the embedded structs).

Currently I implemented a changeset function like this:

defmodule MySchema do
   ....

   def changeset(%__MODULE__{} = schema, %__MODULE__{} = attrs) do
      changeset(schema, Map.from_struct(attrs))
   end

   def changeset(%__MODULE__{} = schema, attrs) do
      # all changeset stuff: cast, validate, ...
   end
end

All the embedded schemas implement a changeset like that. In this way it recursively transform a struct in a map before executing real changeset functionalities.

It works, but it seems like a lot of work.

Any suggestion? Do you know any better approach? maybe can I approach things differently?

Any help is really welcome.

Thanks!

What you’re running into is twofold.

Ecto.Changeset.cast doesn’t allow non map inputs. That’s by design because cast is meant to deal with external input – as in having uncontrolled values, unknown types of value represenation and such. Stuff like json or form input. When you got a struct that’s very unlikely external input like that anymore. There are other API more suited for the job like put_change/3 or change/2.

The other part is that changeset validations only validate changes, not existing values. You can force values to become changes with e.g. force_change/3 or some other APIs can also optionally force changes on a changeset.

Generally I’d however wonder why you’re validating things in the middle of handling a command and not at creation. A command should likely adhere to a contract shared between parties to be enforced at creation time.