Can't save embedded schema field into database

Hi, there

Goal:

can someone help me figure out why the field :gender can not save into database ?

  • when I look into database, the :user_profile field it is still the default %{}. However, the user can be registered successfully and redirect to homepage.

Context:

the simple_form of user_registration_live.ex file

<.simple_form
    :let={f}
    id="registration_form"
    for={@changeset}
    phx-submit="save"
    phx-change="validate"
    phx-trigger-action={@trigger_submit}
    action={~p"/users/log_in?_action=registered"}
    method="post"
    as={:user}
  >
    <.error :if={@changeset.action == :insert}>
      Oops, something went wrong! Please check the errors below.
    </.error>

    <.input field={{f, :email}} type="email" label="Email" required />
    <.input field={{f, :password}} type="password" label="Password" required />
    <.input field={{f, :username}} type="text" label="Username" required />
    <.input
      field={{f, :user_profile}}
      name="user_profile[gender]"
      type="select"
      label="Gender"
      options={["Female", "Male"]}
      required
    />
    <:actions>
      <.button phx-disable-with="Creating account..." class="w-full">Create an account</.button>
    </:actions>
</.simple_form>

users module

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :confirmed_at, :naive_datetime
    field :username, :string

    embeds_one :user_profile, UserProfile

    timestamps()
  end
def registration_changeset(user, attrs, opts \\ []) do
    user
    |> cast(attrs, [:email, :password, :username])
    |> cast_embed(:user_profile)
    |> validate_email(opts)
    |> validate_username(opts)
    |> validate_password(opts)
end

defp validate_username(changeset, opts) do
  changeset
  |> validate_required([:username])
  |> validate_length(:username, min: 5, max: 30)
  |> validate_format(:username, ~r/^[a-zA-Z0-9_.-]*$/,
    message: "Please use letters and numbers without space(only characters allowed _ . -)"
  )
  |> maybe_validate_unique_username(opts)
end

user_profile

defmodule Cgc.Users.UserProfile do
  @moduledoc """
  The User profile schema.
  Add arbitary user attributes here.
  """
  use Cgc.Schema
  import Ecto.Changeset

  @primary_key false
  embedded_schema do
    field :gender, :string
    field :avatar_url, :string, default: "/images/default-avatar.png"
    field :dark_mode, :boolean, default: false
    field :visibility, Ecto.Enum, values: [:public, :private, :friends_only], default: :private
  end

  # set @required_fields and @optional_fields
  @required_fields ~w(gender)a
  @optional_fields ~w(avatar_url dark_mode visibility)a

  @doc false
  def changeset(%__MODULE__{} = user_profile, attr \\ %{}) do
    user_profile
    |> cast(attr, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    |> validate_inclusion(:gender, ["Female", "Male"])
  end
end

migration

defmodule Cgc.Repo.Migrations.CreateUsersAuthTables do
  use Ecto.Migration

  def change do
    execute "CREATE EXTENSION IF NOT EXISTS citext", ""

    create table(:users, primary_key: false) do
      add :id, :binary_id, primary_key: true
      add :email, :citext, null: false
      add :hashed_password, :string, null: false
      add :confirmed_at, :naive_datetime
      add :username, :citext, null: false
      add :user_profile, :map, default: %{}, null: false
      timestamps()
    end

    create unique_index(:users, [:email])
    create unique_index(:users, [:username])

    create table(:users_tokens, primary_key: false) do
      add :id, :binary_id, primary_key: true
      add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false
      add :token, :binary, null: false
      add :context, :string, null: false
      add :sent_to, :string
      timestamps(updated_at: false)
    end

    create index(:users_tokens, [:user_id])
    create unique_index(:users_tokens, [:context, :token])
  end
end

Thanks! :smiley:

Wouldnt name be name="user[user_profile][gender]" or similar? I always use <%= for ff <- inputs_for(f, :user_profile) do %>

I recently learned by digging around a little that you shoiuld only have
add :user_profile, :map when you intend to update.

1 Like

I am using LiveView 0.18.

I don’t know hot to use inputs_for form helper of <%= for ff <- inputs_for(f, :user_profile) do %> in the input component from coure_component.ex. Can you give me more concrete modification suggestion of the <.simple_form> ?

does embeds_one :user_profile, UserProfile, on_replace: : update work when intend to update ?

more info when a user submit registration

[debug] HANDLE EVENT
  View: CgcWeb.UserRegistrationLive
  Event: "save"
  Parameters: %{"_csrf_token" => "WAlUMjgSSyMPCjckcj8kIAx9Dwo8fHlXixcYzG3sMnQM9tETtL_Zt45f", 
     "user" => %{"email" => "sd@cgc.com", "password" => "[FILTERED]", "username" => "jkljkl"}, 
     "user_profile" => %{"gender" => "Female"}}

[(cgc 0.1.0) lib/cgc/users/user.ex:46: Cgc.Users.User.registration_changeset/3]
attrs #=> %{"email" => "sd@cgc.com", "password" => "jkljkl", "username" => "jkljkl"}

[(cgc 0.1.0) lib/cgc/users/user.ex:47: Cgc.Users.User.registration_changeset/3]
opts #=> []

Thank you very much. name="user[user_profile][gender]" works, but need to update field={{f, :user}} insdead of field={{f, :user_profile}}