Versions:
Noob here on both Elixir and Phoenix. I am really struggling to learn that framework.
I am trying to go through a user authentication tutorial with a newer version of phoenix. The tutorial suggests to implement a login user form of (the source can be found in Github):
<h1>Sign in</h1>
<%= form_for @conn, session_path(@conn, :new), [as: :session], fn f -> %>
<%= text_input f, :username, placeholder: "username" %>
<%= password_input f, :password, placeholder: "password" %>
<%= submit "Sign in" %>
<% end %>
As I struggled using the <%= form_for
syntax (not working), I am trying to use something like below which I copy-pasted from the auto-generated user_html/user_form.html.heex
(my sample uses a little different name
attribute):
<.simple_form :let={f} for={???} action={~p"/login"}>
<.input field={f[:name]} type="text" label="Username" />
<.input field={f[:password]} type="password" label="Password" />
<:actions>
<.button>Sign In</.button>
</:actions>
</.simple_form>
In the auto-generated components/core_components.ex
I can see simple_form
implemented with a .form
-tag. However, I have no idea how to use the for
(is that @conn
?) and as
(in the original code [as: :session]
) attributes to the form.
My "project" is pretty much mix phx.new project
plus the tutorial.
Does anyone have an idea how to implement the code to the new form?
I'm still confused a lot. But here is that login form working. As mentioned in the question there are some changes to the original tutorial.
The original login form
<%= form_for @conn, session_path(@conn, :new), [as: :session], fn f -> %>
<%= text_input f, :username, placeholder: "username" %>
<%= password_input f, :password, placeholder: "password" %>
<%= submit "Sign in" %>
<% end %>
changed to:
<.simple_form :let={f} for={@changeset} as={:session} method={"post"} action={~p"/login"}>
<.input field={f[:name]} type="text" label="Username" />
<.input field={f[:password]} type="password" label="Password" />
<:actions>
<.button>Sign In</.button>
</:actions>
</.simple_form>
I guess, since I pass back a changeset that contains the name
and then retry the log in, submitting makes the system look for a PUT
path, which I don't provide. Hence, I added the method={"post"}
. Is this the right way to do it? I'm not so sure.
Below is the original SessionController
from the tutorial (a little shortened)
defmodule TeacherWeb.SessionController do
#...
def new(conn, _params) do
render(conn, "new.html")
end
def create(conn, %{"session" => auth_params}) do
user = Accounts.get_by_username(auth_params["username"])
case Comeonin.Bcrypt.check_pass(user, auth_params["password"]) do
{:ok, user} ->
conn
|> put_session(:current_user_id, user.id)
|> put_flash(:info, "Signed in successfully.")
|> redirect(to: movie_path(conn, :index))
{:error, _} ->
conn
|> put_flash(:error, "There was a problem with your username/password")
|> render("new.html")
end
end
def delete(conn, _params) do
# ...
end
end
I guess the changeset
is now mandatory in the form. So, I added that:
def new(conn, _params) do
changeset = Accounts.change_user(%User{})
render(conn, :new, changeset: changeset)
end
and
def create(conn, %{"session" => auth_params}) do
user = Accounts.get_by_name(auth_params["name"])
{ res_creds, user } = if user do
{ Comeonin.Bcrypt.check_pass(user, auth_params["password"]), user }
else
{ {:error, "User does not exist"}, %User{} }
end
case res_creds do
{:ok, user} ->
conn
|> put_session(:current_user_id, user.id)
|> put_flash(:info, "Signed in successfully.")
|> redirect(to: ~p"/")
{:error, _} ->
cs = User.invalidate_password(user) # This is just to create dummy changeset, so that the form works
conn
|> put_flash(:error, "There was a problem with your username/password")
|> render("new.html", changeset: cs)
end
end
It feels like the new version works quite a bit differently which is somewhat troublesome (unless I handled it not in the right way). But for now I'm happy that that works.