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
No Comments