Adding live blog URL links to the post dashboard
It’s a small thing but I kept running into it: the /posts dashboard lists all my blog posts, and every time I want to read one on the actual blog, I have to manually navigate to it. The titles were just plain text. The natural action, clicking the title, did nothing useful.
The fix sounds obvious: make the title a link. But there’s a wrinkle. The URL pattern isn’t the same for every blog. My blogs table already had a base_url column (https://vdaluz.com), but the path between the domain and the slug, /blog/ for vdaluz.com, was hardcoded in the show page partial. That worked for one blog but would break the moment I added another blog with a different URL structure.
So I needed to make that path segment configurable.
What I built
Added a post_path_prefix column to blogs; a string like /blog that sits between base_url and the post slug. Then Post#live_url assembles the full URL:
def live_url
return nil if blog.base_url.blank? || pub_date.blank?
"#{blog.base_url.chomp('/')}#{blog.post_path_prefix}/#{slug}"
end
The pub_date gate is intentional. Drafts and posts with no publish date don’t have a live URL yet; showing a broken link is worse than no link.
The migration backfills existing rows to /blog so the current setup keeps working without any manual data fix:
reversible do |dir|
dir.up { Blog.update_all(post_path_prefix: "/blog") }
end
On the index, published post titles now link out with target="_blank" rel="noopener". Drafts fall back to the internal show page. The show page’s URL badge, which was previously hardcoded as "#{post.blog.base_url}/blog/#{post.slug}", now uses post.live_url instead, so there’s one source of truth for the URL shape.
The thing I almost missed
The issue spec said to add a live_url column to blogs. But base_url already existed and did the same job. Adding live_url would have been a redundant field with no clear owner. I scrapped that part of the spec and kept the existing column; the post_path_prefix addition was all that was actually needed.
Result
Click a published post title, land on the live post. Takes about 2 seconds less mental effort than it used to. Not glamorous, but this is the kind of thing that makes a tool feel like it was built for you.
Related reading
The normalization bug that only shows up on tags made of nothing
A strip-based normalizer meets an all-punctuation tag: empty string as a hash key, wrong-tag substitution, and an autocomplete that matches everything. Three symptoms, one root cause.
The same button choice cost me a bigger bug than expected
Embedding the hero flow in the editor looked like the smaller option - until 'replace' met 166 real files that had never gone through the insertion-only path, and a migration with no backfill.
The editor commit button is a deploy button
Committing a draft to main auto-deploys the blog. Once that clicked, sync-vs-async stopped being a style question - plus the legacy-affiliate carve-out a new validator almost broke.