One form to edit multiple records

Hi!

I have these two schemas :

schema "shifts" do
    field(:starts_at, :utc_datetime)
    field(:ends_at, :utc_datetime)

    field(:total_openings, :integer, default: 1)

    # ...lots of other fields...

    has_many(:shift_applications, ShiftApplication)

    timestamps()
end

schema "shift_applications" do
    field(:status, Ecto.Enum,
      values: [:accepted, :pending, :rejected],
      default: :pending
    )

    belongs_to(:user, User)
    belongs_to(:shift, Shift)

    timestamps()
end

I want to create a form to accept the applications (changing the status of selected shift_applications to :accepted) for all the shifts in a given period, and the number of accepted applications need to be at most equal to total_openings.

Is there a way to create a form to edit all these shifts records at once and use changesets to validate the data? I’m guessing there’s something to do with embedded schemas, but I’m struggling to see how to do that.

Thanks for your help!

This does show you how to build such a form. You can probably even skip the dynamic add/delete row parts: One-to-Many LiveView Form | Benjamin Milde

It’s using heex and phoenix 1.7 components, but everything can be mapped to previous versions or non-LV contexts as well.

2 Likes

Really cool article, thanks! I already use input_for a lot, and that (kind of) wasn’t what I was looking for.

Somehow, I thought schemas could only embeds_many embedded_schemas. But that’s not the case, and so my solution is to create an embedded_schema only used for the form, like this:

 embedded_schema do
    field :starts_at, :date
    field :months, :integer, default: 2
    field :role_id, :binary
    embeds_many :shifts, Shift
end

Then, in my form, I use multiple nested input_for to edit all the Shifts in the Planning. The changeset for this schema looks like this:

def changeset(%Planning{} = planning, params \\ %{}) do
    planning
    |> cast(params, [:id, :role_id, :starts_at, :months])
    |> validate_number(:months, greater_than: 0, less_than_or_equal_to: 6)
    |> cast_embed(:shifts)
end