Passing current_user.id into form

The name of the module is singular, the name of the schema is plural… here is an example of a User with many events.

defmodule Koko.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  alias Koko.Core.Event

  schema "users" do
    has_many(:events, Event)
    ...
  end
end

and how to test in the console

$ iex -S mix
iex(1)> u = Accounts.get_user_by_name "admin"
[debug] QUERY OK source="users" db=9.6ms decode=1.4ms queue=1.7ms idle=458.2ms
SELECT u0."id", u0."name", u0."email", u0."password_hash", u0."roles_mask", u0."sign_in_count", u0."current_sign_in_at", u0."last_sign_in_at", u0."current_sign_in_ip", u0."last_sign_in_ip", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."name" = $1) ["admin"]
%App4am.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  current_sign_in_at: nil,
  current_sign_in_ip: nil,
  email: "admin@example.local",
  events: #Ecto.Association.NotLoaded<association :events is not loaded>,
  id: "55f25ed1-6f5c-4754-b90d-13e07efb606d",
  inserted_at: ~U[2021-08-21 17:41:29Z],
  last_sign_in_at: nil,
  last_sign_in_ip: nil,
  name: "admin",
  password: nil,
  password_hash: "$argon2id$v=19$m=131072,t=8,p=4$XnOozYyAhLuuvyvLODb8mA$Q3DhosYC+ZXDYt9oZyrTfFaeFPiqPiUWf6mt8Qua54k",
  roles: nil,
  roles_mask: 1,
  sign_in_count: 0, 
  updated_at: ~U[2021-08-21 17:41:29Z]
}
iex(2)> Ecto.build_assoc u, :events          
%App4am.Core.Event{
  __meta__: #Ecto.Schema.Metadata<:built, "events">,
  expiry_date: nil,
  id: nil,
  inserted_at: nil,
  medium: nil,
  publication_date: nil,
  publication_status: :draft,
  thumbnail: nil,
  title: nil,
  updated_at: nil,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: "55f25ed1-6f5c-4754-b90d-13e07efb606d"
}

Of course those are my demo data.


So here are my modules:

defmodule EmployeeRewardApp.Points.GivenPoint do
  use Ecto.Schema
  import Ecto.Changeset

  schema "given_points" do
    field :given_points, :integer
    field :given_to_user_id, :integer
    belongs_to :user, EmployeeRewardApp.Accounts.User, type: :binary_id

    timestamps()
  end

  @doc false
  def changeset(given_point, attrs) do
    given_point
    |> cast(attrs, [:user_id, :given_points, :given_to_user_id])
    |> validate_required([:user_id, :given_points, :given_to_user_id])
  end
end
defmodule EmployeeRewardApp.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  import Comeonin.Bcrypt, only: [hashpwsalt: 1]

  schema "users" do
    field :email, :string
    field :full_name, :string
    field :password_digest, :string
    belongs_to :role, EmployeeRewardApp.Role
    has_one :pool, EmployeeRewardApp.Points.Pool, on_delete: :delete_all
    has_many :given_points, EmployeeRewardApp.Points.GivenPoint, foreign_key: :user_id, on_delete: :delete_all
    timestamps()
    # Virtual Fields
    field :password, :string, virtual: true
    field :password_confirmation, :string, virtual: true
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:full_name, :email, :password, :password_confirmation, :role_id])
    |> validate_required([:full_name, :email, :password, :password_confirmation, :role_id])
    |> cast_assoc(:pool)
    |> cast_assoc(:given_points, required: true)
    |> hash_password
  end
end

And controller:

  def add(conn, %{"given_points" => points_params}) do
    roles = Repo.all(Role)
    current_user = get_session(conn, :current_user)
    point = Ecto.build_assoc(current_user, :given_points)
    case Points.insert_points(point, 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

When I tested in the console it worked:

iex(4)> u = Accounts.get_user!(4)       
[debug] QUERY OK source="users" db=7.7ms queue=0.5ms idle=1886.5ms
SELECT u0."id", u0."email", u0."full_name", u0."password_digest", u0."role_id", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [4]
[debug] QUERY OK source="pools" db=1.4ms queue=0.4ms idle=1918.0ms
SELECT p0."id", p0."starting_points", p0."used_points", p0."user_id", p0."inserted_at", p0."updated_at", p0."user_id" FROM "pools" AS p0 WHERE (p0."user_id" = $1) [4]
%EmployeeRewardApp.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "admin@admin.com",
  full_name: "Admin",
  given_points: #Ecto.Association.NotLoaded<association :given_points is not loaded>,
  id: 4,
  inserted_at: ~N[2021-08-10 17:53:51],
  password: nil,
  password_confirmation: nil,
  password_digest: "$2b$12$/w4dboQ1h8tFGnb8V4NBZu74QKCMeO3a0gIJjELqFW9YHD9nDGqd6",
  pool: %EmployeeRewardApp.Points.Pool{
    __meta__: #Ecto.Schema.Metadata<:loaded, "pools">,
    id: 1,
    inserted_at: ~N[2021-08-11 14:29:52],
    starting_points: 50,
    updated_at: ~N[2021-08-11 14:29:52],
    used_points: 0,
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 4
  },
  role: #Ecto.Association.NotLoaded<association :role is not loaded>,
  role_id: 2,
  updated_at: ~N[2021-08-10 17:53:51]
}
iex(5)> Ecto.build_assoc u, :given_points
%EmployeeRewardApp.Points.GivenPoint{
  __meta__: #Ecto.Schema.Metadata<:built, "given_points">,
  given_points: nil,
  given_to_user_id: nil,
  id: nil,
  inserted_at: nil,
  updated_at: nil,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 4
}

