hubert-janson
hubert-janson

Reputation: 3

Ecto: changeset drops update to parent model

I’m trying the save an update to a record (user) which has a parent (state). The user record displays fine (in show, edit) including the state. The user record updates fine for it’s own attributes, but any change to the state selected by the user is not persisted. When changing the user’s state from 1 to 2, the user params returned to the controller from the view look fine...from log ->

%{"email" => “[email protected]", "first_name" => “Abe",
"last_name" => “Sam", "password" => “foobar", "state_id" => "2"}

But when this is passed to the user changeset, it comes back with the change to the state_id dropped...from log ->

#Ecto.Changeset<action: nil,
changes: %{password: "foobar",
password_hash: “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx."},
errors: [], data: #SomeApp.User<>, valid?: true>

The update in the controller is:

def update(conn, %{"id" => id, "user" => user_params}) do
  user = Repo.get!(User, id)
  changeset =  User.changeset(user, user_params)
  case Repo.update(changeset) do

   …

The relevant changeset code is:

def changeset(model, params \\ %{}) do
    model
    |> cast(params, [:email, :first_name, :last_name, :password])
    |> assoc_constraint(:state)
    |> validate_required([:email, :first_name, :last_name, :password])
    |> unique_constraint(:email)
    |> validate_length(:password, min: 4, max: 100)
    |> put_pass_hash()
  end

The schema for the user is:

schema "users" do
    field :email, :string
    field :password,  :string, virtual: true
    field :password_hash, :string
    field :first_name, :string
    field :last_name, :string
    belongs_to :state, SomeApp.State

    timestamps()
  end

What’s am i doing wrong here? Am i missing something in my changeset that casts the user_id from the params into the changeset? I expect I'm doing something simple and stupid - i've genuinely spent hours trying to read up pick this apart and i'm stuck!

Upvotes: 0

Views: 63

Answers (1)

Dogbert
Dogbert

Reputation: 222368

Since you want to use the state_id from params, you'll need to add :state_id to the allowed list in the call to cast. Without :state_id in the allowed list, the value will be ignored, which is what is happening right now.

|> cast(params, [:email, :first_name, :last_name, :password])

should be

|> cast(params, [:email, :first_name, :last_name, :password, :state_id])

Upvotes: 1

Related Questions