Claude
Skills
Sign in
Back

rails-activity-timeline

Included with Lifetime
$97 forever

Use when adding polymorphic activity timelines with live Turbo Stream updates to any Rails model. Covers migration, model, concern, shared partials, broadcasting, and optional AI-generated change summaries. Triggers on activity feed, audit trail, timeline, field change tracking, or Turbo Stream broadcasting patterns.

Security

What this skill does


# 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`.

2. **Missing broadcast routing for new types** — add new model types to the `broadcast_partial` case statement; without this broadcasts silently fail.

3. **Missing actions in ACTIONS + DISPLAY** — validation rejects unrecognized actions. Always add both.

4. **N+1 on timeline** — always use `.timeline` scope (includes `:user`). Add associations to the scope, not the partial.

5. **Turbo Stream not updating** — `turbo_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

Related in Security