With the current code, I still see the same error (no function clause matching in Ecto.build_assoc/3). Do you have any idea what can be wrong now?

Probably the problem is with cast_assoc. The way I showed the example is to create a GivenPoint, but cast_assoc is not meant to be used like this. It is meant to create GivenPoint from the a User.

You don’t have the fullstack error? It should indicate the line and the file where the error occurs.

Yes, I have the fullstack error.

[error] #PID<0.602.0> running EmployeeRewardAppWeb.Endpoint (connection #PID<0.601.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", id: 4, role_id: 2}, :given_points, %{})
        (employee_reward_app 0.1.0) lib/employee_reward_app_web/controllers/points_controller.ex:17: EmployeeRewardAppWeb.PointsController.add/2
        (employee_reward_app 0.1.0) lib/employee_reward_app_web/controllers/points_controller.ex:1: EmployeeRewardAppWeb.PointsController.action/2
        (employee_reward_app 0.1.0) lib/employee_reward_app_web/controllers/points_controller.ex:1: EmployeeRewardAppWeb.PointsController.phoenix_controller_pipeline/2
        (phoenix 1.5.10) lib/phoenix/router.ex:352: Phoenix.Router.__call__/2
        (employee_reward_app 0.1.0) lib/employee_reward_app_web/endpoint.ex:1: EmployeeRewardAppWeb.Endpoint.plug_builder_call/2
        (employee_reward_app 0.1.0) lib/plug/debugger.ex:136: EmployeeRewardAppWeb.Endpoint."call (overridable 3)"/2
        (employee_reward_app 0.1.0) lib/employee_reward_app_web/endpoint.ex:1: EmployeeRewardAppWeb.Endpoint.call/2
        (phoenix 1.5.10) lib/phoenix/endpoint/cowboy2_handler.ex:65: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy 2.9.0) /Users/kacpermuryn/Desktop/employee_reward_app.nosync/employee_reward_app/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.9.0) /Users/kacpermuryn/Desktop/employee_reward_app.nosync/employee_reward_app/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
        (cowboy 2.9.0) /Users/kacpermuryn/Desktop/employee_reward_app.nosync/employee_reward_app/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
        (stdlib 3.15.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

You should show this file, line 17

Sure.

  def add(conn, %{"given_points" => points_params}) do
    roles = Repo.all(Role)
    current_user = get_session(conn, :current_user)
    point = Ecto.build_assoc(current_user, :given_points) # THIS LINE
    case Points.insert_points(point, 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

The issue is that the current_user = get_session(conn, :current_user) gives just the list of parameters and not the user struct, but I don’t know how can I change that…

When do You set the current_user?

It should be in some plug, or in the session controller.

conn
|> put_session(:current_user, user)

I am not sure where your code come from, is that some tuto? or some legacy code?

I see You are using binary_id, which is not common, because You need to configure it properly… like foreign_key etc.

You should share your code if You can… like a github project with some mvp to test.

Here it’s just difficult to understand why your code don’t work.

I think I got it working. I made change to my session controller. Now it looks like that in the terminal:

%EmployeeRewardApp.Points.GivenPoint{
  __meta__: #Ecto.Schema.Metadata<:built, "given_points">,
  given_points: nil,
  given_to_user_id: nil,
  id: nil,
  inserted_at: nil,
  updated_at: nil,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 4
}

But I cant insert the records because of function nil.insert/1 is undefined pointing to this line:
case Points.insert_points(point, points_params) do

EDIT:
Okay, got everything working now. Thank you so much for help and patience :slight_smile:

You should show the content of the function.

How can we guess without the code calling this function?