lapinkoira
Issue with Ecto query preloading
Hi all, I am reading the Programming Phoenix book and following the steps to implement the Rumbl webapp.
Then at some steps I try to add something custom to learn a bit more.
So in that application, I have a controller which renders a template like this:
Listing videos
<%= for video <- @videos do %> <td class="text-right">
<%= link "Show", to: video_path(@conn, :show, video), class: "btn btn-default btn-xs" %>
<%= link "Edit", to: video_path(@conn, :edit, video), class: "btn btn-default btn-xs" %>
<%= link "Delete", to: video_path(@conn, :delete, video), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %>
</td>
</tr>
<% end %>
| User | Url | Title | Description | Category | |
|---|---|---|---|---|---|
| <%= video.user_id %> | <%= video.url %> | <%= video.title %> | <%= video.description %> | <%= if category = video.category, do: category.name %> |
<%= link “New video”, to: video_path(@conn, :new) %>
I added this line to render the categories <%= if category = video.category, do: category.name %>
This is my controller
def index(conn, _params, user) do
videos = Repo.all(user_videos(user)) |> Repo.preload(:category)
render(conn, “index.html”, videos: videos)
end
defp user_videos(user) do
# query user videos
assoc(user, :videos)
end
And when I run a test I wrote, this one:
test “renders index.html”, %{conn: conn} do
videos = [%Rumbl.Video{id: “1”, title: “dogs”},
%Rumbl.Video{id: “2”, title: “cats”}]
content = render_to_string(Rumbl.VideoView, “index.html”,
conn: conn, videos: videos)
assert String.contains?(content, "Listing videos")
for video <- videos do
assert String.contains?(content, video.title)
end
end
It complains with categories not being preloaded
** (KeyError) key :name not found in: #Ecto.Association.NotLoaded
I also tried this:
defp user_videos(user) do
query = (from u in user, select: u.videos, preload: [:category])
Repo.all(query)
end
But not working neither, so how can I preload them?
I thought I just needed to preload them at the pipe with |> Repo.preload(:category)
Also the strange thing is this started to crash with test, without the test it renders the index.html template without any issue
Marked As Solved
dom
It doesn’t matter what you do in the controller, since this test tests the view, not the controller.
videos = [%Rumbl.Video{id: "1", title: "dogs"}, %Rumbl.Video{id: "2", title: "cats"}]
You need to preload the category for these videos (in the test itself). Here that could mean just hardcoding it like:
videos = [%Rumbl.Video{id: "1", title: "dogs", category: %Rumbl.Category{name: “Animals”}}, ...]
Also Liked
axelson
I have a little helper function that might be useful to you:
def clear_associations(%{__struct__: struct} = schema) do
struct.__schema__(:associations)
|> Enum.reduce(schema, fn association, schema ->
%{schema | association => build_not_loaded(struct, association)}
end)
end
defp build_not_loaded(struct, association) do
%{
cardinality: cardinality,
field: field,
owner: owner
} = struct.__schema__(:association, association)
%Ecto.Association.NotLoaded{
__cardinality__: cardinality,
__field__: field,
__owner__: owner
}
end
With that you could write a assert_matches_without_associations function. Just call clear_associations/1 on each input and then assert equality.
Although (in line with what @LostKobrakai is saying) I actually use this assert_ids_match/2 helper more:
@doc """
Helper for checking that for two structs, or two lists of structs have the
same id keys
"""
def assert_ids_match(list1, list2) when is_list(list1) and is_list(list2) do
list1_ids =
list1
|> Enum.map(& &1.id)
|> Enum.sort()
list2_ids =
list2
|> Enum.map(& &1.id)
|> Enum.sort()
assert list1_ids == list2_ids
end
def assert_ids_match(%{id: id1}, %{id: id2}) do
assert id1 == id2
end
LostKobrakai
Maybe this can shed some light:
jc00ke
Awesome, thanks for the code! I’ll probably head the assert_ids_match route soon; it’s good enough for me.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #podcasts
- #code-sync
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








