The typical changeset in Ecto is created via the cast/4
function.
cast(data, params, permitted, opts \\ [])
I've hardly found its usage intuitive.
def register_user(attrs) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password])
|> validate_email(opts)
|> validate_password(opts)
end
Why are we casting with an empty User
? What do we actually use attrs
for?
As it turns out, it's not even mildly confusing. The purpose of the cast/4
function is as the name says on the box: to cast data from one type into the other. It's necessary as we may receive data from external sources with the wrong type i.e an integer sent as a string from a form. Keep in mind though, we're still dealing with a changeset, whose sole purpose is to track changes to fields in our data.
Per the function signature, cast/4
casts the fields of params
into the expected types specified by data
, while tracking changes to the data
. The only changes that are tracked are those specified by the atoms passed via permitted
.
Take this Ecto schema
defmodule User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :age, :integer
end
end
And say we do this
iex> changeset = Ecto.Changeset.cast(%User{}, %{age: "0"}, [:age])
The parameter age
is a string. The cast/4
function will convert it into an integer. And since we've passed in an empty User
, we'll get a changeset reflecting the changes we've made i.e adding a parameter age with the value of 0.
iex> changeset
#Ecto.Changeset<
action: nil,
changes: %{age: 0}, # type is cast to an integer
errors: [],
data: #User<>,
valid?: true
>
We'll see this fail if our data cannot be converted into an integer with no changes applied.
iex> changeset = Ecto.Changeset.cast(%User{}, %{age: "a"}, [:age])
iex> changeset
#Ecto.Changeset<
action: nil,
changes: %{},
errors: [age: {"is invalid", [type: :integer, validation: :cast]}],
data: #User<>,
valid?: false
>
If we already have a populated User
type and we pass in valid data, we get our change tracking as expected and the data is cast if need be.
iex> changeset = Ecto.Changeset.cast(%User{age: 24}, %{age: "0"}, [:age])
iex> changeset
#Ecto.Changeset<
action: nil,
changes: %{age: 0},
errors: [],
data: #User<>,
valid?: true
>
More writing
For more of my writing, check out my newsletter: off the wire
Top comments (0)