Phoenix form helper and HTTP methods

I have two partials, both get passed an action and a changeset, both start with this code:

<%= form_for @changeset, @action, fn f -> %>

and then use helper to build inputs.

Now as seen above I do not use :method option, though when rendered one of these forms is switched to put:

<input name="_method" type="hidden" value="put"> 

but the other remains post.

By (briefly) looking at the form_for as well as to_form and form_tag I didn’t find out what’s going on except that form_tag seems to already “know” the method: https://github.com/phoenixframework/phoenix_html/blob/6b95f57972b0eb103b8cb7681be81e6994fcfd21/lib/phoenix_html/tag.ex#L192, I might as well waste my time while missing something obvious.

What is happening here? Does phoenix use changeset to “guess” the appropriate HTTP verb?

I actually like this feature, it guesses correctly, just feels a little magic-y and I’d like to know exactly how it works :slight_smile:

I’m not sure but if I had to guess it figures it out from the ‘changeset’

1 Like

thanks for the feedback @hlx, that would also be my guess. I looked at the changesets closer: changeset.data.__meta__ has :loaded or :built that should affect the verb

2 Likes

Yes, that’s exactly from where it comes from: https://github.com/phoenixframework/phoenix_ecto/blob/master/lib/phoenix_ecto/html.ex#L251-L252

7 Likes

thanks, @josevalim

the to_form implementation I looked at the first time (the one in phoenix_html package) is triggered for conn forms

the right one is in the phoenix_ecto package and is triggered on changeset

it then pattern matches on changeset’s state and sets the method

https://github.com/phoenixframework/phoenix_ecto/blob/173ad3af407610f521a516c662d46452cf2ec569/lib/phoenix_ecto/html.ex#L17

https://github.com/phoenixframework/phoenix_ecto/blob/173ad3af407610f521a516c662d46452cf2ec569/lib/phoenix_ecto/html.ex#L251-L252

which is then used by form_tag

https://github.com/phoenixframework/phoenix_html/blob/6b95f57972b0eb103b8cb7681be81e6994fcfd21/lib/phoenix_html/tag.ex#L189-L192

case closed :slight_smile:

1 Like

I am using the generated boilerplate code with Phoenix 1.4 and changed my schema to an embedded schema. The result is, that the form_for function generates a POST and not a PUT request.

What do I have to adjust on the changeset so it generates a PUT request?

Don’t adjust the changeset, but add method: :put to the form_for options.

1 Like

But then the same form doesn’t work for the create page.

https://github.com/phoenixframework/phoenix/issues/2053#issuecomment-269905935

Sure, but the form won’t use the same endpoint url as well I’d imagine. The method is just another option to differenciate between new and edit pages.

The generated code uses the same form code for both new and edit pages, And as Chris said in the linked issue comment, the form_for should handle the method automatically based on the changeset. But that doesn’t work out of the box with embedded schemas.

It only works automatically for changesets of db stored schemas, because they keep track of being loaded from the db or being created at runtime. Embedded schemas don’t do that so you need to fall back to the options phoenix provides without the automation of the phoenix_ecto implementation.

Long ago I read somewhere that there just needs to be something set on the changeset for it to work. But I cannot find it anymore and it’s not in any of the official documentation.

It’s part of the metadata of schema structs, but the necessary field is not available for embedded schemas, so you cannot set it manually. I’d also argue that it’s just an implementation detail and I’d much rather use just the options of phoenix and not something only used by ecto schemas.

Edit:
See here for a more detailed view on the topic: Updating an embedded_schema & general problems understanding Ecto