Problem retrieving product's selected status in 1.6.0-rc0 Contexts guide

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.

2 Likes

I ran into the same problem and found out that your proposed fix should be changed a bit to support being used from the “new” view (adding a default value) :

existing_ids = changeset |> Ecto.Changeset.get_change(:categories, []) |> Enum.map(& &1.data.id)`

I also asked on the slack forums but didn’t yet get answers.

2 Likes

Seems to work in all cases I’ve tried with your fallback in place. Nice!

Hope they can get the guide sorted out.