Programming Phoenix tutorial: Ecto changesets not working

I created the changeset function as specified in the book but I keep getting the following errors. So far the solutions I have seen online involve updating Ecto, which is not my issue as I am on the latest version. I also found the book to be pretty out of date (I bought it used). Which makes me happy that the codebase is evolving and getting better, but also making it difficult to learn.

On the def changeset line:

Function changeset/1 has no local returnElixirLS Dialyzer
Function changeset/2 has no local returnElixirLS Dialyzer

And the following error on the cast(params.... line:

The call 'Elixir.Ecto.Changeset':cast
         (_user@1 :: any(),
          _params@1 :: any(),
          [<<110,97,109,101>>, <<117,115,101,114,110,97,109,101>>],
          []) breaks the contract 
          ('Elixir.Ecto.Schema':t() | t() | {data(), types()},
          #{binary() := term()} | #{atom() := term()} | 'invalid',
          'Elixir.Keyword':t()) ->
             t()ElixirLS Dialyzer

Here is the full code:

defmodule Rumbl.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :username, :string
    field :password, :string, virtual: true
    field :password_hash, :string


  def changeset(user, params \\ :empty) do
    |> cast(params, ~w(name username), [])
    |> validate_length(:username, min: 1, max: 20)

Your cast usage seems to be outdated.

 def changeset(user, params \\ %{}) do
    |> cast(params, ~w(name username)a)
    |> validate_length(:username, min: 1, max: 20)
  1. changed :empty to an empty map %{}
  2. Removed an empty list from cast
  3. Added a at the end of sigil to get atoms instead of strings.

Thank you! That worked. Can you please explain the changes further?

What is the ~w(name username)a doing exactly? I know it creates a word list of atoms but why?
And why do you want atoms instead of strings?

After reading the book + documentation – I’m still fuzzy on changesets and why they’re necessary, as opposed to creating a def update or def create function.

Sorry to bother, but I’m trying to get expert-level mastery of Elixir.

During casting, all permitted parameters whose values match the specified type information will have their key name converted to an atom and stored together with the value as a change in the :changes field of the changeset. All parameters that are not explicitly permitted are ignored.

In this case, the word list of atoms is there to indicate the permitted parameters (name and username, other parameters passed in from params will be ignored).

Because they are converted to atoms anyway during casting, it makes sense to generate the permitted parameters as atoms rather than generating it as strings and then converting. There may also be other benefits to converting to atoms, but I don’t remember.

1 Like

I have usually seen ~w(word) being used as a syntactic convention but it is just that, use what works best to you.

If the same fields are being used once and once again for various changesets in youf module you may define module constants:

@obligatory_fields `~w(name username)a`
@phone_format `[:main, :phone1 :phone2]`

And pass them along your cast or custom changesets.

It would be ok to just update or create records directly, but phoenix takes a more integral approach with Ecto changesets; for example phoenix templates take advantage of changeset errors to display them as an HTML form.

But changesets are by no means necessary or required they’re just a convenience. If you have time, this podcast episode briefly suggest an alternative to changesets.

1 Like