hi
I made a derivative of the Chirp TImeline example video and have a post schema with a belongs_to User
post.ex
schema
schema "post" do
field :body, :string
field :image, :string
field :likes_count, :integer, default: 0
field :repost_count, :integer, default: 0
belongs_to :user, User
timestamps()
end
form_component.html.heex
<div>
<h2><%= @title %></h2>
<.form
let={f}
for={@changeset}
id="post-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save">
<%= hidden_input f, :user_id, value: (@user_id) %>
<%= hidden_input f, :website_id, value: (@website_id) %>
<%= hidden_input f, :channel_id, value: (@user_id) %>
<%= textarea f, :body, class: "textarea h-32 w-full textarea-primary" %>
<div class="p-4">
<%= error_tag f, :body %>
</div>
<div>
<%= submit "Save", phx_disable_with: "Saving...", class: "p-4 btn btn-sm" %>
</div>
</.form>
</div>
on edit and new, when I try to render the preloaded user in the live_component, the edits and new complete, but they also crash the Genserver
def render(assigns) do
~H"""
<div class="Media">
<div id={"post-#{@post.id}"} class="Media">
<div class="Media-body">
<p><%= @post.body %><%= inspect @post.user.id %></p>
<a href="#" phx-click="like" phx-target={@myself}>đź‘Ť<%= @post.likes_count %></a>
<a href="#" phx-click="retale" phx-target={@myself}>♻️<%= @post.retales_count %></a>
<span><%= live_patch "Edit", to: Routes.tale_index_path(@socket, :edit, @post) %></span>
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: @post.id, data: [confirm: "Are you sure?"] %></span>
</div>
</div>
</div>
"""
end
<div class="container mx-auto p-8">
<%= if @live_action in [:new, :edit] do %>
<.modal return_to={Routes.post_index_path(@socket, :index)}>
<.live_component
module={AppWeb.PostLive.FormComponent}
id={@post.id || :new}
title={@page_title}
action={@live_action}
post={@post}
website_id={@website_id}
user_id={@user_id}
channel_id={@channel_id}
return_to={Routes.post_index_path(@socket, :index)}
/>
</.modal>
<% end %>
<div id="post" phx-update="prepend">
<%= for post <- @posts do %>
<%= live_component @socket, FolkbotWeb.TaleLive.TaleComponent, id: tale.id, post: post %>
<% end %>
</div>
<span><%= live_patch "New Tale", to: Routes.tale_index_path(@socket, :new) %></span>
</div>
</div>
post_component.ex
<div class="container mx-auto p-8">
<%= if @live_action in [:new, :edit] do %>
<.modal return_to={Routes.post_index_path(@socket, :index)}>
<.live_component
module={AppWeb.postLive.FormComponent}
id={@post.id || :new}
title={@page_title}
action={@live_action}
post={@post}
website_id={@website_id}
user_id={@user_id}
channel_id={@channel_id}
return_to={Routes.post_index_path(@socket, :index)}
/>
</.modal>
<% end %>
<div id="posts" phx-update="prepend">
<%= for post <- @posts do %>
<%= live_component @socket, AppWeb.postLive.postComponent, id: post.id, post: post %>
<% end %>
</div>
<span><%= live_patch "New post", to: Routes.post_index_path(@socket, :new) %></span>
</div>
index.ex
defmodule AppWeb.postLive.Index do
use AppWeb, :live_view
alias App.Channels
alias App.Channels.post
alias AppWeb.MountHelpers
require Logger
@impl true
def mount(params, session, socket) do
if connected?(socket), do: Channels.subscribe()
socket =
socket
|> MountHelpers.assign_defaults(params, session, [:post, :read])
|> assign(:page_title, "posts")
|> assign_posts()
# |> assign_blank_banner()
{:ok, socket, temporary_assigns: [posts: []]}
end
defp assign_posts(socket) do
posts = list_posts
assign(socket, :posts, posts)
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit post")
|> assign(:post, Channels.get_post!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New post")
|> assign(:post, %post{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "posts")
|> assign(:post, nil)
end
def handle_event("delete", %{"id" => id}, socket) do
Logger.info("Delete: #{inspect(id)}")
post = Channels.get_post!(id)
# Logger.info("Delete: #{inspect(post)}")
# IO.puts("DEBUG: broadcast")
# IO.inspect(post)
case Channels.delete_post(post) do
{:ok, post} ->
send(self(), {:post_deleted, post})
# Logger.info("Delete: #{inspect(post)}")
# IO.puts("DEBUG: Delete")
# IO.inspect(post)
{:noreply,
socket
|> put_flash(:info, "post deleted successfully")
|> assign(:posts, list_posts())
|> push_redirect(to: "/posts")
}
{:error, changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
def handle_info({:post_created, post}, socket) do
{:noreply, update(socket, :posts, fn posts -> [post | posts] end)}
end
def handle_info({:post_updated, post}, socket) do
{:noreply, update(socket, :posts, fn posts -> [post | posts] end)}
end
# def handle_info({:post_deleted, post}, socket) do
# {:noreply, update(socket, :posts, fn posts -> posts -- [post] end)}
# end
def handle_info({:post_deleted, post}, socket) do
socket =
socket
|> update(:posts, fn posts -> posts -- [post] end)
{:noreply, socket}
end
def list_posts do
Channels.list_preloaded_posts_order_by_id()
end
end
channels.ex Context
def get_post!(id), do: Repo.get!(post, id)
def get_preloaded_post!(id) do
post_id = id
Repo.one(from t in post,
where: t.id == ^post_id, preload: :user)
# |> Repo.preload(:users)
end
def create_post(attrs \\ %{}) do
%post{}
|> post.changeset(attrs)
|> Repo.insert()
|> broadcast(:post_created)
end
def update_post(%post{} = post, attrs) do
post
|> post.changeset(attrs)
|> Repo.update()
|> broadcast(:post_updated)
end
def delete_post(%post{} = post) do
Repo.delete(post)
broadcast({:ok, post}, :post_deleted)
end
def change_post(%post{} = post, attrs \\ %{}) do
post.changeset(post, attrs)
end
def subscribe do
Phoenix.PubSub.subscribe(App.PubSub, "posts")
end
def broadcast({:error, _reason} = error, _event), do: error
def broadcast({:ok, post}, event) do
Phoenix.PubSub.broadcast(App.PubSub, "posts", {event, post})
{:ok, post}
end
def inc_likes(%post{id: id}) do
{1, [post]} =
from(t in post, where: t.id == ^id, select: t, preload: :user)
|> Repo.update_all(inc: [likes_count: 1])
broadcast({:ok, post}, :post_updated)
end
def inc_reposts(%post{id: id}) do
{1, [post]} =
from(t in post, where: t.id == ^id, select: t, preload: :user)
|> Repo.update_all(inc: [reposts_count: 1])
broadcast({:ok, post}, :post_updated)
end
form_component.ex
defmodule AppWeb.postLive.FormComponent do
use AppWeb, :live_component
alias AppWeb.postLive.Index
alias App.Channels
@impl true
def update(%{post: post} = assigns, socket) do
post = Channels.get_preloaded_post!(assigns.id)
changeset = Channels.change_post(post)
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)}
end
# @impl true
# def handle_event("delete", %{"id" => id}, socket) do
# post = Channels.get_post!(id)
# {:ok, _} = Channels.delete_post(post)
# {:noreply, assign(socket, :posts, Index.list_posts())}
# end
@impl true
def handle_event("validate", %{"post" => post_params}, socket) do
changeset =
socket.assigns.post
|> Channels.change_post(post_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("save", %{"post" => post_params}, socket) do
save_post(socket, socket.assigns.action, post_params)
end
defp save_post(socket, :edit, post_params) do
case Channels.update_post(socket.assigns.post, post_params) do
{:ok, _post} ->
{:noreply,
socket
# |> put_flash(:info, "post updated successfully")
|> push_redirect(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
defp save_post(socket, :new, post_params) do
case Channels.create_post(post_params) do
{:ok, _post} ->
{:noreply,
socket
# |> put_flash(:info, "post created successfully")
|> push_redirect(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
end
trying to understand where I should be preloading user in the Live Component lifecycle for new and edit
the new and edit complete, but they crash the genserver in the process
[debug] QUERY OK db=0.7ms queue=0.2ms idle=1637.0ms
UPDATE "posts" SET "body" = $1, "updated_at" = $2 WHERE "id" = $3 ["new ggggjjj ddd", ~N[2022-01-29 18:51:37], 156]
[debug] QUERY OK source="users_tokens" db=0.4ms idle=1643.1ms
SELECT u1."id", u1."admin", u1."editor", u1."host", u1."seller", u1."banner_image", u1."profile_image", u1."bio", u1."email", u1."first_name", u1."last_name", u1."location", u1."slug", u1."snowflake", u1."tagline", u1."username", u1."website", u1."hashed_password", u1."confirmed_at", u1."inserted_at", u1."updated_at" FROM "users_tokens" AS u0 INNER JOIN "users" AS u1 ON u1."id" = u0."user_id" WHERE ((u0."token" = $1) AND (u0."context" = $2)) AND (u0."inserted_at" > $3::timestamp + (-(60)::numeric * interval '1 day')) [<<247, 30, 135, 246, 22, 78, 171, 35, 35, 74, 184, 30, 39, 109, 117, 32, 63, 93, 78, 105, 211, 196, 51, 160, 68, 20, 222, 229, 86, 178, 196, 175>>, "session", ~U[2022-01-29 18:51:37.904014Z]]
[error] GenServer #PID<0.2931.0> terminating
** (KeyError) key :id not found in: #Ecto.Association.NotLoaded<association :user is not loaded>
(App 0.1.0) lib/App_web/live/post_live/post_component.ex:11: anonymous fn/2 in AppWeb.postLive.postComponent.render/1
(phoenix_live_view 0.17.6) lib/phoenix_live_view/diff.ex:372: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.17.6) lib/phoenix_live_view/diff.ex:658: Phoenix.LiveView.Diff.render_component/9
(phoenix_live_view 0.17.6) lib/phoenix_live_view/diff.ex:603: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
(elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
(stdlib 3.12.1) maps.erl:232: :maps.fold_1/3
(phoenix_live_view 0.17.6) lib/phoenix_live_view/diff.ex:576: Phoenix.LiveView.Diff.render_pending_components/6
(phoenix_live_view 0.17.6) lib/phoenix_live_view/diff.ex:145: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.17.6) lib/phoenix_live_view/channel.ex:769: Phoenix.LiveView.Channel.render_diff/3
(phoenix_live_view 0.17.6) lib/phoenix_live_view/channel.ex:625: Phoenix.LiveView.Channel.handle_changed/4
(stdlib 3.12.1) gen_server.erl:637: :gen_server.try_dispatch/4
(stdlib 3.12.1) gen_server.erl:711: :gen_server.handle_msg/6
(stdlib 3.12.1) proc_lib.erl:259: :proc_lib.wake_up/3
Last message: {:post_updated, %App.Channels.post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "new ggggjjj ddd", channel: #Ecto.Association.NotLoaded<association :channel is not loaded>, channel_id: 1, id: 156, image: nil, inserted_at: ~N[2022-01-29 08:12:38], likes_count: 10, parent_id: nil, reposts_count: 7, updated_at: ~N[2022-01-29 18:51:37], user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: 1, website: #Ecto.Association.NotLoaded<association :website is not loaded>, website_id: 1}}
State: %{components: {%{1 => {AppWeb.postLive.postComponent, 162, %{__changed__: %{}, flash: %{}, id: 162, myself: %Phoenix.LiveComponent.CID{cid: 1}, post: %App.Channels.post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "ddd sss. dddd", channel: #Ecto.Association.NotLoaded<association :channel is not loaded>, channel_id: 1, id: 162, image: nil, inserted_at: ~N[2022-01-29 08:51:47], likes_count: 2, parent_id: nil, reposts_count: 2, updated_at: ~N[2022-01-29 18:32:06], user: #App.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, admin: true, banner_image: %{file_name: "5.sm.webp", updated_at: ~N[2021-12-28 08:26:57]}, bio: nil, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, confirmed_at: ~N[2022-01-04 05:34:55], content_accessible: #Ecto.Association.NotLoaded<association :content_accessible is not loaded>, editor: false, email: "niccolox@devekko.com", first_name: nil, host: true, hosts_channels: #Ecto.Association.NotLoaded<association :hosts_channels is not loaded>, id: 1, inserted_at: ~U[2021-11-24 02:32:42.000000Z], last_name: nil, location: nil, owns_websites: #Ecto.Association.NotLoaded<association :owns_websites is not loaded>, profile_image: %{file_name: "pixel.png", updated_at: ~N[2021-12-27 03:41:38]}, seller: false, slug: nil, snowflake: "6862262450054500352", tagline: "the architect", updated_at: ~U[2022-01-04 05:34:56.000000Z], username: "niccolox", website: "devekko.com", ...>, user_id: 1, website: #Ecto.Association.NotLoaded<association :website is not loaded>, website_id: 1}}, %{__changed__: %{}, root_view: AppWeb.postLive.Index}, {37841891526857918788129004884530619461, %{}}}, 2 => {AppWeb.postLive.postComponent, 161, %{__changed__: %{}, flash: %{}, id: 161, myself: %Phoenix.LiveComponent.CID{cid: 2}, post: %App.Channels.post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "preloads are awesome", channel: #Ecto.Association.NotLoaded<association :channel is not loaded>, channel_id: 1, id: 161, image: nil, inserted_at: ~N[2022-01-29 08:49:30], likes_count: 3, parent_id: nil, reposts_count: 1, updated_at: ~N[2022-01-29 18:33:41], user: #App.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, admin: true, banner_image: %{file_name: "5.sm.webp", updated_at: ~N[2021-12-28 08:26:57]}, bio: nil, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, confirmed_at: ~N[2022-01-04 05:34:55], content_accessible: #Ecto.Association.NotLoaded<association :content_accessible is not loaded>, editor: false, email: "niccolox@devekko.com", first_name: nil, host: true, hosts_channels: #Ecto.Association.NotLoaded<association :hosts_channels is not loaded>, id: 1, inserted_at: ~U[2021-11-24 02:32:42.000000Z], last_name: nil, location: nil, owns_websites: #Ecto.Association.NotLoaded<association :owns_websites is not loaded>, profile_image: %{file_name: "pixel.png", updated_at: ~N[2021-12-27 03:41:38]}, seller: false, slug: nil, snowflake: "6862262450054500352", tagline: "the architect", updated_at: ~U[2022-01-04 05:34:56.000000Z], username: "niccolox", website: "devekko.com", ...>, user_id: 1, website: #Ecto.Association.NotLoaded<association :website is not loaded>, website_id: 1}}, %{__changed__: %{}, root_view: AppWeb.postLive.Index}, {37841891526857918788129004884530619461, %{}}}, 3 => {AppWeb.postLive.postComponent, 160, %{__changed__: %{}, flash: %{}, id: 160, myself: %Phoenix.LiveComponent.CID{cid: 3}, post: %App.Channels.post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "thihs 11111111", channel: #Ecto.Association.NotLoaded<association :channel is not loaded>, channel_id: 1, id: 160, image: nil, inserted_at: ~N[2022-01-29 08:18:03], likes_count: 17, parent_id: nil, reposts_count: 15, updated_at: ~N[2022-01-29 18:25:58], user: #App.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, admin: true, banner_image: %{file_name: "5.sm.webp", updated_at: ~N[2021-12-28 08:26:57]}, bio: nil, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, confirmed_at: ~N[2022-01-04 05:34:55], content_accessible: #Ecto.Association.NotLoaded<association :content_accessible is not loaded>, editor: false, email: "niccolox@devekko.com", first_name: nil, host: true, hosts_channels: #Ecto.Association.NotLoaded<association :hosts_channels is not loaded>, id: 1, inserted_at: ~U[2021-11-24 02:32:42.000000Z], last_name: nil, location: nil, owns_websites: #Ecto.Association.NotLoaded<association :owns_websites is not loaded>, profile_image: %{file_name: "pixel.png", updated_at: ~N[2021-12-27 03:41:38]}, seller: false, slug: nil, snowflake: "6862262450054500352", tagline: "the architect", updated_at: ~U[2022-01-04 05:34:56.000000Z], username: "niccolox", website: "devekko.com", ...>, user_id: 1, website: #Ecto.Association.NotLoaded<association :website is not loaded>, website_id: 1}}, %{__changed__: %{}, root_view: AppWeb.postLive.Index}, {37841891526857918788129004884530619461, %{}}}, 4 => {AppWeb.postLive.postComponent, 159, %{__changed__: %{}, flash: %{}, id: 159, myself: %Phoenix.LiveComponent.CID{cid: 4}, post: %App.Channels.post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "AWESOME", channel: #Ecto.Association.NotLoaded<association :channel is not loaded>, channel_id: 1, id: 159, image: nil, inserted_at: ~N[2022-01-29 08:17:11], likes_count: 7, parent_id: nil, reposts_count: 2, updated_at: ~N[2022-01-29 08:51:18], user: #App.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, admin: true, banner_image: %{file_name: "5.sm.webp", updated_at: ~N[2021-12-28 08:26:57]}, bio: nil, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, confirmed_at: ~N[2022-01-04 05:34:55], content_accessible: #Ecto.Association.NotLoaded<association :content_accessible is not loaded>, editor: false, email: "niccolox@devekko.com", first_name: nil, host: true, hosts_channels: #Ecto.Association.NotLoaded<association :hosts_channels is not loaded>, id: 1, inserted_at: ~U[2021-11-24 02:32:42.000000Z], last_name: nil, location: nil, owns_websites: #Ecto.Association.NotLoaded<association :owns_websites is n (truncated)
[debug] QUERY OK source="content_access" db=0.2ms idle=1644.0ms
SELECT c0."id", c0."user_id", c0."s