How to insert has_one Data into postgres?

Hello.

I’m creating a database server.

user.ex

defmodule Pals.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Pals.Accounts.Auth

  schema "users" do
    field :icon_path, :string, default: nil
    field :name, :string
    field :notification_number, :integer, default: 0
    field :point, :integer, default: 0
    has_one :auth, Auth

    timestamps()
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :icon_path, :point, :notification_number])
  end
end

auth.ex

defmodule Pals.Accounts.Auth do
  use Ecto.Schema
  import Ecto.Changeset
  alias Argon2
  alias Pals.Accounts.User

  schema "auth" do
    field :email, :string
    field :logout_time, :utc_datetime, default: nil
    field :name, :string
    field :password, :string
    #field :user_id, :id
    belongs_to :user, User

    timestamps()
  end

  @doc false
  def changeset(auth, attrs) do
    auth
    |> cast(attrs, [:name, :email, :password, :logout_time])
    |> validate_required([:name, :email, :password])
    |> unique_constraint([:email])
    |> put_password_hash()
  end

  defp put_password_hash(%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset) do
    change(changeset, password: Argon2.hash_pwd_salt(password))
  end

  defp put_password_hash(changeset), do: changeset
end

accounts.ex

defmodule Pals.Accounts do
  @moduledoc """
  The Accounts context.
  """

  import Ecto.Query, warn: false
  alias Pals.Repo

  alias Pals.Accounts.{User, Auth}
  ...
  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert!()
    |> IO.inspect()
    |> Ecto.build_assoc(:auth)
    |> IO.inspect()
    |> Auth.changeset(attrs)
    |> IO.inspect()
    |> Repo.insert()
  end
  ...
end

Those are my codes for inserting user data into database. But an error occurs when I run the create_user/1.

[debug] Processing with PalsWeb.UserController.create/2
  Parameters: %{"user" => %{"email" => "test13@gmail.com", "name" => "testname9", "password" => "[FILTERED]"}}
  Pipelines: [:api]
[debug] QUERY OK db=6.5ms queue=0.9ms idle=396.3ms
INSERT INTO "users" ("name","notification_number","point","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["testname9", 0, 0, ~N[2020-07-07 11:55:11], ~N[2020-07-07 11:55:11]]
%Pals.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  auth: #Ecto.Association.NotLoaded<association :auth is not loaded>,
  icon_path: nil,
  id: 40,
  inserted_at: ~N[2020-07-07 11:55:11],
  name: "testname9",
  notification_number: 0,
  point: 0,
  updated_at: ~N[2020-07-07 11:55:11]
}
%Pals.Accounts.Auth{
  __meta__: #Ecto.Schema.Metadata<:built, "auth">,
  email: nil,
  id: nil,
  inserted_at: nil,
  logout_time: nil,
  name: nil,
  password: nil,
  updated_at: nil,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 40
}
#Ecto.Changeset<
  action: nil,
  changes: %{
    email: "test13@gmail.com",
    name: "testname9",
    password: "$argon2id$v=19$m=131072,t=8,p=4$qlv9BAaRfWAcucv2dvn53g$NT13kqrClmaS3WbXSCIXLIUaUgF5K7UF5bWs/mHIdK0"
  },
  errors: [],
  data: #Pals.Accounts.Auth<>,
  valid?: true
>
[debug] QUERY OK db=4.4ms queue=1.0ms idle=760.4ms
INSERT INTO "auth" ("email","name","password","user_id","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id" ["test13@gmail.com", "testname9", "$argon2id$v=19$m=131072,t=8,p=4$qlv9BAaRfWAcucv2dvn53g$NT13kqrClmaS3WbXSCIXLIUaUgF5K7UF5bWs/mHIdK0", 40, ~N[2020-07-07 11:55:12], ~N[2020-07-07 11:55:12]]
[info] Sent 500 in 417ms

To associate user with auth, I used build_assoc/2 in accounts.ex. But I’m wondering that build_assoc/2 should not be used in this case… (not really though)

Help me!

The log you posted shows that the INSERT into the auth table succeeds, which is the last line of the Pals.Accounts.create_user function. It’s unclear what is causing that HTTP 500 to be returned, but I don’t believe it’s in the code included in the post.

One thing to think about: create_user’s return type is unusual - it returns {:ok, %Auth{}} (or an error tuple). Using cast_assoc to build and save the associated Auth record along with User would be the “standard” approach.

1 Like

Thank you for your hints, create_user worked properly!

def create_user(attrs \\ %{}) do
    {:ok, user} = %User{}
                  |> User.changeset(attrs)
                  |> Repo.insert()

    {:ok, _auth} = user
                  |> Ecto.build_assoc(:auth)
                  |> Auth.changeset(attrs)
                  |> Repo.insert()
      
    {:ok, user}
  end

My return value was auth, not user, so It did not work.