Controller fails if form doesn't validate, "method" parameter breaks routing

Hey folks, there’s something basic I’m missing about Phoenix controllers. I have an action,, running at /documents/new:

  def new(conn, _params) do
    user = conn.assigns[:current_user]

    changeset =
        settings: Documents.change_settings(%Settings{}, %{}, user)

    render(conn, "new.html", changeset: changeset)

My form creation line in the template reads:

<%= form_for @changeset, Routes.document_path(@conn, :create), [id: "form", multipart: true], fn f -> %>

One thing I notice, though, is that if /documents/new doesn’t validate or succeed in creation, the URL is then set to /documents, even though the new document form is rendered. Further, I get a _method="put" or similar in my <form> tag. So if my first form submission errors, future form submissions swap out the method, meaning they don’t route to the create action because they try a PUT rather than a POST.

First, should I care that I’m not returning to /documents/new? I’d expect to appear back on /documents/new with the form in its errored state, but I don’t see how to pass existing form state back into the new action.

Next, how do I fix this? In my system, documents can’t be updated. They either create or don’t, then subordinate records are created/updated separately. I think there’s an action parameter that has some effect on this, and I see it in the generated scaffolds, but since I don’t want an update operation, I removed it to simplify the generated code. Should I be adding it as a hidden field in new.html.eex, merging the parameters in somehow, etc.?

Here’s my create action as well. It’s a bit convoluted because documents can be created either via URL or an uploaded file, and I don’t know how to indicate that one or the other is required:

  def create(conn, %{"document" => document_params}) do
    user = conn.assigns[:current_user]

    document_params =
      if String.trim(document_params["url"]) != "" do
        Map.put(document_params, "file", document_params["url"])

    case Documents.create_document(document_params, user) do
      {:ok, document} ->
        |> redirect(to: Routes.document_path(conn, :show, document, "html"))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)


That’s normal behaviour. You post to /documents and in case of errors, your create action renders new.html with the changeset. There’s no redirect back to /documents/new. In the success scenario, there’s a redirect.

I just quickly error’ed one of my forms and the method doesn’t change for me from post to put.