Showing comment counts on the index page (Simple Phoenix LiveView App)

This screencast goes over how to get comment counts for each podcast (or blog post, etc) and show them on the listing page.

One way to do this would be to preload all the comments. That's good for a show page for a single podcast, but it's not very efficient to load every comment on every podcast for the index page.

Instead, we'll add a virtual field comment_count to the podcast schema and then write a more complex Ecto query that will retrieve comment counts but not preload each individual comment.

A virtual field

First, add a virtual field to the podcast schema for comment_count:

schema "podcasts" do
  field :audio_url, :string
  field :comment_count, :integer, default: 0, virtual: true
  field :is_published, :boolean, default: false
  field :notes_html, :string
  field :notes_md, :string
  field :subtitle, :string
  field :title, :string
  has_many(:comments, Reactor.Content.Comment)

  timestamps()
end

The field is virtual since we don't need to store anything in the database for it and it will default to zero so we can depend on it being an integer.

A view helper

When we display the number of comments on a podcast, we want a nicely formatted string that handles singular and plural numbers in a reasonable way. To do that, we'll create a helper function in podcast_view.ex:

defmodule ReactorWeb.PodcastView do
  use ReactorWeb, :view

  def comment_count_str(n) do
    case n do
      0 -> "(no comments)"
      1 -> "(1 comment)"
      n -> "(#{n} comments)"
    end
  end
end

Then, instead of calling to_string on whatever integer value is in a comment count field, we can just call comment_count_str in our template and have a reasonable display for the number of comments.

Listing podcasts with comment counts

In our context module, we'll need a more complex list_podcasts function in order to populate the virtual comments_count field in each podcast.

  def list_podcasts do
    comment_counts = get_comment_counts()

    Podcast
    |> order_by(desc: :id)
    |> Repo.all()
    |> Enum.map(& %{&1 | comment_count: comment_counts[&1.id] || 0})
  end

  def get_comment_counts do
    from(p in Podcast,
      join: c in assoc(p, :comments),
      group_by: p.id,
      select: {p.id, count(c.id)}
    )
    |> Repo.all()
    |> Enum.into(%{})
  end

This is a three-step process. First we create a mapping of podcast IDs to comment counts, e.g. %{1 => 3, 2 => 4, 3 => 4}. Then, we retrieve the podcast listing without comments. Finally we map over the podcasts and update their comment_counts, using the mapping from the first step as a look-up table.

(Source code available for premium members)

Next episode: Adding comments

Back to index

No Comments