nil given for :username, comparison with nil is forbidden as it always evaluates to false. Pass a full query expression and use is_nil/1 instead.
def create(conn, %{"user" => user_params}) do user = Repo.get_by(User, username: user_params["username"]) user |> sign_in(user_params["password"], conn) end
def create(conn, %{"user" => %{"username" => username, "password" => password}}) when not is_nil(username) and not is_nil(password) do user = Repo.get_by(User, username: username) sign_in(user, password, conn) end def create(conn, _) do failed_login(conn) end
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
defp failed_login(conn) do dummy_checkpw() conn |> put_session(:current_user, nil) |> put_flash(:error, "Invalid username/password combination!") |> redirect(to: page_path(conn, :index)) |> halt() end
defp sign_in(user, _password, conn) when is_nil(user) do failed_login(conn) end defp sign_in(user, password, conn) do if checkpw(password, user.password_digest) do conn |> put_session(:current_user, %{id: user.id, username: user.username}) |> put_flash(:info, "Sign in successful!") |> redirect(to: page_path(conn, :index)) else failed_login(conn) end end
$ mix ecto.gen.migration add_user_id_to_posts
Compiling 1 file (.ex) * creating priv/repo/migrations * creating priv/repo/migrations/20160720211140_add_user_id_to_posts.exs
def change do alter table(:posts) do add :user_id, references(:users) end create index(:posts, [:user_id]) end
mix ecto.migrate
and start editing our models. belongs_to :user, Pxblog.User
has_many :posts, Pxblog.Post
resources "/users", UserController do resources "/posts", PostController end
mix phoenix.routes
right now, we get an error. This is the norm! Since we changed the structure of the paths, the post_path helper, the new version of which is called user_post_path and refers to the embedded resource, is lost. Nested helpers allow us to gain access to the paths represented by resources that require the availability of another resource (for example, posts require a user). post_path(conn, :show, post)
post_path(conn, :show, 1)
user_post_path(conn, :show, user, post)
plug :assign_user
defp assign_user(conn, _opts) do case conn.params do %{"user_id" => user_id} -> user = Repo.get(Pxblog.User, user_id) assign(conn, :user, user) _ -> conn end end
def create(conn, %{"post" => post_params}) do changeset = Post.changeset(%Post{}, post_params) case Repo.insert(changeset) do {:ok, _post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end def update(conn, %{"id" => id, "post" => post_params}) do post = Repo.get!(Post, id) changeset = Post.changeset(post, post_params) case Repo.update(changeset) do {:ok, post} -> conn |> put_flash(:info, "Post updated successfully.") |> redirect(to: user_post_path(conn, :show, conn.assigns[:user], post)) {:error, changeset} -> render(conn, "edit.html", post: post, changeset: changeset) end end def delete(conn, %{"id" => id}) do post = Repo.get!(Post, id) # delete! ( ), # ( ). Repo.delete!(post) conn |> put_flash(:info, "Post deleted successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) end
<h2>Listing posts</h2> <table class="table"> <thead> <tr> <th>Title</th> <th>Body</th> <th></th> </tr> </thead> <tbody> <%= for post <- @posts do %> <tr> <td><%= post.title %></td> <td><%= post.body %></td> <td class="text-right"> <%= link "Show", to: user_post_path(@conn, :show, @user, post), class: "btn btn-default btn-xs" %> <%= link "Edit", to: user_post_path(@conn, :edit, @user, post), class: "btn btn-default btn-xs" %> <%= link "Delete", to: user_post_path(@conn, :delete, @user, post), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %> </td> </tr> <% end %> </tbody> </table> <%= link "New post", to: user_post_path(@conn, :new, @user) %>
<h2>Show post</h2> <ul> <li> <strong>Title:</strong> <%= @post.title %> </li> <li> <strong>Body:</strong> <%= @post.body %> </li> </ul> <%= link "Edit", to: user_post_path(@conn, :edit, @user, @post) %> <%= link "Back", to: user_post_path(@conn, :index, @user) %>
<h2>New post</h2> <%= render "form.html", changeset: @changeset, action: user_post_path(@conn, :create, @user) %> <%= link "Back", to: user_post_path(@conn, :index, @user) %>
<h2>Edit post</h2> <%= render "form.html", changeset: @changeset, action: user_post_path(@conn, :update, @user, @post) %> <%= link "Back", to: user_post_path(@conn, :index, @user) %>
mix phoenix.routes
, we should see the output of the paths and the successful compilation! Compiling 14 files (.ex) page_path GET / Pxblog.PageController :index user_path GET /users Pxblog.UserController :index user_path GET /users/:id/edit Pxblog.UserController :edit user_path GET /users/new Pxblog.UserController :new user_path GET /users/:id Pxblog.UserController :show user_path POST /users Pxblog.UserController :create user_path PATCH /users/:id Pxblog.UserController :update PUT /users/:id Pxblog.UserController :update user_path DELETE /users/:id Pxblog.UserController :delete user_post_path GET /users/:user_id/posts Pxblog.PostController :index user_post_path GET /users/:user_id/posts/:id/edit Pxblog.PostController :edit user_post_path GET /users/:user_id/posts/new Pxblog.PostController :new user_post_path GET /users/:user_id/posts/:id Pxblog.PostController :show user_post_path POST /users/:user_id/posts Pxblog.PostController :create user_post_path PATCH /users/:user_id/posts/:id Pxblog.PostController :update PUT /users/:user_id/posts/:id Pxblog.PostController :update user_post_path DELETE /users/:user_id/posts/:id Pxblog.PostController :delete session_path GET /sessions/new Pxblog.SessionController :new session_path POST /sessions Pxblog.SessionController :create session_path DELETE /sessions/:id Pxblog.SessionController :delete
iex -S mix
start by launching the interactive console with the iex -S mix
command to learn a little about how to select user posts. But before that we need to set up a list of standard imports / aliases that will be loaded each time the iex console is loaded inside our project. Create a new .iex.exs file in the project root (note the point at the beginning of the file name) and fill it with the following content: import Ecto.Query alias Pxblog.User alias Pxblog.Post alias Pxblog.Repo import Ecto
iex(1)> import Ecto.Query nil iex(2)> alias Pxblog.User nil iex(3)> alias Pxblog.Post nil iex(4)> alias Pxblog.Repo nil iex(5)> import Ecto nil
iex(8)> user = Repo.get(User, 1) [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1] OK query=8.2ms %Pxblog.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: "test", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, password: nil, password_confirmation: nil, password_digest: "$2b$12$pV/XBBCRl0RQhadQd9Y4mevOy5y0j4bCC/LjGgx7VJMosRdwme22a", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, updated_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, username: "test"} iex(10)> Repo.all(assoc(user, :posts)) [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 WHERE (p0."user_id" IN ($1)) [1] OK query=3.5ms []
iex(14)> Repo.all from p in Post, ...(14)> join: u in assoc(p, :user), ...(14)> select: p [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 INNER JOIN "users" AS u1 ON u1."id" = p0."user_id" [] OK query=0.9ms
iex(18)> Repo.all(from u in User, preload: [:posts]) [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 [] OK query=0.9ms [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 WHERE (p0."user_id" IN ($1)) ORDER BY p0."user_id" [1] OK query=0.8ms iex(20)> Repo.all(from p in Post, preload: [:user]) [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 [] OK query=0.8ms []
iex(1)> user = Repo.get(User, 1) iex(2)> post = build_assoc(user, :posts, %{title: "Test Title", body: "Test Body"}) iex(3)> Repo.insert(post) iex(4)> posts = Repo.all(from p in Post, preload: [:user])
iex(4)> posts = Repo.all(from p in Post, preload: [:user]) [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 [] OK query=0.7ms [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" IN ($1)) [1] OK query=0.7ms [%Pxblog.Post{__meta__: #Ecto.Schema.Metadata<:loaded>, body: "Test Body", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, title: "Test Title", updated_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, user: %Pxblog.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: "test", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, password: nil, password_confirmation: nil, password_digest: "$2b$12$pV/XBBCRl0RQhadQd9Y4mevOy5y0j4bCC/LjGgx7VJMosRdwme22a", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, updated_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, username: "test"}, user_id: 1}]
iex(5)> post = List.first posts %Pxblog.Post{__meta__: #Ecto.Schema.Metadata<:loaded>, body: "Test Body", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, title: "Test Title", updated_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, user: %Pxblog.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: "test", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, password: nil, password_confirmation: nil, password_digest: "$2b$12$pV/XBBCRl0RQhadQd9Y4mevOy5y0j4bCC/LjGgx7VJMosRdwme22a", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, updated_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, username: "test"}, user_id: 1} iex(6)> post.title "Test Title" iex(7)> post.user.username "test"
def index(conn, _params) do posts = Repo.all(assoc(conn.assigns[:user], :posts)) render(conn, "index.html", posts: posts) end
defp assign_user(conn, _opts) do case conn.params do %{"user_id" => user_id} -> case Repo.get(Pxblog.User, user_id) do nil -> invalid_user(conn) user -> assign(conn, :user, user) end _ -> invalid_user(conn) end end defp invalid_user(conn) do conn |> put_flash(:error, "Invalid user!") |> redirect(to: page_path(conn, :index)) |> halt end
def new(conn, _params) do changeset = conn.assigns[:user] |> build_assoc(:posts) |> Post.changeset() render(conn, "new.html", changeset: changeset) end
def create(conn, %{"post" => post_params}) do changeset = conn.assigns[:user] |> build_assoc(:posts) |> Post.changeset(post_params) case Repo.insert(changeset) do {:ok, _post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end
def show(conn, %{"id" => id}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) render(conn, "show.html", post: post) end def edit(conn, %{"id" => id}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) changeset = Post.changeset(post) render(conn, "edit.html", post: post, changeset: changeset) end def update(conn, %{"id" => id, "post" => post_params}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) changeset = Post.changeset(post, post_params) case Repo.update(changeset) do {:ok, post} -> conn |> put_flash(:info, "Post updated successfully.") |> redirect(to: user_post_path(conn, :show, conn.assigns[:user], post)) {:error, changeset} -> render(conn, "edit.html", post: post, changeset: changeset) end end def delete(conn, %{"id" => id}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) # delete! ( ), # ( ). Repo.delete!(post) conn |> put_flash(:info, "Post deleted successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) end
defp authorize_user(conn, _opts) do user = get_session(conn, :current_user) if user && Integer.to_string(user.id) == conn.params["user_id"] do conn else conn |> put_flash(:error, "You are not authorized to modify that post!") |> redirect(to: page_path(conn, :index)) |> halt() end end
plug :authorize_user when action in [:new, :create, :update, :edit, :delete]
** (CompileError) test/controllers/post_controller_test.exs:14: function post_path/2 undefined (stdlib) lists.erl:1337: :lists.foreach/2 (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6 (elixir) lib/code.ex:363: Code.require_file/2 (elixir) lib/kernel/parallel_require.ex:50: anonymous fn/4 in Kernel.ParallelRequire.spawn_requires/5
alias Pxblog.User setup do {:ok, user} = create_user conn = build_conn() |> login_user(user) {:ok, conn: conn, user: user} end defp create_user do User.changeset(%User{}, %{email: "test@test.com", username: "test", password: "test", password_confirmation: "test"}) |> Repo.insert end defp login_user(conn, user) do post conn, session_path(conn, :create), user: %{username: user.username, password: user.password} end
test "lists all entries on index", %{conn: conn, user: user} do conn = get conn, user_post_path(conn, :index, user) assert html_response(conn, 200) =~ "Listing posts" end
$ mix test test/controller/post_controller_test.exs:[line number]
test "renders form for new resources", %{conn: conn, user: user} do conn = get conn, user_post_path(conn, :new, user) assert html_response(conn, 200) =~ "New post" end
test "creates resource and redirects when data is valid", %{conn: conn, user: user} do conn = post conn, user_post_path(conn, :create, user), post: @valid_attrs assert redirected_to(conn) == user_post_path(conn, :index, user) assert Repo.get_by(assoc(user, :posts), @valid_attrs) end
test "does not create resource and renders errors when data is invalid", %{conn: conn, user: user} do conn = post conn, user_post_path(conn, :create, user), post: @invalid_attrs assert html_response(conn, 200) =~ "New post" end
test "shows chosen resource", %{conn: conn, user: user} do post = build_post(user) conn = get conn, user_post_path(conn, :show, user, post) assert html_response(conn, 200) =~ "Show post" end
Repo.insert! %Post{}
Repo.insert! %Post{}
. This will no longer work, so now we need to create them with the correct association. Since this line is used quite often in the remaining tests, we will write a helper to facilitate its use. defp build_post(user) do changeset = user |> build_assoc(:posts) |> Post.changeset(@valid_attrs) Repo.insert!(changeset) end
test "renders page not found when id is nonexistent", %{conn: conn, user: user} do assert_raise Ecto.NoResultsError, fn -> get conn, user_post_path(conn, :show, user, -1) end end test "renders form for editing chosen resource", %{conn: conn, user: user} do post = build_post(user) conn = get conn, user_post_path(conn, :edit, user, post) assert html_response(conn, 200) =~ "Edit post" end test "updates chosen resource and redirects when data is valid", %{conn: conn, user: user} do post = build_post(user) conn = put conn, user_post_path(conn, :update, user, post), post: @valid_attrs assert redirected_to(conn) == user_post_path(conn, :show, user, post) assert Repo.get_by(Post, @valid_attrs) end test "does not update chosen resource and renders errors when data is invalid", %{conn: conn, user: user} do post = build_post(user) conn = put conn, user_post_path(conn, :update, user, post), post: %{"body" => nil} assert html_response(conn, 200) =~ "Edit post" end test "deletes chosen resource", %{conn: conn, user: user} do post = build_post(user) conn = delete conn, user_post_path(conn, :delete, user, post) assert redirected_to(conn) == user_post_path(conn, :index, user) refute Repo.get(Post, post.id) end
test "redirects when the specified user does not exist", %{conn: conn} do conn = get conn, user_post_path(conn, :index, -1) assert get_flash(conn, :error) == "Invalid user!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end
test "redirects when trying to edit a post for a different user", %{conn: conn, user: user} do other_user = User.changeset(%User{}, %{email: "test2@test.com", username: "test2", password: "test", password_confirmation: "test"}) |> Repo.insert! post = build_post(user) conn = get conn, user_post_path(conn, :edit, other_user, post) assert get_flash(conn, :error) == "You are not authorized to modify that post!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end
mix test
and wait for the results: ....................................... Finished in 0.4 seconds 39 tests, 0 failures Randomized with seed 102543
Source: https://habr.com/ru/post/313482/
All Articles