lapinkoira

lapinkoira

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

Showing Posts 1 to 10

dom

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”}}, ...]
lapinkoira

lapinkoira OP

Thanks that works, is there a way to make it more elegant handling it at the template? like “if video.category is nil dont ask for it”

jc00ke

jc00ke

I’m wondering this as well. It’s becoming a bear to preload associations. I’m close to writing a assert_without_assoc function :wink:

LostKobrakai

LostKobrakai

There’s Ecto.assoc_loaded?

jc00ke

jc00ke

Thanks, but that’s not quite what I’m looking for.

This is more of what I’m looking for (or at least what I think I want :wink:)

user = %User{id: "foo", widgets: [])
u = Users.get_user!(user.id) # this won't have widgets preloaded
# %User{id: "foo", widgets: Ecto.Association.NotLoaded< :widgets is not loaded>}
assert user == u

# or maybe I need to write
assert_without_assoc user == u
# where this will ignore fields where the assoc is not loaded.
LostKobrakai

LostKobrakai

Maybe this can shed some light:

jc00ke

jc00ke

Word, that makes sense, except that’s not how the test code from mix phx.gen.html|context is generated. In a pinch I compare ids, but that doesn’t feel right.

LostKobrakai

LostKobrakai

That code is meant as a starting point to get people going and not to be some holy grail of how things are to be done. Their focus lies quite a bit more on being accessable to people new to the framework.

That I talk about in the linked post: Compare IDs if you want to assert on the identity (Is it the same thing?). Compare fields to each other if you want to make sure creating / updating works correctly for them.

axelson

axelson

Scenic Core Team

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
jc00ke

jc00ke

Awesome, thanks for the code! I’ll probably head the assert_ids_match route soon; it’s good enough for me.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
rlipscombe
I’d like to create a file with 600 permissions (rw-------), but there’s a race condition if I create it and then change the permissions. ...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
rlipscombe
When I use Mix.install with a git dependency, using git@github.com:... how do I change the SSH identity used? I’ve got two Github accoun...
New
adr
Hello all. I have a question as to best practices for code organization when implementing a liveview with a lot of responsibilities. Sup...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews