I tend to use Ecto and embedded schemas for map → struct and boundary validation like this:
defmodule MyApp.MyContext do
def do_the_thing(params \\ %{}) when is_map(params) do
with {:ok, command} <- MyApp.MyContext.Commands.DoTheThing.new(params) do
# fun business logic here
end
end
end
defmodule MyApp.MyContext.Commands.DoTheThing do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :field_1, :string
field :field_2, :integer
end
def new(params) do
command = changeset(params)
case command.valid? do
true -> {:ok, apply_changes(command)}
false -> {:error, command.errors}
end
end
defp changeset(params) do
%__MODULE__{}
|> cast(params, [:field_1, :field_2])
|> validate_required([:field_1, :field_2])
end
end
It doesn’t feel so dirty to do this since Ecto 3.0 and the :ecto_sql separation.
I’ve found this to be a good way to accept large form inputs - maybe not as practical for smaller sets of parameters.






















