DEV Community

Cover image for dev.to's Dashboard Can't Count Its Own Posts
Daniel Nwaneri
Daniel Nwaneri Subscriber

Posted on

dev.to's Dashboard Can't Count Its Own Posts

Summer Bug Smash: Clear the Lineup 🐛🛹

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.

Project Overview

forem is the open source platform behind dev.to itself. I've had it starred and cloned for months and never opened the codebase — it's Rails, and I don't write Ruby. Jess's post was the reason that finally changed.

github.com/forem/forem

Bug Fix or Performance Improvement

#23687 is a one-line report: a user published exactly one post, and the dashboard's "Posts" counter said 2.

Not writing Ruby meant I couldn't guess my way to the fix from vibes. I had to actually trace it — reading DashboardsController, the sidebar partials, and the Article model until the shape of the bug was undeniable, not assumed.

The "Posts" badge in the dashboard sidebar renders @user.articles_count — a counter_culture cache on User that increments for every Article row belonging to that user, full stop. No filter on type, no filter on state:

# app/models/article.rb
counter_culture :user
Enter fullscreen mode Exit fullscreen mode

But the link that badge sits on always opens the same default view: DashboardsController#show with no params. That view only lists non-archived, full-post-type articles:

# app/controllers/dashboards_controller.rb
@articles = target.articles.from_subforem.includes(:organization)
@articles = params[:state] == "status" ? @articles.statuses : @articles.full_posts
@show_archived = params[:filter].to_s.casecmp("archived").zero?
Enter fullscreen mode Exit fullscreen mode

Forem has three article types — full_post, status (a short "Boost" update), and fullscreen_embed — and the counter doesn't distinguish between them, or between archived and active. The badge counts everything. The list under it shows a strict subset. Anyone who's ever posted a status update, or archived a post, sees a number that doesn't match what they can actually click into and see — exactly what got reported in #23687.

I couldn't verify that in Ruby, but I recognized the shape of it instantly once it was laid out: a cached count drifting from what a filtered view actually renders. I've shipped that exact bug in JavaScript. Same failure, different syntax.

Code

github.com/forem/forem/pull/23690

The fix doesn't touch the shared articles_count counter — that cache is read elsewhere for badges and spam heuristics, where "every article this user has ever made" is the correct meaning. Instead, DashboardsController gets a helper scoped to match what the Posts tab actually renders, and both the full-page and AJAX sidebar actions use it instead of the raw cache:

# The "Posts" nav item always links to the default (non-archived, full posts
# only) view of the user's own dashboard, so its indicator should reflect
# that same scope rather than the user's raw articles_count, which also
# includes statuses and archived posts that never show up in that list.
def posts_count_for(user)
  user.articles.from_subforem.full_posts.where(archived: false).count
end
Enter fullscreen mode Exit fullscreen mode

My Improvements

There was no way for me to eyeball this and trust it — I can't read Ruby well enough for that, and there's no Ruby or Postgres on the machine I was working from, so I couldn't run the spec suite locally either. Verification had to happen somewhere else: I wrote regression specs asserting a user with one full post, one status, and one archived post should see a count of exactly 1, pushed the branch, and let Forem's own CI be the judge instead of my own confidence.

CI caught something real on the first run — not in the fix, in my test. create(:article, type_of: "status") failed its own model validation, because status-type articles in Forem aren't allowed to have body markdown, and the factory's default does. I found the pattern already used elsewhere in the suite (body_markdown: "", main_image: nil), fixed the two specs, and pushed again.

That failure is the actual proof this wasn't guesswork dressed up as a fix. If I'd been able to run specs locally I might have caught it before pushing; instead the project's own CI did the job a local run would have.

Same lesson my other two entries kept landing on: The Cloudflare Worker That Ran Perfectly and Still Failed Twice and I Was Filming a Demo of My Monitoring Tool. The Monitor Wasn't Monitoring. — "it compiled" and "it's correct" are different claims, and only one of them is worth trusting.

Everything's green now — 19 successful checks, 1 skipped, 0 failures, including the shard that runs dashboard_spec.rb. The PR is open against forem/forem and waiting on a maintainer review, since third-party fork PRs need one before merge. Not merged yet as of writing this — I'd rather say that plainly than imply otherwise.


Different from my other two entries in one way: I don't write Ruby. Claude found the bug and wrote the fix. I picked the issue and gated everything that left my machine — the fork, the push, the PR, the CLA. Full delegation on the code, not on whether it shipped.

Top comments (3)

Collapse
 
sylwia-lask profile image
Sylwia Laskowska

Great post! I'd love to contribute something to Forem one day as well!

Also... those submissions are way too good. I was counting on winning that skateboard! 😂

Collapse
 
dannwaneri profile image
Daniel Nwaneri

Sylwia, do it . mine sat starred and uncloned for months before I actually opened it. The hardest part isn't the code, it's the opening. (And honestly, I'd trade the skateboard for the merge . still waiting on that part.)

Collapse
 
hemapriya_kanagala profile image
Hemapriya Kanagala

I don't know Ruby either, so this was nice to read 😄 I'd love to contribute to Forem one day too, but the Ruby codebase has always made me a little hesitant to try.

Hoping your PR gets merged, Daniel! Great work on this one.