← All series

Series contents

Log in to track your progress

  1. 1. Ecto Overview: The Repo Episode
  2. 2. Configuring a new project with Ecto Episode
  3. 3. Creating Ecto Migrations Episode
  4. 4. Schema-less Ecto Queries Episode
  5. 5. Our first Ecto schemas Episode
  6. 6. Some useful Ecto functions Episode
  7. 7. Configuring IEx for working with Ecto Episode
  8. 8. Ecto belongs_to and has_many Episode
  9. 9. Ecto many_to_many and joining through Episode
  10. 10. Naming things in an Ecto project Episode
  11. 11. Ecto changesets: change vs cast Episode
  12. 12. Ecto changesets, validations and constraints Episode
  13. 13. Ecto build_assoc and put_assoc Episode

Ecto changesets: change vs cast

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:

  1. the struct to be created or updated (e.g. %User{})
  2. the map or keyword list of attributes to be updated
  3. 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)

Open standalone page ↗

No Comments