应用PETAL(Phoenix、Elixir、TailwindCSS、AlpineJS、LiveView)技术栈构建一个简化版的Instagram Web应用程序
<!--more-->
在第 5 局部中,咱们增加了 show-post 页面,在这部分中,咱们将在主页上进行工作。您能够赶上Instagram 克隆 GitHub Repo。
首先,咱们在 posts 上下文中增加一个函数来获取 feed,而后增加另一个函数来获取 feed 的总数 open lib/instagram_clone/posts.ex
:
@doc """ Returns the list of paginated posts of a given user id And posts of following list of given user id With user and likes preloaded With 2 most recent comments preloaded with user and likes User, page, and per_page are given with the socket assigns ## Examples iex> get_accounts_feed(following_list, assigns) [%{photo_url: "", url_id: ""}, ...] """ def get_accounts_feed(following_list, assigns) do user = assigns.current_user page = assigns.page per_page = assigns.per_page query = from c in Comment, select: %{id: c.id, row_number: over(row_number(), :posts_partition)}, windows: [posts_partition: [partition_by: :post_id, order_by: [desc: :id]]] comments_query = from c in Comment, join: r in subquery(query), on: c.id == r.id and r.row_number <= 2 Post |> where([p], p.user_id in ^following_list) |> or_where([p], p.user_id == ^user.id) |> limit(^per_page) |> offset(^((page - 1) * per_page)) |> order_by(desc: :id) |> preload([:user, :likes, comments: ^{comments_query, [:user, :likes]}]) |> Repo.all() end def get_accounts_feed_total(following_list, assigns) do user = assigns.current_user Post |> where([p], p.user_id in ^following_list) |> or_where([p], p.user_id == ^user.id) |> select([p], count(p.id)) |> Repo.one() end
咱们须要以下列表,在外面lib/instagram_clone/accounts.ex
增加以下函数:
@doc """ Returns the list of following user ids ## Examples iex> get_following_list(user) [3, 2, 1] """ def get_following_list(user) do Follows |> select([f], f.followed_id) |> where(follower_id: ^user.id) |> Repo.all() end
在外面lib/instagram_clone_web/live/page_live.ex
让咱们调配提要:
alias InstagramClone.Uploaders.Avatar alias InstagramClone.Accounts alias InstagramCloneWeb.UserLive.FollowComponent alias InstagramClone.Posts alias InstagramCloneWeb.Live.LikeComponent @impl true def mount(_params, session, socket) do socket = assign_defaults(session, socket) {:ok, socket |> assign(page: 1, per_page: 15), temporary_assigns: [user_feed: []]} end @impl true def handle_params(_params, _uri, socket) do {:noreply, socket |> assign(live_action: apply_action(socket.assigns.current_user)) |> assign_posts()} end defp apply_action(current_user) do if !current_user, do: :root_path end defp assign_posts(socket) do if socket.assigns.current_user do current_user = socket.assigns.current_user following_list = Accounts.get_following_list(current_user) accounts_feed_total = Posts.get_accounts_feed_total(following_list, socket.assigns) socket |> assign(following_list: following_list) |> assign(accounts_feed_total: accounts_feed_total) |> assign_user_feed() else socket end end defp assign_user_feed(socket, following_list) do user_feed = Posts.get_accounts_feed(socket.assigns.following_list, socket.assigns) socket |> assign(user_feed: user_feed) end
页和每页被调配给咱们的挂载函数中的套接字。咱们正在检查用户是否登录以获取以下列表并将其传递给调配提要函数以返回调配了提要的套接字,咱们在句柄参数函数中执行此操作。
当初让咱们为帖子提要创立一个组件,在咱们的 live 文件夹中增加以下文件:
lib/instagram_clone_web/live/page_post_feed_component.ex
lib/instagram_clone_web/live/page_post_feed_component.html.leex
外面lib/instagram_clone_web/live/page_live.html.leex
:
<%= if @current_user do %> <section class="flex"> <div id="user-feed" class="w-3/5" phx-update="append"> <%= for post <- @user_feed do %> <%= live_component @socket, InstagramCloneWeb.Live.PagePostFeedComponent, post: post, id: post.id, current_user: @current_user %> <% end %> </div> </section> <div id="profile-posts-footer" class="flex justify-center" phx-hook="ProfilePostsScroll"> </div><% else %> <%= live_component @socket, InstagramCloneWeb.PageLiveComponent, id: 1 %><% end %>
外面lib/instagram_clone_web/live/page_post_feed_component.ex
:
defmodule InstagramCloneWeb.Live.PagePostFeedComponent do use InstagramCloneWeb, :live_component alias InstagramClone.Uploaders.Avatar alias InstagramClone.Comments alias InstagramClone.Comments.Comment @impl true def mount(socket) do {:ok, socket |> assign(changeset: Comments.change_comment(%Comment{})), temporary_assigns: [comments: []]} end @impl true def handle_event("save", %{"comment" => comment_param}, socket) do %{"body" => body} = comment_param current_user = socket.assigns.current_user post = socket.assigns.post if body == "" do {:noreply, socket} else comment = Comments.create_comment(current_user, post, comment_param) {:noreply, socket |> update(:comments, fn comments -> [comment | comments] end) |> assign(changeset: Comments.change_comment(%Comment{}))} end endend
咱们正在设置表单变更集和长期正文,咱们将应用它们来附加新正文。保留句柄性能与咱们在展现页面上应用的性能雷同。
外面lib/instagram_clone_web/live/page_post_feed_component.html.leex
:
<div class="mb-16 shadow" id="post-<%= @post.id %>"> <div class="flex p-4 items-center"> <!-- Post header section --> <%= live_redirect to: Routes.user_profile_path(@socket, :index, @post.user.username) do %> <%= img_tag Avatar.get_thumb(@post.user.avatar_url), class: "w-8 h-8 rounded-full object-cover object-center" %> <% end %> <div class="ml-3"> <%= live_redirect @post.user.username, to: Routes.user_profile_path(@socket, :index, @post.user.username), class: "truncate font-bold text-sm text-gray-500 hover:underline" %> </div> <!-- End post header section --> </div> <!-- Post Image section --> <%= img_tag @post.photo_url, class: "w-full object-contain h-full shadow-sm" %> <!-- End Post Image section --> <div class="w-full"> <!-- Action icons section --> <div class="flex pl-4 pr-2 pt-2"> <%= live_component @socket, InstagramCloneWeb.Live.LikeComponent, id: @post.id, liked: @post, w_h: "w-8 h-8", current_user: @current_user %> <%= live_redirect to: Routes.live_path(@socket, InstagramCloneWeb.PostLive.Show, @post.url_id) do %> <div class="ml-4 w-8 h-8"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> </svg> </div> <% end %> <div class="ml-4 w-8 h-8 cursor-pointer"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" /> </svg> </div> <div class="w-8 h-8 ml-auto cursor-pointer"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" /> </svg> </div> </div> <!-- End Action icons section --> <!-- Description section --> <button class="px-5 text-xs text-gray-500 font-bold focus:outline-none"><%= @post.total_likes %> likes</button> <!-- End Description Section --> </div> <%= if @post.description do %> <!-- Description section --> <div class="flex mt-2"> <div class="px-4 w-11/12"> <%= live_redirect @post.user.username, to: Routes.user_profile_path(@socket, :index, @post.user.username), class: "font-bold text-sm text-gray-500 hover:underline" %> <span class="text-sm text-gray-700"> <p class="inline"><%= @post.description %></p></span> </span> </div> </div> <!-- End Description Section --> <% end %> <%= if @post.total_comments > 2 do %> <%= live_redirect to: Routes.live_path(@socket, InstagramCloneWeb.PostLive.Show, @post.url_id) do %> <h6 class="px-5 text-sm text-gray-400"> View all <%= @post.total_comments %> comments </h6> <% end %> <% end %> <section id="comments" phx-update="append"> <%= for comment <- @post.comments do %> <div class="flex" id="comment-<%= comment.id %>"> <div class="px-4 w-11/12"> <%= live_redirect comment.user.username, to: Routes.user_profile_path(@socket, :index, comment.user.username), class: "truncate font-bold text-sm text-gray-500 hover:underline" %> <span class="text-sm text-gray-700"> <p class="inline"><%= comment.body %></p> </span> </div> <%= live_component @socket, InstagramCloneWeb.Live.LikeComponent, id: comment.id, liked: comment, w_h: "w-5 h-5", current_user: @current_user %> </div> <% end %> <%= for comment <- @comments do %> <div class="flex" id="comment-<%= comment.id %>"> <div class="px-4 w-11/12"> <%= live_redirect comment.user.username, to: Routes.user_profile_path(@socket, :index, comment.user.username), class: "truncate font-bold text-sm text-gray-500 hover:underline" %> <span class="text-sm text-gray-700"> <p class="inline"><%= comment.body %></p> </span> </div> <%= live_component @socket, InstagramCloneWeb.Live.LikeComponent, id: comment.id, liked: comment, w_h: "w-5 h-5", current_user: @current_user %> </div> <% end %> </section> <h6 class="px-5 py-2 text-xs text-gray-400"><%= Timex.from_now(@post.inserted_at) %></h6> <!-- Comment input section --> <%= f = form_for @changeset, "#", id: @id, phx_submit: "save", phx_target: @myself, class: "p-2 flex items-center mt-3 border-t-2 border-gray-100", x_data: "{ disableSubmit: true, inputText: null, displayCommentBtn: (refs) => { refs.cbtn.classList.remove('opacity-30') refs.cbtn.classList.remove('cursor-not-allowed') }, disableCommentBtn: (refs) => { refs.cbtn.classList.add('opacity-30') refs.cbtn.classList.add('cursor-not-allowed') } }" %> <div class="w-full"> <%= textarea f, :body, class: "w-full border-0 focus:ring-transparent resize-none", rows: 1, placeholder: "Add a comment...", aria_label: "Add a comment...", autocorrect: "off", autocomplete: "off", x_model: "inputText", "@input": "[ (inputText.length != 0) ? [disableSubmit = false, displayCommentBtn($refs)] : [disableSubmit = true, disableCommentBtn($refs)] ]" %> </div> <div> <%= submit "Post", phx_disable_with: "Posting...", class: "text-light-blue-500 opacity-30 cursor-not-allowed font-bold pb-2 text-sm focus:outline-none", x_ref: "cbtn", "@click": "inputText = null", "x_bind:disabled": "disableSubmit" %> </div> </form></div>
咱们应用与显示页面上应用的雷同的表单来增加新评论,并且循环遍历帖子评论和长期评论,以便在增加新评论时可能更新评论。
当咱们喜爱帖子或评论时,咱们须要解决从 Like 组件发送的音讯,咱们还必须解决用钩子触发的事件以加载更多帖子,更新lib/instagram_clone_web/live/page_live.ex
为以下内容:
defmodule InstagramCloneWeb.PageLive do use InstagramCloneWeb, :live_view alias InstagramClone.Uploaders.Avatar alias InstagramClone.Accounts alias InstagramCloneWeb.UserLive.FollowComponent alias InstagramClone.Posts alias InstagramCloneWeb.Live.LikeComponent @impl true def mount(_params, session, socket) do socket = assign_defaults(session, socket) {:ok, socket |> assign(page: 1, per_page: 15), temporary_assigns: [user_feed: []]} end @impl true def handle_params(_params, _uri, socket) do {:noreply, socket |> assign(live_action: apply_action(socket.assigns.current_user)) |> assign_posts()} end @impl true def handle_event("load-more-profile-posts", _, socket) do {:noreply, socket |> load_posts} end defp load_posts(socket) do total_posts = socket.assigns.accounts_feed_total page = socket.assigns.page per_page = socket.assigns.per_page total_pages = ceil(total_posts / per_page) if page == total_pages do socket else socket |> update(:page, &(&1 + 1)) |> assign_user_feed() end end @impl true def handle_info({LikeComponent, :update_comment_likes, _}, socket) do {:noreply, socket} end @impl true def handle_info({LikeComponent, :update_post_likes, post}, socket) do post_feed = Posts.get_post_feed!(post.id) {:noreply, socket |> update(:user_feed, fn user_feed -> [post_feed | user_feed] end)} end defp apply_action(current_user) do if !current_user, do: :root_path end defp assign_posts(socket) do if socket.assigns.current_user do current_user = socket.assigns.current_user following_list = Accounts.get_following_list(current_user) accounts_feed_total = Posts.get_accounts_feed_total(following_list, socket.assigns) socket |> assign(following_list: following_list) |> assign(accounts_feed_total: accounts_feed_total) |> assign_user_feed() else socket end end defp assign_user_feed(socket) do user_feed = Posts.get_accounts_feed(socket.assigns.following_list, socket.assigns) socket |> assign(user_feed: user_feed) endend
让咱们对 Like 组件进行一些更改,因为咱们在帖子和评论之间共享它,将文件挪动到 post_live 文件夹内部的 live 文件夹并将模块重命名为以下内容:
lib/instagram_clone_web/live/like_component.ex
defmodule InstagramCloneWeb.Live.LikeComponent do
lib/instagram_clone_web/live/post_live/show.html.leex
在第 70 行外部重命名该组件:
... InstagramCloneWeb.Live.LikeComponent,...
第 24 行外部lib/instagram_clone_web/live/post_live/comment_component.html.leex
也重命名了该组件:
... InstagramCloneWeb.PostLive.LikeComponent, ...
在外部lib/instagram_clone_web/live/like_component.ex
,让咱们更新send_msg()
以将 like 作为变量发送,而不仅仅是 id:
... defp send_msg(liked) do msg = get_struct_msg_atom(liked) send(self(), {__MODULE__, msg, liked}) end...
同样在 外部lib/instagram_clone_web/live/like_component.ex
,让咱们删除该liked?()
函数,而后检查用户 ID 是否在第 61 行的用户 ID 列表中:
... if assigns.current_user.id in assigns.liked.likes do # LINE 61 # DELETE THIS FUNCTION WE WON"T NEED ANYMORE # Enum.any?(likes, fn l -> # l.user_id == user_id # end)...
在第 30 行,咱们更新以查看数据库:
... if Likes.liked?(current_user.id, liked.id) do...
咱们新更新的文件应如下所示:
defmodule InstagramCloneWeb.Live.LikeComponent do use InstagramCloneWeb, :live_component alias InstagramClone.Likes @impl true def update(assigns, socket) do get_btn_status(socket, assigns) end @impl true def render(assigns) do ~L""" <button phx-target="<%= @myself %>" phx-click="toggle-status" class="<%= @w_h %> focus:outline-none"> <%= @icon %> </button> """ end @impl true def handle_event("toggle-status", _params, socket) do current_user = socket.assigns.current_user liked = socket.assigns.liked if Likes.liked?(current_user.id, liked.id) do unlike(socket, current_user.id, liked) else like(socket, current_user, liked) end end defp like(socket, current_user, liked) do Likes.create_like(current_user, liked) send_msg(liked) {:noreply, socket |> assign(icon: unlike_icon(socket.assigns))} end defp unlike(socket, current_user_id, liked) do Likes.unlike(current_user_id, liked) send_msg(liked) {:noreply, socket |> assign(icon: like_icon(socket.assigns))} end defp send_msg(liked) do msg = get_struct_msg_atom(liked) send(self(), {__MODULE__, msg, liked}) end defp get_btn_status(socket, assigns) do if assigns.current_user.id in assigns.liked.likes do get_socket_assigns(socket, assigns, unlike_icon(assigns)) else get_socket_assigns(socket, assigns, like_icon(assigns)) end end defp get_socket_assigns(socket, assigns, icon) do {:ok, socket |> assign(assigns) |> assign(icon: icon)} end defp get_struct_name(struct) do struct.__struct__ |> Module.split() |> List.last() |> String.downcase() end defp get_struct_msg_atom(struct) do name = get_struct_name(struct) update_struct_likes = "update_#{name}_likes" String.to_atom(update_struct_likes) end defp like_icon(assigns) do ~L""" <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /> </svg> """ end defp unlike_icon(assigns) do ~L""" <svg class="text-red-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd" /> </svg> """ endend
当初,当咱们预加载点赞时,咱们只需发送 id 列表、关上,lib/instagram_clone/posts.ex
并且在咱们收到帖子的每个性能上,咱们必须更新预加载点赞的形式:
defmodule InstagramClone.Posts do @moduledoc """ The Posts context. """ import Ecto.Query, warn: false alias InstagramClone.Repo alias InstagramClone.Posts.Post alias InstagramClone.Accounts.User alias InstagramClone.Comments.Comment alias InstagramClone.Likes.Like @doc """ Returns the list of posts. ## Examples iex> list_posts() [%Post{}, ...] """ def list_posts do Repo.all(Post) end @doc """ Returns the list of paginated posts of a given user id. ## Examples iex> list_user_posts(page: 1, per_page: 10, user_id: 1) [%{photo_url: "", url_id: ""}, ...] """ def list_profile_posts(page: page, per_page: per_page, user_id: user_id) do Post |> select([p], map(p, [:url_id, :photo_url])) |> where(user_id: ^user_id) |> limit(^per_page) |> offset(^((page - 1) * per_page)) |> order_by(desc: :id) |> Repo.all end @doc """ Returns the list of paginated posts of a given user id And posts of following list of given user id With user and likes preloaded With 2 most recent comments preloaded with user and likes User, page, and per_page are given with the socket assigns ## Examples iex> get_accounts_feed(following_list, assigns) [%{photo_url: "", url_id: ""}, ...] """ def get_accounts_feed(following_list, assigns) do user = assigns.current_user page = assigns.page per_page = assigns.per_page query = from c in Comment, select: %{id: c.id, row_number: over(row_number(), :posts_partition)}, windows: [posts_partition: [partition_by: :post_id, order_by: [desc: :id]]] comments_query = from c in Comment, join: r in subquery(query), on: c.id == r.id and r.row_number <= 2 likes_query = Like |> select([l], l.user_id) Post |> where([p], p.user_id in ^following_list) |> or_where([p], p.user_id == ^user.id) |> limit(^per_page) |> offset(^((page - 1) * per_page)) |> order_by(desc: :id) |> preload([:user, likes: ^likes_query, comments: ^{comments_query, [:user, likes: likes_query]}]) |> Repo.all() end @doc """ Gets a single post. Raises `Ecto.NoResultsError` if the Post does not exist. ## Examples iex> get_post!(123) %Post{} iex> get_post!(456) ** (Ecto.NoResultsError) """ def get_post!(id) do likes_query = Like |> select([l], l.user_id) Repo.get!(Post, id) |> Repo.preload([:user, likes: likes_query]) end def get_post_feed!(id) do query = from c in Comment, select: %{id: c.id, row_number: over(row_number(), :posts_partition)}, windows: [posts_partition: [partition_by: :post_id, order_by: [desc: :id]]] comments_query = from c in Comment, join: r in subquery(query), on: c.id == r.id and r.row_number <= 2 likes_query = Like |> select([l], l.user_id) Post |> preload([:user, likes: ^likes_query, comments: ^{comments_query, [:user, likes: likes_query]}]) |> Repo.get!(id) end def get_post_by_url!(id) do likes_query = Like |> select([l], l.user_id) Repo.get_by!(Post, url_id: id) |> Repo.preload([:user, likes: likes_query]) end @doc """ Creates a post. ## Examples iex> create_post(%{field: value}) {:ok, %Post{}} iex> create_post(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_post(%Post{} = post, attrs \\ %{}, user) do post = Ecto.build_assoc(user, :posts, put_url_id(post)) changeset = Post.changeset(post, attrs) update_posts_count = from(u in User, where: u.id == ^user.id) Ecto.Multi.new() |> Ecto.Multi.update_all(:update_posts_count, update_posts_count, inc: [posts_count: 1]) |> Ecto.Multi.insert(:post, changeset) |> Repo.transaction() end # Generates a base64-encoding 8 bytes defp put_url_id(post) do url_id = Base.encode64(:crypto.strong_rand_bytes(8), padding: false) %Post{post | url_id: url_id} end @doc """ Updates a post. ## Examples iex> update_post(post, %{field: new_value}) {:ok, %Post{}} iex> update_post(post, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def update_post(%Post{} = post, attrs) do post |> Post.changeset(attrs) |> Repo.update() end @doc """ Deletes a post. ## Examples iex> delete_post(post) {:ok, %Post{}} iex> delete_post(post) {:error, %Ecto.Changeset{}} """ def delete_post(%Post{} = post) do Repo.delete(post) end @doc """ Returns an `%Ecto.Changeset{}` for tracking post changes. ## Examples iex> change_post(post) %Ecto.Changeset{data: %Post{}} """ def change_post(%Post{} = post, attrs \\ %{}) do Post.changeset(post, attrs) endend
咱们还必须对正文执行雷同的操作,关上lib/instagram_clone/comments.ex
文件并将其更新为以下内容:
defmodule InstagramClone.Comments do @moduledoc """ The Comments context. """ import Ecto.Query, warn: false alias InstagramClone.Repo alias InstagramClone.Likes.Like alias InstagramClone.Comments.Comment @doc """ Returns the list of comments. ## Examples iex> list_comments() [%Comment{}, ...] """ def list_comments do Repo.all(Comment) end def list_post_comments(assigns, public: public) do user = assigns.current_user post_id = assigns.post.id per_page = assigns.per_page page = assigns.page likes_query = Like |> select([l], l.user_id) Comment |> where(post_id: ^post_id) |> get_post_comments_sorting(public, user) |> limit(^per_page) |> offset(^((page - 1) * per_page)) |> preload([:user, likes: ^likes_query]) |> Repo.all end defp get_post_comments_sorting(module, public, user) do if public do order_by(module, asc: :id) else order_by(module, fragment("(CASE WHEN user_id = ? then 1 else 2 end)", ^user.id)) end end @doc """ Gets a single comment. Raises `Ecto.NoResultsError` if the Comment does not exist. ## Examples iex> get_comment!(123) %Comment{} iex> get_comment!(456) ** (Ecto.NoResultsError) """ def get_comment!(id) do likes_query = Like |> select([l], l.user_id) Repo.get!(Comment, id) |> Repo.preload([:user, likes: likes_query]) end @doc """ Creates a comment. ## Examples iex> create_comment(%{field: value}) {:ok, %Comment{}} iex> create_comment(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_comment(user, post, attrs \\ %{}) do update_total_comments = post.__struct__ |> where(id: ^post.id) comment_attrs = %Comment{} |> Comment.changeset(attrs) comment = comment_attrs |> Ecto.Changeset.put_assoc(:user, user) |> Ecto.Changeset.put_assoc(:post, post) Ecto.Multi.new() |> Ecto.Multi.update_all(:update_total_comments, update_total_comments, inc: [total_comments: 1]) |> Ecto.Multi.insert(:comment, comment) |> Repo.transaction() |> case do {:ok, %{comment: comment}} -> likes_query = Like |> select([l], l.user_id) comment |> Repo.preload(likes: likes_query) end end @doc """ Updates a comment. ## Examples iex> update_comment(comment, %{field: new_value}) {:ok, %Comment{}} iex> update_comment(comment, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def update_comment(%Comment{} = comment, attrs) do comment |> Comment.changeset(attrs) |> Repo.update() end @doc """ Deletes a comment. ## Examples iex> delete_comment(comment) {:ok, %Comment{}} iex> delete_comment(comment) {:error, %Ecto.Changeset{}} """ def delete_comment(%Comment{} = comment) do Repo.delete(comment) end @doc """ Returns an `%Ecto.Changeset{}` for tracking comment changes. ## Examples iex> change_comment(comment) %Ecto.Changeset{data: %Comment{}} """ def change_comment(%Comment{} = comment, attrs \\ %{}) do Comment.changeset(comment, attrs) endend
外部lib/instagram_clone_web/live/post_live/show.ex
更新第 6 行:
... alias InstagramCloneWeb.Live.LikeComponent...
外面lib/instagram_clone_web/live/post_live/show.html.leex
更新第70行和行:
... InstagramCloneWeb.Live.LikeComponent,...
外部lib/instagram_clone_web/live/post_live/comment_component.html.leex
更新第 24 行:
... InstagramCloneWeb.Live.LikeComponent,...
更新lib/instagram_clone/likes.ex
如下:
defmodule InstagramClone.Likes do import Ecto.Query, warn: false alias InstagramClone.Repo alias InstagramClone.Likes.Like def create_like(user, liked) do user = Ecto.build_assoc(user, :likes) like = Ecto.build_assoc(liked, :likes, user) update_total_likes = liked.__struct__ |> where(id: ^liked.id) Ecto.Multi.new() |> Ecto.Multi.insert(:like, like) |> Ecto.Multi.update_all(:update_total_likes, update_total_likes, inc: [total_likes: 1]) |> Repo.transaction() end def unlike(user_id, liked) do like = liked?(user_id, liked.id) update_total_likes = liked.__struct__ |> where(id: ^liked.id) Ecto.Multi.new() |> Ecto.Multi.delete(:like, like) |> Ecto.Multi.update_all(:update_total_likes, update_total_likes, inc: [total_likes: -1]) |> Repo.transaction() end # Returns nil if not found def liked?(user_id, liked_id) do Repo.get_by(Like, [user_id: user_id, liked_id: liked_id]) endend
让咱们增加一个蕴含 5 个随机用户倡议的侧边栏,在外面lib/instagram_clone/accounts.ex
增加以下函数:
def random_5(user) do following_list = get_following_list(user) User |> where([u], u.id not in ^following_list) |> where([u], u.id != ^user.id) |> order_by(desc: fragment("Random()")) |> limit(5) |> Repo.all() end
在外面lib/instagram_clone_web/live/page_live.ex
增加一个handle_info()
并将公有assign_posts()
函数更新为以下内容:
... @impl true def handle_info({FollowComponent, :update_totals, _}, socket) do {:noreply, socket} end defp assign_posts(socket) do if socket.assigns.current_user do current_user = socket.assigns.current_user random_5_users = Accounts.random_5(current_user) socket |> assign(users: random_5_users) |> assign_user_feed() else socket end end
当初要显示带有随机用户的侧边栏,请将 Inside 更新lib/instagram_clone_web/live/page_live.html.leex
为以下内容:
<%= if @current_user do %> <section class="flex"> <div id="user-feed" class="w-3/5" phx-update="append"> <%= for post <- @user_feed do %> <%= live_component @socket, InstagramCloneWeb.Live.PagePostFeedComponent, post: post, id: post.id, current_user: @current_user %> <% end %> </div> <div> <sidebar class="fixed w-1/4"> <section class=" ml-auto pl-8"> <div class="flex items-center"> <!-- Post header section --> <%= live_redirect to: Routes.user_profile_path(@socket, :index, @current_user.username) do %> <%= img_tag Avatar.get_thumb(@current_user.avatar_url), class: "w-14 h-14 rounded-full object-cover object-center" %> <% end %> <div class="ml-3"> <%= live_redirect @current_user.username, to: Routes.user_profile_path(@socket, :index, @current_user.username), class: "truncate font-bold text-sm text-gray-500 hover:underline" %> <h2 class="text-sm text-gray-500"><%= @current_user.full_name %></h2> </div> <!-- End post header section --> </div> <h1 class="text-gray-500 mt-5">Suggestions For You</h1> <%= for user <- @users do %> <div class="flex items-center p-3"> <!-- Post header section --> <%= live_redirect to: Routes.user_profile_path(@socket, :index, user.username) do %> <%= img_tag Avatar.get_thumb(user.avatar_url), class: "w-10 h-10 rounded-full object-cover object-center" %> <% end %> <div class="ml-3"> <%= live_redirect user.username, to: Routes.user_profile_path(@socket, :index, user.username), class: "truncate font-bold text-sm text-gray-500 hover:underline" %> <h2 class="text-xs text-gray-500">Suggested for you</h2> </div> <span class="ml-auto"> <%= live_component @socket, InstagramCloneWeb.UserLive.FollowComponent, id: user.id, user: user, current_user: @current_user %> </span> <!-- End post header section --> </div> <% end %> </section> </sidebar> </div> </section> <div id="profile-posts-footer" class="flex justify-center" phx-hook="ProfilePostsScroll"> </div><% else %> <%= live_component @socket, InstagramCloneWeb.PageLiveComponent, id: 1 %><% end %>
当初就是这样,您能够通过将以下列表发送到后续组件来进步代码效率,从而无需拜访数据库即可设置按钮,从而改良代码。
转自:Elixirprogrammer