How to pass a changeset with put_assoc in Phoenix Form tag

How to pass a changeset in phoenix form?

I use put_assoc function as I get the user struct from the cureent user that are assign to session.

Here’s a snippet from the controller:

def new(conn, _params) do
    changeset = Blog.change_post(%Post{})
    render(conn, "new.html", changeset: changeset)
end

But I get this error
key :user not found in: %{}

Here’s my post changeset:

def changeset(post, attrs) do
   post
   |> cast(attrs, [:title, :content, :is_published])
   |> validate_required([:title, :content])
   |> put_slug()
   |> put_assoc(:user, attrs.user)
end

:wave:

It seems like you are passing an empty attrs map in Blog.change_post(%Post{}) (the contents of the change_post/1 might be helpful), and in your changeset/2 you expect it to have a :user.

To avoid the error, try replacing your current change_post/1 with

def change_post(post) do
  Ecto.Changeset.change(post)
end

then the changeset/2 won’t be called.

Since you are using Blog context in your code, I would suggest putting put_assoc in your context instead of messing your changeset/2, for example, you can define change_post/2 as bellow:

def change_post(%User{} = user, %Post{} = post) do
  Post.changeset(post, %{})
  |> put_assoc(:user, user)
end

Then in your new action:

def new(conn, _) do
  changeset = Blog.change_post(conn.assigns.current_user, %Post{})
  render(conn, "new.html", changeset: changeset)
end
1 Like

I try to follow what you suggested but error occurs on the edit action./

So here’s what I’ve done.
I change the change_post to this:

def change_post(%User{} = user, %Post{} = post) do
    Post.changeset(post, %{})
      |> put_assoc(:user, user)
end

then use it in the edit controller action:

def edit(conn, %{"id" => id}) do
  post = Blog.get_post!(id)
  changeset = Blog.change_post(conn.assigns.current_user, post)
  render(conn, "edit.html", post: post, changeset: changeset)
end

but I get this error instead

you have set that the relation :user of … has :on_replace set to :update but you are giving it a struct/ changeset to put_assoc/put_change.`

here’s my post schema:

  schema "posts" do
    field :content, :string
    field :is_published, :boolean, default: false
    field :slug, :string
    field :title, :string

    belongs_to :user, Pluma.Accounts.User, on_replace: :update

    timestamps()
  end

solution given by @idi527 works without this issues.

Will removing on_replace: :update fix the error?

It would probably start raising, since :raise is the default.

Already tried removing it but as @idi527 said it will show a message regarding :raise option.

Would your suggestion ideal to use?

Or there are other way aside from it.

Depends on your use case, if all you need is to get a changeset without any transformations (which is usually the case for new forms), then yes, change/1 is ok.

We’ll then I may use it instead.

Another question, is putting put_assoc in the changeset a bad practice?

Another question, is putting put_assoc in the changeset a bad practice?

Why would it be a bad practice? Is there something in put_assoc that you consider dangerous / inconvenient?

None, I just want to ask if it good to pass it along the changeset pipe.

Anyways thank you for the help. Very much appreciated as I’m starting to learn phoenix from simple blog application.

Hi,

Did you solve your problem?

I am not sure what is it that you are trying to achieve.

put_assoc is intended to be used when working with the parent and child associations at once.

So you should preload all the child records first and then call the put_assoc function. It will compare the preloaded records with the new records using the id as key and will determine what to do with each record. The on replace function is triggered when a id is missing in the new records. Ecto assumes you deleted the record and acts accordingly.

Please review the Ecto’s documentation: put_assoc/4

The documentation covers different use cases that might be suitable for you needs.

Hope this help.

Yes it’s already been solved, I just forgot to marked this a solved.

1 Like

First, I don’t think on_replace: :update in your posts schema is suitable here. From the documentation we know:

  • :update - updates the association, available only for has_one and belongs_to . This option will update all the fields given to the changeset including the id for the association

Do you really want to update the user when inserting your new post every time? I think you just want to associate your new post to an existing user.