maqnius

maqnius

Hey there :slight_smile:

I was trying to adapt the Contexts Article using the Form Struct and to_form.

I ended up having problems to render the input

<.input
      field={@form[:categories]}
      type="select"
      multiple={true}
      options={category_options(@article)}
    />

It errors like this

** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for type Ecto.Changeset (a struct).

because the value of the field @form[:categories] contains a Category changeset, introduced by the change_article\2 function - which is taken from the Context Article

  def change_article(%Article{} = article, attrs \\ %{}) do
    categories = list_categories_by_id(attrs["categories"])

    article
    |> Repo.preload(:categories)
    |> Article.changeset(attrs)
    |> Ecto.Changeset.put_assoc(:categories, categories)
  end

Phoenix tries to render the value in the select component but the changeset doesn’t help much here

  def input(%{type: "select"} = assigns) do
    ~H"""
    <fieldset class="fieldset mb-2">
      <label>
        <span :if={@label} class="fieldset-label mb-1">{@label}</span>
        <select
          id={@id}
          name={@name}
          class={["w-full select", @errors != [] && "select-error"]}
          multiple={@multiple}
          {@rest}
        >
          <option :if={@prompt} value="">{@prompt}</option>
          {Phoenix.HTML.Form.options_for_select(@options, @value)}
        </select>
      </label>
      <.error :for={msg <- @errors}>{msg}</.error>
    </fieldset>
    """
  end

I guess, I understand the problem, but I’m unsure how to proceed from here. I wasn’t very successfull finding examples using to_form.

What should I change, the input template, the form template, the change_article function or something else?

Thank you for help <3

Showing Posts 1 to 10

jdiago

jdiago

I think the select is supposed to be for @form[:category_ids].

The many_to_many :categories in the schema takes the categories key for itself so you can’t use it.

maqnius

maqnius OP

So would it be best to change the change_article function to populate category_ids?

def change_article(%Article{} = article, attrs \\ %{}) do
   article
   |> Article.changeset(attrs)
   |> Ecto.Changeset.put_change(:category_ids, attrs["category_ids"])
 end

That would make it harder to be re-used in the update and create functions, as I would have to parse the IDs in both of them

def update_article(%Article{} = article, attrs) do
    article
    |> change_article(attrs)
   # put_assoc from list of ids..
    |> Repo.update()
  end
jdiago

jdiago

attrs["categories"] there needs to be category_ids, too. list_categories_by_id and put_assoc are doing the work of wiring up the associated data. Read more about it here: Ecto.Changeset — Ecto v3.14.0

maqnius

maqnius OP

Mh yes, but the changeset returned by change_article ends up to build the form using to_form atm:

  defp apply_action(socket, :new, _params) do
    article = %Article{}

    socket
    |> assign(:page_title, "New Article")
    |> assign(:article, article)
    |> assign(:form, to_form(Catalog.change_article(article)))
  end

So category_ids has to be in the changeset (if I’m not missing anything).

But it also might just not work out this way, when m2m relations are involved.

Where would you inject category_ids into fhe form?

jdiago

jdiago

When you submit the form, part of the article params will be category_ids. When that goes into the create or update context functions, those will call change_article where it gets passed to list_categories_by_id and the result of that query gets shoved into the changeset by put_assoc.

maqnius

maqnius OP

Ahhh! Now I got it!

<.input
          field={@form[:category_ids]}
          type="select"
          multiple={true}
          options={category_options(@article)}
        />

@form[:category_ids] in this case merely defines the name of the submitted value not the value itself.

The actual submitted value is defined by the options={category_options(@article)} which renders to options.

I was wondering where :category_ids would be populated, as only the changeset with :categories is assigned (as a HTML.Form):

|> assign(:form, to_form(Catalog.change_article(article)))

Thank you very much for clearing things up! :folded_hands:

jdiago

jdiago

I think the confusion is coming from the relationship between the Phoenix.HTML.Form struct that comes from the to_form function and what you pass to it(in this case %Article{})?

In here Phoenix.HTML.Form — Phoenix.HTML v4.1.1 there’s this snippet:

It is possible to “access” fields which do not exist in the source data structure. A Phoenix.HTML.FormField struct will be dynamically created with some attributes such as name and id populated.

maqnius

maqnius OP

Yes :slight_smile: I noticed that it’s possible, but I didn’t understand when and how to make use of it.

The frameworks I used so far, expected to have a form data structure completely defined to render it properly and I treated the changeset as such in my mind.

jdiago

jdiago

In Phoenix, if you want to sketch out a form quickly, you can use to_form(%{}, as: "some_name"). You can then put whatever inputs to shape the params however you like.

LostKobrakai

LostKobrakai

I’d suggest reframing “article form setting categories” to “article form managing article_categories”, so moving from many to many to has many relationship. Then you can use inputs_for + select for category_id.

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
mnkhod
So i have been using ash framework for a while and i love it. However currently the issue im having with ash framework is the error handl...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews