First question so I hope I don’t bungle it too much. Happy to be learning Elixir and Phoenix
So, for context, I come from a Laravel background, and while I think Rails and Laravel share a lot of conventions, one thing they don’t share that Phoenix inherited from Rails(?) is rendering the new form when there is an error during resource creation.
In Laravel, the flow goes like this:
GET /posts/new
POST /posts
ERROR in submitted data
Redirect to GET /posts/new but pass along the invalid changeset to prefill the form.
I realize that this doesn’t really matter, but I’d like to learn how to implement this behavior. I toyed around with a barebones Phoenix install using phx.gen.html and got the redirect back to :new but passing the %Ecto.Changeset{} as a param was blowing up when the request was being processed by the framework.
I tried a few different function signatures to match on the changeset as a param, but no dice.
Any help is appreciated and I look forward to chatting with you all in the future!
This is the tricky part: you’ll need to transform the changeset into something that can be passed along in a GET:
URL parameters. Beware that browsers and middleboxes all have a maximum URL length, so this will not work if there are too many parameters or the values are too long - or at all, if there are file parameters
session data. This can be too shared, though, and cause “leftover data” to show up unexpectedly
Then in your new action, you’d pass those parameters instead of %{} to create a changeset.
Thanks @al2o3cr ! I could have sworn I tried that combination but it wasn’t working. Maybe I was trying to cast something to a different type…
Here’s what my final solution looks like
# posts_controller.ex
def new(conn, params) do
changeset = Blog.change_post(%Post{}, params)
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"post" => post_params}) do
case Blog.create_post(post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post created successfully.")
|> redirect(to: Routes.post_path(conn, :show, post))
{:error, %Ecto.Changeset{} = changeset} ->
redirect(conn, to: Routes.post_path(conn, :new, post_params))
end
end
This adds them as GET params which is fine for this use case. I did some digging in Laravel and they use a flash to the session to store them, so I might look at implementing that if I get bored.
Actually, crap, now that I’m writing this, I remember why I started trying to change things.
With this approach, there is no visible error message for the user. The request is redirected with no feedback. Is there a way to always validate the passed in params?