Skip to content

Instantly share code, notes, and snippets.

@linusdm
Last active March 12, 2023 20:14
Show Gist options
  • Save linusdm/e995320494c2bdcb6093c8f467831704 to your computer and use it in GitHub Desktop.
Save linusdm/e995320494c2bdcb6093c8f467831704 to your computer and use it in GitHub Desktop.
Reproduction of ecto inputs_for bug

Reproduction of ecto inputs_for bug

Mix.install([
  {:ecto, "~> 3.9"},
  {:phoenix_html, "~> 3.3"},
  {:phoenix_ecto, "~> 4.4"}
])

ExUnit.start(auto_run: false)

Section

defmodule Parent do
  use Ecto.Schema
  import Ecto.Changeset
  alias Parent.Child

  schema "parent" do
    has_one(:child, Child)
  end

  def changeset(parent, attrs \\ %{}) do
    parent
    |> cast(attrs, [])
    |> cast_assoc(:child)
  end

  defmodule Child do
    use Ecto.Schema
    import Ecto.Changeset

    schema "child" do
      field(:boolean_field, :boolean)
      field(:another_field, :string)
    end

    def changeset(child, attrs) do
      cast(child, attrs, [:boolean_field, :another_field])
      # This is important!
      # we change the boolean_field back to false (in real life this is something dynamic)
      |> put_change(:boolean_field, false)
    end
  end
end
defmodule Test do
  use ExUnit.Case
  import Ecto.Changeset

  test "FormField should reflect changes" do
    parent = %Parent{child: %Parent.Child{id: 1, boolean_field: false}}

    changeset =
      Parent.changeset(parent, %{
        "child" => %{"id" => "1", "boolean_field" => "true", "another_field" => "bananas"}
      })

    # This assert proves the above setup results in the desired outcome.
    # Remember that boolean_field is changed back to `false` no matter
    # what the params were. This works as expected.
    changed_parent = apply_changes(changeset)
    assert %{boolean_field: false, another_field: "bananas"} = changed_parent.child

    # Emulate creation of a FormField (this should replicate the usage of
    # the <.form> and <.inputs_for> components more or less).
    form_parent = Phoenix.HTML.FormData.to_form(changeset, [])
    [form_child] = Phoenix.HTML.Form.inputs_for(form_parent, :child)

    # works as expected for the other field
    assert form_child[:another_field].value == "bananas"

    # BOOM! This is not what is expected. We would expect `false` here,
    # just like in the resulting struct after doing `apply_changes/1` on the changeset.
    assert form_child[:boolean_field].value == false
  end
end

ExUnit.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment