0
votes

I have a nested form where the only 1/5 supplied options is required. However if a user submits the form with only 1 of the nested forms filled out, Ecto returns the changeset as invalid.

I added a scrub_params plug to my controller, but it appears this does not remove empty nested forms.

plug(:scrub_params, "inventories" when action in [:create])

Is there a way in Phoenix to remove empty forms?


Conn Params params["inventories"]:

%{"payment_invoice_type" => "mail",
  "delivery_methods" =>
  %{"0" => %{"cost" => "10.00", "description" => "Del Option 1"},
    "1" => %{"cost" => "20.00", "description" => "Del Option 2"},
    "2" => %{"cost" => "30.00", "description" => "Del Option 3"},
    "3" => %{"cost" => nil, "description" => nil}, # should be discarded
    "4" => %{"cost" => nil, "description" => nil}}, # should be discarded
  "payment_method" => "cash",
}

Changeset results:

#Ecto.Changeset<action: nil,
 changes: %{payment_invoice_type: "mail,
   delivery_methods: [#Ecto.Changeset<action: :insert,
     changes: %{cost: #Decimal<10.00>, description: "Del Option 1"}, errors: [],
     data: #Book.Store.ShippingMethod<>, valid?: true>,
    #Ecto.Changeset<action: :insert,
     changes: %{cost: #Decimal<20.00>, description: "Del Option 2"}, errors: [],
     data: #Book.Store.ShippingMethod<>, valid?: true>,
    #Ecto.Changeset<action: :insert,
     changes: %{cost: #Decimal<30.00>, description: "Del Option 3"}, errors: [],
     data: #Book.Store.ShippingMethod<>, valid?: true>,
    #Ecto.Changeset<action: :insert, changes: %{},
     errors: [description: {"can't be blank", [validation: :required]},
      cost: {"can't be blank", [validation: :required]}],
     data: #Book.Store.ShippingMethod<>, valid?: false>,
    #Ecto.Changeset<action: :insert, changes: %{},
     errors: [description: {"can't be blank", [validation: :required]},
      cost: {"can't be blank", [validation: :required]}],
     data: #Book.Store.ShippingMethod<>, valid?: false>],
   payment_method: "cash"},
 errors: [],
 data: #Book.Store.Inventory<>, valid?: false>

Parent Schema:

schema "inventories" do
  field :payment_invoice_type, :string
  field :payment_method, :string
  embeds_many :shipping_options, Book.Store.ShippingOptions
  timestamps()
end

Parent Changeset:

def changeset(%Inventory{} = inventory, attrs) do
  inventory
  |> cast(attrs, [:payment_invoice_type, :payment_method])
  |> validate_required([:payment_invoice_type, :payment_method])
  |> cast_embed(:shipping_methods, required: true)
end

Embedded Schema:

  @primary_key false
  embedded_schema do
    field :description, :string
    field :cost, :decimal
  end

  def changeset(%DeliveryOption{} = delivery_option, attrs) do
    delivery_option
    |> cast(attrs, [:description, :cost])
    |> validate_required([:description, :cost])
    |> validate_length(:description, min: 5, max: 75)
  end
1
scrub_params is actually correctly working here since those fields are nil, not "".Dogbert
You are correct. I incorrectly thought that scrub_params removes empty params.user7391757

1 Answers

0
votes

I don't think there's a way to remove such entries automatically in Ecto. You can manually remove the entries fairly easily like this:

inventories = Map.update!(inventories, "delivery_methods", fn map ->
  :maps.filter(fn _key, value -> !match?(%{"cost" => nil, "description" => nil}, value), map)
end)

This will remove all delivery_methods values which have both cost and description set to nil.

Alternatively, the following will remove any entries which have all their values set to nil:

inventories = Map.update!(inventories, "delivery_methods", fn map ->
  map |> Map.values |> Enum.all?(&is_nil/1)
end)