There are two ways of making an Ecto changeset. You can use Ecto.Changeset.change/2
or you can use Ecto.Changeset.cast/3
. (The final argument of each is optional).
When to use change vs cast
For internal data, use change
. It's very convenient to use, but doesn't do any filtering or validations, so make sure the data is coming from a source you trust.
For external data use cast
. It filters attributes and acts as a type cast. For example, data coming into a web server via a form will all be in string form, but cast will transform each attribute into the type specified by the struct's Ecto schema. E.g., a user's "age" might come in as the string "27"
and be cast as the integer 27
.
Using change
Change takes two attributes. First is the struct to be changed. The second (optional) struct is a map of the changes to be made. The return value is either a changeset or an error.
iex> manning = Repo.get!(Link, 4)
%Linkly.Link{
__meta__: #Ecto.Schema.Metadata<:loaded, "links">,
bookmarks: #Ecto.Association.NotLoaded<association :bookmarks is not loaded>,
id: 4,
inserted_at: ~N[2021-02-10 18:23:02],
taggings: #Ecto.Association.NotLoaded<association :taggings is not loaded>,
tags: #Ecto.Association.NotLoaded<association :tags is not loaded>,
updated_at: ~N[2021-02-10 18:23:02],
url: "https://manning.com",
users: #Ecto.Association.NotLoaded<association :users is not loaded>
}
iex> cs = change(hn, %{url: "nostarch.com"})
#Ecto.Changeset<
action: nil,
changes: %{url: "nostarch.com"},
errors: [],
data: #Linkly.Link<>,
valid?: true
>
To commit the change, just pass it as a parameter to Repo.insert/1
or in this case, for updating an existing entry, pass it into Repo.update/1
.
Using cast
The syntax for cast is similar to change, but it takes a third argument of allowed changes. So call cast with:
- the struct to be created or updated (e.g. %User{})
- the map or keyword list of attributes to be updated
- the list of allowed attributes
For example, cast(user, update_map, [:username])
, will create a changeset to update user
, and that changeset will include only the update for username (if any) supplied in the update_map. The email change will be discarded since :email
wasn't included in the allowed attributes list. This way we can filter which attributes can be updated from a given source.
iex> bob = Repo.get!(User, 2)
%Linkly.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
about: nil,
birth_date: nil,
email: "robert@example.com",
id: 2,
inserted_at: ~N[2021-02-10 18:16:25],
updated_at: ~N[2021-02-10 18:16:25],
username: "bob"
}
iex> cs = cast(bob, %{username: "rob", email: "robert@example.com"}, [:username])
#Ecto.Changeset<
action: nil,
changes: %{username: "rob"},
errors: [],
data: #Linkly.User<>,
valid?: true
>
(associations not shown for brevity)
No Comments