Passing current_user.id into form

Hey,

I’m struggling with passing current_user.id to form (create new item).
I was trying to replicate this solution:

without luck…

This is my code:

AuctionWeb.ItemController


  def create(conn, %{"item" => item_params}) do
    current_user = conn.assigns.current_user
    changeset = Auction.Item.changeset(%{user_id: current_user.id}, item_params)
    case Auction.insert_item(changeset) do
    #case Auction.insert_item(item_params) do
      {:ok, item} -> redirect(conn, to: Routes.item_path(conn, :show, item))
      {:error, item} -> render(conn, "new.html", item: item)
    end
  end

Auction.Item

  def changeset(item, params \\ %{}) do
    item
    |> cast(params, [:title, :description, :ends_at, :user_id])
    |> validate_required(:title)
    |> validate_length(:title, min: 3)
    |> validate_length(:description, max: 200)
    |> validate_change(:ends_at, &validate/2)
  end

Auction.insert_item

  def insert_item(attrs) do
    %Item{}
    |> Item.changeset(attrs)
    |> @repo.insert()
  end

error

no function clause matching in Ecto.Changeset.cast/4

Called with 4 arguments

1. %{user_id: 1}
2. %{"description" => "3333", "ends_at" => %{"day" => "1", "hour" => "0", "minute" => "0", "month" => "1", "year" => "2021"}, "title" => "333"}
3. [:title, :description, :ends_at, :user_id]
4. []

Cast must be called with schema, changeset or {data (), types ()}.

changeset = Auction.Item.changeset(%Item{user_id: current_user.id}, item_params)

1 Like

It’s not that recommended to cast id (user_id) like this. It can be done… but using association, and build_assoc is more appropriate.

https://hexdocs.pm/ecto/Ecto.html#build_assoc/3

1 Like

I tried it before, but I get this error:

expected params to be a :map, got: `#Ecto.Changeset<action: nil, changes: %{description: "struct", ends_at: ~U[2025-01-01 00:00:00Z], title: "struct"}, errors: [], data: #Auction.Item<>, valid?: true>`

schema users

  schema "users" do
    field :username, :string
    field :email_address, :string
    field :password, :string, virtual: true
    field :hashed_password, :string
    has_many :bids, Auction.Bid
    has_many :items, Auction.Item
    timestamps()
  end

schema items


  schema "items" do
    field :title, :string
    field :description, :string
    field :ends_at, :utc_datetime
    belongs_to :user, Auction.User
    has_many :bids, Auction.Bid

  timestamps()
  end

ItemController

  def create(conn, %{"item" => item_params}) do
    current_user = conn.assigns.current_user
    changeset = Ecto.build_assoc(current_user, :items, item_params)
    case Auction.insert_item(changeset) do
    #case Auction.insert_item(item_params) do
      {:ok, item} -> redirect(conn, to: Routes.item_path(conn, :show, item))
      {:error, item} -> render(conn, "new.html", item: item)
    end
  end

error

expected params to be a :map, got: `%Auction.Item{__meta__: #Ecto.Schema.Metadata<:built, "items">, bids: #Ecto.Association.NotLoaded<association :bids is not loaded>, description: nil, ends_at: nil, id: nil, inserted_at: nil, title: nil, updated_at: nil, user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: 1}`

I’ve added a helper

  def inject_user_id(input, user_id) do
    Map.put(input, "user_id", user_id)
  end

My code

  def create(conn, %{"item" => item_params}) do
    user = conn.assigns.current_user
    changeset = Auction.Item.changeset(item_params |> GlobalHelpers.inject_user_id(user.id))
    case Auction.insert_item(changeset) do
      {:ok, item} -> redirect(conn, to: Routes.item_path(conn, :show, item))
      {:error, item} -> render(conn, "new.html", item: item)
    end
  end

error

How to resolve it?

I’m following “Phoenix in Action”, assigning user to item is one of the tasks.
Project available at GitHub:

Here is problem with insert_item function where attrs is changeset, but second parameter must be %{required(binary()) => term()} | %{required(atom()) => term()} | :invalid

I’m sorry, I don’t get this.

For what I understand is that this function is wrong:

  def insert_item(attrs) do
    %Item{}
    |> Item.changeset(attrs)
    |> @repo.insert()
  end
  def changeset(item, params \\ %{}) do
    item
    |> cast(params, [:title, :description, :ends_at, :user_id])
    |> validate_required(:title)
    |> validate_length(:title, min: 3)
    |> validate_length(:description, max: 200)
    |> validate_change(:ends_at, &validate/2)
  end

And the code should look like this?

  def insert_item(attrs, %{required(binary()) => term()} | %{required(atom()) => term()} | :invalid) do
    %Item{}
    |> Item.changeset(attrs)
    |> @repo.insert()
  end

It gives me error:

cannot find or invoke local required/1 inside match. Only macros can be invoked in a match and they must be defined before their invocation. Called as: required(binary())

I’m new to programming and I thought this small change would be easy to do.

Try to add this…

def insert_item(item, attrs) do
  item
  |> Item.changeset(attrs)
  |> @repo.insert()
end

This way You can pass the initial item, which will be build using build_assoc(user, :items).

It also has the advantage not to require casting user_id, because it’s already in the initial item.

1 Like

Thank You! It’s working now! :smiley:

  def create(conn, %{"item" => item_params}) do
    current_user = conn.assigns.current_user
    changeset = Ecto.build_assoc(current_user, :items)
    case Auction.insert_item(changeset, item_params) do
    #case Auction.insert_item(item_params) do
      {:ok, item} -> redirect(conn, to: Routes.item_path(conn, :show, item))
      {:error, item} -> render(conn, "new.html", item: item)
    end
  end
def insert_item(item, attrs) do
  item
  |> Item.changeset(attrs)
  |> @repo.insert()
end

Nice it’s working… but You should probably rename changeset, because it’s the initial item, and not a changeset :slight_smile:

1 Like