Skip to content

feat(views): replace chapters sidebar with ViewComponent#2523

Merged
mroderick merged 2 commits intocodebar:masterfrom
mroderick:feature/chapters-sidebar-v3
Mar 18, 2026
Merged

feat(views): replace chapters sidebar with ViewComponent#2523
mroderick merged 2 commits intocodebar:masterfrom
mroderick:feature/chapters-sidebar-v3

Conversation

@mroderick
Copy link
Collaborator

@mroderick mroderick commented Mar 14, 2026

Summary

  • Replaces the inline HAML chapters sidebar on the homepage with a reusable ViewComponent
  • Uses Rails fragment caching (cache "chapters-sidebar") for performance
  • Adds cache expiration callbacks to Chapter model (after_create_commit, after_update_commit, after_destroy_commit)
  • Adds component specs for the ViewComponent
  • Adds view specs for the dashboard show page
  • Adds cache expiration tests to chapter_spec.rb
  • Fixes broken use_transactional_fixtures check in spec_helper.rb

Caching Strategy

How it works

The component uses Rails fragment caching to cache the rendered HTML for the chapters sidebar:

<% cache "chapters-sidebar" do %>
  <!-- rendered HTML is cached -->
<% end %>

This caches the entire sidebar HTML fragment under the key "chapters-sidebar". On subsequent requests, Rails serves the cached fragment directly from the cache store (typically Redis in production), bypassing view rendering entirely.

Cache Invalidation

The cache is automatically invalidated when Chapters are created, updated, or deleted via Active Record callbacks in Chapter model:

after_update_commit :expire_chapters_sidebar_cache
after_create_commit :expire_chapters_sidebar_cache
after_destroy_commit :expire_chapters_sidebar_cache

def expire_chapters_sidebar_cache
  Rails.cache.delete('chapters-sidebar')
end

Using *_commit callbacks (rather than *_save/*_destroy) ensures the cache is only expired after the database transaction successfully commits, preventing stale reads.

Trade-offs

Pros:

  • Fast page loads - sidebar is served from cache on every request
  • Simple implementation - uses Rails' built-in fragment caching

Cons:

  • All chapters share one cached fragment - adding/editing any chapter invalidates the entire sidebar
  • Cache stampede potential if many requests hit while cache is cold/warming

Alternatives considered

  • Cache per chapter: Would add complexity with more cache keys to manage
  • Russian doll caching: Nested caching per-chapter, more complex to implement
  • HTTP caching: Not appropriate here as content is dynamic per-user

For the current scale of ~10 chapters, a single cached fragment is the simplest effective solution.

Changes

  • app/components/chapters_sidebar_component.rb - New ViewComponent
  • app/components/chapters_sidebar_component.html.erb - ERB template with caching
  • app/models/chapter.rb - Added cache expiration callbacks
  • app/views/dashboard/show.html.haml - Use component instead of inline HAML
  • spec/components/chapters_sidebar_component_spec.rb - Component tests
  • spec/views/dashboard/show.html.haml_spec.rb - View integration tests
  • spec/models/chapter_spec.rb - Cache expiration tests
  • spec/rails_helper.rb - ViewComponent test configuration
  • Gemfile - Added view_component gem

@mroderick mroderick requested a review from olleolleolle March 14, 2026 13:40
Copy link
Collaborator

@olleolleolle olleolleolle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks nice!


class ChaptersSidebarComponent < ViewComponent::Base
def initialize(chapters:, title: nil)
super()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't it work without this one?

require 'rails_helper'
require 'view_component/test_helpers'

RSpec.describe ChaptersSidebarComponent, type: :component do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Connect ViewComponent to RSpec, adding RSpec metadata type: :component.
#
# This extends Rails' own built-in (e.g. "type: :controller") metadata system.
RSpec::Rails::DIRECTORY_MAPPINGS[:component] = %w[ spec components ]

Something like this, in the rails_helper would auto-add the type: :component by file location.

in RSpec.config we'd need:

  # Automatically tag specs in conventional directories with matching type
  # metadata so that they have relevant helpers available to them.
  # See RSpec::Rails::DIRECTORY_MAPPINGS for details on which metadata is
  # applied to each directory.
  config.infer_spec_type_from_file_location!

- Add ChaptersSidebarComponent with Rails fragment caching
- Add cache expiration callbacks to Chapter model
- Create component and view specs
- Fix broken use_transactional_fixtures check in spec_helper.rb
@mroderick
Copy link
Collaborator Author

mroderick commented Mar 18, 2026

Thank you for the review @olleolleolle! I've addressed both of your suggestions:

  1. Removed - You're right, ViewComponent::Base doesn't define initialize, so it's unnecessary. I added a rubocop:disable comment to silence the warning.

  2. Added DIRECTORY_MAPPINGS - I've added to rails_helper.rb so that specs in spec/components/ automatically get the metadata inferred. This lets us remove the explicit from the spec file.

Appreciate the careful review!

@mroderick mroderick force-pushed the feature/chapters-sidebar-v3 branch from 985d5e3 to 15b191e Compare March 18, 2026 07:06
…ping for components

Based on review feedback from olleolleolle:
- Remove unnecessary super() call from ViewComponent initialize
- Add RSpec::Rails::DIRECTORY_MAPPINGS to auto-infer type: :component
- Remove explicit type: :component from component spec (now inferred)
@mroderick mroderick force-pushed the feature/chapters-sidebar-v3 branch from 15b191e to 1070aed Compare March 18, 2026 07:08
@mroderick mroderick requested a review from olleolleolle March 18, 2026 07:08
Copy link
Collaborator

@olleolleolle olleolleolle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for introducing and using ViewComponent, it can be pretty handy.

@mroderick mroderick merged commit 2550a4c into codebar:master Mar 18, 2026
7 checks passed
@mroderick mroderick deleted the feature/chapters-sidebar-v3 branch March 18, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants