Hello. I’m following the Phoenix 1.6.0-rc.0 guide, and I’ve run into an issue in the Contexts section.
The problem I’m seeing is that, following the guide, the multiple select widget on the Edit Product page does not reflect what categories are currently selected. I’ve traced the issue as far as this code from the guide’s product_view.ex
:
defmodule HelloWeb.ProductView do
use HelloWeb, :view
def category_select(f, changeset) do
existing_ids = changeset |> Ecto.Changeset.get_field(:categories) |> Enum.map(& &1.id)
category_opts =
for cat <- Hello.Catalog.list_categories(),
do: [key: cat.title, value: cat.id, selected: cat.id in existing_ids]
multiple_select(f, :category_ids, category_opts)
end
end
With some IO.inspect
I can see that the initial changeset
variable correctly contains however many categories are selected.
...
def category_select(f, changeset) do
IO.inspect(changeset)
...
1 category selected:
#Ecto.Changeset<
action: nil,
changes: %{
categories: [
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #Hello.Catalog.Category<>, valid?: true>
]
},
errors: [],
data: #Hello.Catalog.Product<>,
valid?: true
>
vs 5 categories selected:
#Ecto.Changeset<
action: nil,
changes: %{
categories: [
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #Hello.Catalog.Category<>, valid?: true>,
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #Hello.Catalog.Category<>, valid?: true>,
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #Hello.Catalog.Category<>, valid?: true>,
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #Hello.Catalog.Category<>, valid?: true>,
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #Hello.Catalog.Category<>, valid?: true>
]
},
errors: [],
data: #Hello.Catalog.Product<>,
valid?: true
>
But after Ecto.Changeset.get_field(:categories)
only an empty list comes out.
...
existing_ids =
changeset
|> Ecto.Changeset.get_field(:categories)
|> IO.inspect(label: "after get_field")
...
after get_field: []
Which means cat.id
is never in
existing_ids
and so selected
is always false
.
...
category_opts =
for cat <- Hello.Catalog.list_categories(),
do: [key: cat.title, value: cat.id, selected: cat.id in existing_ids]
IO.inspect(category_opts)
...
[
[key: "Fashion", value: 1, selected: false],
[key: "Power Tools", value: 2, selected: false],
[key: "Gardening", value: 3, selected: false],
[key: "Books", value: 4, selected: false],
[key: "Education", value: 5, selected: false]
]
I looked at the documentation for get_field
, and my understanding is that it differs from get_change
in that it falls back to the existing data if no changes are present. That sounds like what I want to happen. But if I I swap it out for get_change
and try to navigate the output, I end up with:
...
- |> Ecto.Changeset.get_field(:categories)
+ |> Ecto.Changeset.get_change(:categories)
...
- |> Enum.map(& &1.id)
+ |> Enum.map(& &1.data.id)
...
And now the selected status is showing up properly in the multiple select, as far as I can tell. So, great! But, is this… correct?
Is there something very simple I’m missing as a newbie? I do feel like I understand much more than I did before starting to troubleshoot this, but right now I’m not certain if I’ve missed something somewhere else and come up with a hacky workaround or if this is some kind of required change for 1.6 that hasn’t been reflected in the guide yet.
Thank you for reading. I’ve endeavored to be thorough.