Skip to content

Instantly share code, notes, and snippets.

@bacalj
Last active November 17, 2020 12:54
Show Gist options
  • Save bacalj/5b2b9a4ef2938957276c0f5723f6d605 to your computer and use it in GitHub Desktop.
Save bacalj/5b2b9a4ef2938957276c0f5723f6d605 to your computer and use it in GitHub Desktop.
Phoenix Blog Iteration Number Four

Notes

1. Make the app

$ mix phx.new blogfour
$ cd blogfour
$ mix ecto.create
$ mix phx.server

2. gen auth

# mix.exs

defp deps do
  
  # ...
  
  {:phx_gen_auth, "~> 0.4.0", only: [:dev], runtime: false},
  
$ mix deps.get
$ mix phx.gen.auth Accounts User users
$ mix deps.get
$ mix ecto.migrate
<!-- app.html.eex -->
<%= if @current_user do %>
  Hello, you are logged in.
<% end %>

3. Create Post context and schema, and set up relation to user

generate

$ mix phx.gen.html Content Post posts title:string body:text user_id:references:users

adjust the resulting schema, replacing explicit mention of fk with belongs_to relationship

# post.ex

#field :user_id, :id

belongs_to :user, Blogfour.Accounts.User

add has_many side of relationship to user schema? TODO

# user.ex

has_many :posts, Blogfour.Content.Post

4. Set up resource routes for Post, then migrate

# router.ex

resources "/posts", PostController
$ mix ecto.migrate

5. Require auth for Post create, update, and delete

# post_controller.ex

import BlogfourWeb.UserAuth, only: [require_authenticated_user: 2]

plug :require_authenticated_user when action not in [:index, :show]

6. Give each of Post controller functions access to the current user

# post_controller.ex

def action(conn, _) do
  args = [conn, conn.params, conn.assigns.current_user]
  apply(__MODULE__, action_name(conn), args)
end

def index(conn, _params, current_user) do
 #...
end

# and so on with show, new, ...etc

8. In create function, make sure you pass it through to Content.create_post

# post_controller.ex

def create(conn, %{"post" => post_params}, current_user) do
  case Content.create_post(current_user, post_params) do
    {:ok, post} ->
      conn
      |> put_flash(:info, "Post created successfully.")
      |> redirect(to: Routes.post_path(conn, :show, post))

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

9. In Content context getters, make sure getters preload the user

# content.ex

alias Blogfour.Accounts

def list_posts do
  Repo.all(Post)
  Repo.all(Post) |> Repo.preload(:user)
end

def get_post!(id), do: Repo.get!(Post, id) |> Repo.preload(:user)

10. In the Content context create, add the association

def create_post(%Accounts.User{} = user, attrs \\ %{}) do
  %Post{}
  |> Post.changeset(attrs)
  |> Ecto.Changeset.put_assoc(:user, user)
  |> Repo.insert()
end

11. Expose user on post in templates

<!-- index --> 
<%= post.user.email %>

<!-- show -->
<%= @post.user.email %>

12. Create the Discussion context and Comment schema

$ phx.gen.html Discussion Comment comments content:text post_id:references:posts user_id:references:users
# router.ex

resources "/comments", CommentController

13. add resource routes and stub for our create-comment-on-post-show

# router.ex

resources "/posts", PostController do
  post "/comment", PostController, :add_local_comment
end

resources "/comments", CommentController

13. Add associations to schemas

# post.ex

has_many :comments, Blogfour.Discussion.Comment
# user.ex
has_many :comments, Blogfour.Discussion.Comment
# comment.ex

alias Accounts.User

# field :post_id, :id
# field :user_id, :id

belongs_to :user, Blogfour.Accounts.User
belongs_to :post, Blogfour.Content.Post

14. Add a dedicated commments partial to post show template

$ touch lib/blogfour_web/templates/post/post_comment_area.html.eex
<!-- ... post/show.html.eex -->
<%= render "post_comment_area.html", 
    post: @post, 
    changeset: @changeset, 
    action: Routes.post_post_path(@conn, :add_local_comment, @post) %>
<!-- post_comment_area.html.eex -->
<div class="comments">
    <h3>Comment Form</h3>

    <%= form_for @changeset, @action, fn f -> %>
        <div class="form-group">
            <label>Content</label>
            <%= textarea f, :content, class: "form-control" %>
        </div>
    
        <div class="form-group">
            <%= submit "Add comment", class: "btn btn-primary" %>
        </div>
    <% end %>

    <h3>Comments Shall Be Listed Below</h3>

    <%= for comment <- @post.comments do %>
        <li><%= comment.content %></li>
    <% end %>
</div>

15. post controller show must now preload comments and get comment changeset prepped

# post_controller.ex

alias Blogfour.Repo

def show(conn, %{"id" => id}, current_user) do
  post = Content.get_post!(id) |> Repo.preload(:comments)
  changeset = Comment.changeset(%Comment{}, %{})
  render(conn, "show.html", post: post, changeset: changeset)
end

16. add dedicated function to discussion context for adding comment to post

# discussion.ex

alias Blogfour.Accounts

def create_related_comment(attrs \\ %{}, %Accounts.User{} = user, post) do
  %Comment{}
  |> Comment.changeset(attrs)
  |> Ecto.Changeset.put_assoc(:post, post)  
  |> Ecto.Changeset.put_assoc(:user, user) 
  |> Repo.insert() # |> IO.inspect
end

17. add dedicated function to post controller to call that (as stubbed in router)

# post_controller.ex

alias Blogfour.Discussion
alias Blogfour.Discussion.Comment

def add_local_comment(conn, %{"comment" => comment_params, "post_id" => post_id}, current_user) do
  post = post_id |> Content.get_post!() |> Repo.preload([:comments])
  case Discussion.create_related_comment(comment_params, current_user, post) do
    {:ok, _comment} ->
      conn
      |> put_flash(:info, "all good!")
      |> redirect(to: Routes.post_path(conn, :show, post))
    {:error, _error} ->
      conn
      |> put_flash(:error, "badness!")
      |> redirect(to: Routes.post_path(conn, :show, post))
  end
end

18. add the necessary preloads to getters in the Discussion context

# discussion.ex

def list_comments do
  Repo.all(Comment) 
  |> Repo.preload(:user) 
  |> Repo.preload(:post)
end

# ...

def get_comment!(id) do
  Repo.get!(Comment, id) 
  |> Repo.preload(:user) 
  |> Repo.preload(:post)
end
@bacalj
Copy link
Author

bacalj commented Nov 14, 2020

lotsa places where there would be a # ... to indicate other code exists between but I not doing that now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment