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

Hi, I am trying to do something similar as the OP. current_user.id is passed to controller. However, I face two errors.
First one:
# no function clause matching in Ecto.build_assoc/3
when the controller looks like:

  def add(conn, %{"given_points" => points_params}) do

    roles = Repo.all(Role)
    current_user = get_session(conn, :current_user)
    user_id = current_user.user_id
    changeset = Ecto.build_assoc(current_user, :given_points, points_params)
    case Points.insert_points(changeset, points_params) do
      {:ok, _points} ->
        conn
        |> put_flash(:info, "Points added successfully.")
        |> redirect(to: Routes.page_path(conn, :index))
      {:error, changeset} ->
        render(conn, "index.html", changeset: changeset, roles: roles)
    end
  end

Or when I change the changeset lines to:

  def add(conn, %{"given_points" => points_params}) do

    roles = Repo.all(Role)
    current_user = get_session(conn, :current_user)
    user_id = current_user.user_id
    changeset = GivenPoints.changeset(%GivenPoints{user_id: user_id}, points_params) #HERE
    case Points.insert_points(user_id, points_params) do #AND HERE
      {:ok, _points} ->
        conn
        |> put_flash(:info, "Points added successfully.")
        |> redirect(to: Routes.page_path(conn, :index))
      {:error, changeset} ->
        render(conn, "index.html", changeset: changeset, roles: roles)
    end
  end

Then error is:
# no function clause matching in Ecto.Changeset.cast/4
I attach my schema if it can help.

 schema "given_points" do
    field :points_given, :integer
    field :given_to_user_id, :integer
    belongs_to :user, EmployeeRewardApp.Accounts.User
    timestamps()
  end

  @doc false
  def changeset(given_points, attrs) do
    given_points
    |> cast(attrs, [:user_id, :points_given, :given_to_user_id])
    |> validate_required([:user_id, :points_given, :given_to_user_id])
  end

I was trying to solve it by myself but I ran into errors that I totally didn’t understand.

This code is already wrong, because as I mentionned in my last post, it’s an item.

The esiest way is to test in the console what is returned by

Ecto.build_assoc(current_user, :given_points)

It’s not important if it is not the current_user a Phoenix session, it just need to be a User…

This should be easy to debug… it means You did not pass correct parameters to the function.
I simply log the input, or just IO.inspect the parameters passed to the function to know what I am sending to the function.

Function is called with 3 arguments:

    %{email: "admin@admin.com", role_id: 2, user_id: 4}
    :given_points
    %{}

What does this returns?

It should return a given_point, if not… your user does not have has_many :given_points.

I have has_many :given_points in my user schema.

This is what I see in the terminal:

[debug] Processing with EmployeeRewardAppWeb.PointsController.add/2
  Parameters: %{"_csrf_token" => "Vi0PAD05MUMaOkQwQz9XATgnPgM8Xhkl2zkOPTz2Ihpbpf-Rtuk3ljmi", "given_points" => %{"given_to_user_id" => "54", "points_given" => "1"}}
  Pipelines: [:browser]
[debug] QUERY OK source="roles" db=0.3ms idle=1030.0ms
SELECT r0."id", r0."admin", r0."name", r0."inserted_at", r0."updated_at" FROM "roles" AS r0 []
[info] Sent 500 in 44ms
[error] #PID<0.5048.0> running EmployeeRewardAppWeb.Endpoint (connection #PID<0.5047.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: POST /points
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Ecto.build_assoc/3
        (ecto 3.6.2) lib/ecto.ex:466: Ecto.build_assoc(%{email: "admin@admin.com", role_id: 2, user_id: 4}, :given_points, %{})
        (employee_reward_app 0.1.0) lib/employee_reward_app_web/controllers/points_controller.ex:17: EmployeeRewardAppWeb.PointsController.add/2
... rest of the error msg

Is this, what you wanted me to show?

Are You using plural for your schema?

What is the name? GivenPoint? GivenPoints?

The name is GivenPoints. Out of curiosity, if I change it to GivenPoint and all “given_points” to “given_point” in the schema, will everything work fine? Right now, I have only schema for given_points and using it in the controller function that i pasted here. Just noticed, that all other schemas have singular names…

It messes with associations when You use plural for schemas…

I deleted the schema and table and created it again with singular name. However, everything looks the same, I mean the error and parameters.