majesticlabs-dev/majestic-marketplace

rails-activity-timeline

Use when adding polymorphic activity timelines with live Turbo Stream updates to any Rails model.

Ver código fuente
Documento original del Skill

Contenido del repositorio de origen con títulos, ejemplos, código, tablas, enlaces e imágenes preservados.

Rails Activity Timeline

Add a polymorphic activity timeline with live Turbo Stream updates to any Rails model. Track field changes, status transitions, comments, attachments, and more with configurable icons and colors per action type.

When to Use

  • Adding an activity/audit timeline to any Rails model
  • Tracking field changes, status transitions, comments, or attachments
  • Displaying live-updating activity feeds via Turbo Streams
  • Auto-logging lifecycle events on child/associated models
  • Building activity feeds with configurable icons and colors

Architecture

Four components:

  1. `ActivityEvent` model (polymorphic) — the core event record
  2. `ActivityTrackable` concern — auto-logs child model lifecycle events on a parent's timeline
  3. Shared timeline partial — vertical timeline with Turbo Stream live updates
  4. Display configuration — icon SVG path + accent color per action type, kept in the model

Key Associations (on ActivityEvent)

ruby
belongs_to :trackable, polymorphic: true            # parent entity whose timeline this belongs to
belongs_to :subject, polymorphic: true, optional: true  # related entity being acted upon
belongs_to :user, optional: true                     # who performed the action

Generic Action Set

ruby
ACTIONS = %w[
  created updated destroyed
  field_updated status_changed
  comment_added
  attachment_added attachment_removed
  assigned unassigned
  relationship_added relationship_removed
  tag_added tag_removed
].freeze

Add domain-specific actions (e.g., published, approved) to ACTIONS and the DISPLAY hash.

Core Patterns

Setup from Scratch

Migration, full ActivityEvent model, ActivityTrackable concern, and prerequisites.

See: references/setup.md

Turbo Stream Broadcasting

Stream naming convention, broadcast methods, partial routing, shared timeline partial, and event partial.

See: references/broadcasting.md

Display Configuration

The DISPLAY hash, adding custom actions with icons, display methods, customizing the event partial.

See: references/display.md

AI Summaries (Optional)

AI-generated change summaries for field_updated events with long text changes.

See: references/ai-summaries.md

Adding Timeline to a Model

Step 1: Add the association

ruby
class Project < ApplicationRecord
  has_many :activity_events, as: :trackable, dependent: :destroy
end

Step 2: Add model type to broadcast routing

In ActivityEvent#broadcast_partial, add your model to the case statement:

ruby
def broadcast_partial
  case trackable_type
  when "Project", "Article" then "activity_events/activity_event"
  else "activity_events/activity_event"
  end
end

Step 3: Render the shared partial

erb
<%= render "shared/activity_timeline", record: @project %>

Step 4: Create events

ruby
# Status change
ActivityEvent.create!(
  trackable: @project,
  user: current_user,
  action: "status_changed",
  details: { from_status: "draft", to_status: "active" }
)

# Field change
if @project.saved_change_to_status?
  ActivityEvent.create!(
    trackable: @project,
    user: current_user,
    action: "field_updated",
    details: { field: "status", from: @project.status_previously_was, to: @project.status }
  )
end

ActivityTrackable Concern

For child models that auto-log on a parent's timeline:

ruby
class Comment < ApplicationRecord
  include ActivityTrackable

  def activity_trackable = post
  def activity_action_created = "comment_added"
  def activity_action_destroyed = "comment_removed"
  def activity_label = body.truncate(60)
  def activity_user = user
end

Override activity_created_details or activity_destroyed_details for additional metadata:

ruby
def activity_created_details
  { file_size: byte_size, content_type: content_type }
end

Querying

ruby
@project.activity_events.timeline          # recent, includes user, limit 50
@project.activity_events.by_type("status_changed")
ActivityEvent.where(subject: @task).recent

Common Pitfalls

  1. Wrong trackable — events appear on wrong timeline. activity_trackable must return the parent, not self.
  1. Missing broadcast routing for new types — add new model types to the broadcast_partial case statement; without this broadcasts silently fail.
  1. Missing actions in ACTIONS + DISPLAY — validation rejects unrecognized actions. Always add both.
  1. N+1 on timeline — always use .timeline scope (includes :user). Add associations to the scope, not the partial.
  1. Turbo Stream not updatingturbo_stream_from tag in view must match stream name in broadcast method. Convention: "#{record_type}_{id}_activity".

Prerequisites

  • Rails 7+ with turbo-rails
  • Action Cable or Solid Cable configured
  • A User model (optional, for attribution)
  • Tailwind CSS for default styling
del mismo repositorio

Más Skills

Todos los Skills