Hodgson

Hodgson

User-Story Relationship Issue on Create: (KeyError) key :email not found in: #Ecto.Association.NotLoaded

Phoenix: User-Story Relationship Issue on Create

Hello Elixir community,

I’m new to Elixir and Phoenix, and I’m currently building a simple blogging platform as a learning project. The application has a one-to-many relationship between users and stories, both created using Phoenix generators.

I’m trying to display the user’s email on the story_live index page, but I’m encountering an issue when creating a new story. Here’s the relevant code:

# In lib/my_app/stories/story.ex
schema "stories" do
   field :title, :string
   field :body, :string
   belongs_to :user, Blog.Accounts.User

   timestamps(type: :utc_datetime)
end

# In web/live/story_live/index.ex
@impl true
def mount(_params, _session, socket) do
  {:ok, stream(socket, :stories, Stories.list_stories())}
end

# In lib/my_app/stories.ex
def list_stories do
  Story
  |> preload(:user)
  |> Repo.all()
end

# In web/live/story_live/index.html.heex
<.table
  id="stories"
  rows={@streams.stories}
  row_click={fn {_id, story} -> JS.navigate(~p"/stories/#{story}") end}
>
  <:col :let={{_id, story}} label="Title"><%= story.title %></:col>
  <:col :let={{_id, story}} label="Body"><%= story.body %></:col>
  <:col :let={{_id, story}} label="User Email"><%= story.user.email %></:col>
  <!-- ... action columns ... -->
</.table>

<.modal :if={@live_action in [:new, :edit]} id="story-modal" show on_cancel={JS.patch(~p"/stories")}>
  <.live_component
    module={BlogWeb.StoryLive.FormComponent}
    id={@story.id || :new}
    title={@page_title}
    action={@live_action}
    story={@story}
    user_id={@current_user.id}
    patch={~p"/stories"}
  />
</.modal>

This setup works fine for displaying existing stories and their associated user emails. However, when I create a new story, the Elixir process crashes with the following error:

[error] GenServer #PID<0.1522.0> terminating
** (KeyError) key :email not found in: #Ecto.Association.NotLoaded<association :user is not loaded>
    (blog 0.1.0) lib/blog_web/live/story_live/index.html.heex:17: anonymous fn/3 in BlogWeb.StoryLive.Index.render/1
    (phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:391: Phoenix.LiveView.Diff.traverse/7
    ...

The error suggests that the :user association is not loaded for the newly created story. This is puzzling because both the index and create actions use the same list_stories context function, which preloads the :user details.

I’m particularly confused by these aspects:

  1. Why does the error occur only for newly created stories?
  2. Why doesn’t the preload(:user) in list_stories function seem to be working for new stories?
  3. Is there a difference in how Phoenix handles newly created records vs. existing ones in LiveView?

I would greatly appreciate any insights into this behavior. I’m eager to understand and learn from this issue to improve my grasp of Phoenix and Elixir.

Thank you in advance for your time and expertise!

Marked As Solved

FlyingNoodle

FlyingNoodle

In your Stories.create_story/1 you almost certainly are not preloading the user so it is missing on the created record.

Also Liked

codeanpeace

codeanpeace

Does the story returned by Stories.create_story/1 have the user preloaded? If not and the parent liveview that gets notified of it then directly streams it, that would explain this error.

Phxie

Phxie

Edit 2:

I just realised you may be refering to existing records as the mounted records, and the preload may be failing on the updated stream when you are creating new records.

The below is what I use for updating messages in a stream.

Info() is specific to my app. Its something I use to share handle_info over multiple index routes, so treat it like handle_info.

Pass the message to a handle_info() then use the message id to get a preloaded version of the message, then insert it into the stream. Might be able to run repo.preload on the message in the params, can’t remember why I didn’t.

  def create_message(params, current_user, room) do
    if length(message_limit_reached(current_user, room)) >= 10 do
      {:ok, :rate_limit}
    else
      previous_message = get_previous_message(room)
      merge_flag       = if previous_message && previous_message.user_id === current_user.id, do: true, else: false

      Multi.new()
        |> Multi.insert(:message, %Message{user_id: current_user.id, room_id: room.id, merge: merge_flag} 
        |> Message.changeset(params))
        |> Repo.transaction()
        |> notify(room.id, :message_created)
      {:ok, :new_message}
    end

  def info(:message_created, %{message: message}, socket) do
    new_message = ecto_get!(Message, message.id, [:user])
    {:noreply, stream(socket, :messages, [new_message], at: 0)}
  end

  def ecto_get!(module, id, preload \\ []) do
    Repo.get!(module, id)
    |> Repo.preload(preload)
  end

Edit: The answer below is likely wrong, but still may be useful.

When you say existing records work, do you mean records you’ve seeded to the database?

Have you come accross IO.Inspect yet?

In your mount or heex add: IO.inspect(Stories.list_stories())

IO.inspect(Stories.list_stories())

or in the heex:

<%= IO.inspect(@streams.stories) %>

The above will output the inspect results to terminal and will consist of all returned stories by the list_stories function, and for every story returned, near the bottom of the Struct you will see all available preloads as lists.

To narrow down your issue the you can use inspect to see whether:

  • No story preloads exist
  • All story preloads exist
  • Existing story preloads exist but new stories do not.

As an example you will get something like the below in the terminal from the inspect.

1 - If your preload isn’t set up, but an association exists it will return not loaded.
2 - If the preload is set up but no record exists then you will get nil or depending on whether its belongs_to or has_many.
3 - If the preload has been set up and the record exists, the struct will be returned

1 - children: #Ecto.Association.NotLoaded<association :children is not loaded>,
2 - comments: nil
3 - user: #Phxie.Schema.User<
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  id: 1,
  etc...

Sorry this doesn’t answer the question, but IO.Inpsect() will always help you to narrow down the cause of the issue.

Hope this helps, and good luck.

Hodgson

Hodgson

Thank you all for your help in diagnosing the issue! I really appreciate the time and expertise you’ve shared.

As you suggested, the problem was that my create_story function wasn’t preloading the :user details. I’ve updated the code as recommended, and it now works perfectly.

Here’s the updated create_story function:

def create_story(attrs \\ %{}) do
  %Story{}
  |> Story.changeset(attrs)
  |> Repo.insert()
  |> case do
    {:ok, story} -> {:ok, Repo.preload(story, :user)}
    {:error, changes} -> {:error, changes}
  end
end

Thank you all again for your help!

Where Next?

Popular in Questions Top

New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement