Dynamic nested forms

If you don’t want to use Formex here’s an example of nested form. Assuming User has_one Profile:

defmodule Database.Schema.User do
  use Ecto.Schema
  import Ecto.Changeset

  alias Database.Schema.{
    User,
    Profile,
    Post
  }

  schema "users" do
    field :email, :string
    field :password, :string
    has_one :profile, Profile
    has_many :posts, Post

   timestamps()
  end

  # @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :password])
    |> validate_required([:email, :password])
    |> validate_format(:email, ~r/@/)
    |> unique_constraint(:email)
  end

  def changeset_assoc(%User{} = user, attrs) do
    user
    |> changeset(attrs)
    |> cast_assoc(:profile, required: true)
  end
end
defmodule Database.Schema.Profile do
  use Ecto.Schema
  import Ecto.Changeset

  alias Database.Schema.User

  schema "profiles" do
    field :address, :string
    field :name, :string
    field :phone, :string
    belongs_to :user, User

    timestamps()
  end

  @doc false
  def changeset(profile, attrs) do
    profile
    |> cast(attrs, [:name, :phone, :address])
    |> validate_required([:name, :phone, :address])
  end
end

Here’s the user repository for creating changesets:

defmodule Database.Repo.User do
  import Ecto.Query, warn: false

  alias Database.Repo
  alias Database.Schema.User

  def create_assoc(attrs \\ %{}) do
    %User{}
    |> User.changeset_assoc(attrs)
    |> Repo.insert()
  end

  def change_assoc(%User{} = user) do
    User.changeset_assoc(user, %{})
  end
end

Inside your controller you can do something like this:

defmodule Frontend.User.AuthController do
  use Frontend, :controller

  def register(conn, params) do
    case params do
      %{"user" => user_params}
        -> case Database.Repo.User.create_assoc(user_params) do
            {:ok, user} ->
              conn
              |> put_session(:user_id, user.id)
              |> put_flash(:info, "User created successfully.")
              |> redirect(to: home_path(conn, :index))
            {:error, %Ecto.Changeset{} = changeset} ->
              render(conn, "register.html", changeset: changeset)
          end
      _ -> render(conn, "register.html", changeset: Database.Repo.User.change_assoc(%Database.Schema.User{}))
    end
  end
end

and inside your template you can use the nested form:

<section class="service-layout1 bg-accent s-space-custom2">
  <div class="container">
    <div class="section-title-dark">
      <h1>Register</h1>
    </div>
    <div class="row">
      <div class="col-lg-4 col-md-4 col-sm-6 col-xs-6 col-mb-12 item-mb">
        <%= form_for @changeset, auth_path(@conn, :register), fn f -> %>
          <%= inputs_for f, :profile, fn p -> %>
            <div class="form-group">
              <%= label p, :name, class: "control-label" %>
              <%= text_input p, :name, class: "form-control" %>
              <%= error_tag p, :name %>
            </div>
          <% end %>

          <div class="form-group">
            <%= label f, :email, class: "control-label" %>
            <%= text_input f, :email, class: "form-control" %>
            <%= error_tag f, :email %>
          </div>

          <div class="form-group">
            <%= label f, :password, class: "control-label" %>
            <%= text_input f, :password, class: "form-control" %>
            <%= error_tag f, :password %>
          </div>

          <%= inputs_for f, :profile, fn p -> %>
            <div class="form-group">
              <%= label p, :phone, class: "control-label" %>
              <%= text_input p, :phone, class: "form-control" %>
              <%= error_tag p, :phone %>
            </div>

            <div class="form-group">
              <%= label p, :address, class: "control-label" %>
              <%= text_input p, :address, class: "form-control" %>
              <%= error_tag p, :address %>
            </div>
          <% end %>

          <div class="form-group">
            <%= submit "Submit", class: "btn btn-primary" %>
          </div>
        <% end %>
      </div>
    </div>
  </div>
</section>

Hope will help :slight_smile:

2 Likes