Back to Skills

vanilla-rails-models

verified

Use when writing Rails models - enforces state-as-records not booleans, concerns as adjectives namespaced under model, invocation ordering, and private indentation

View on GitHub

Marketplace

zemptime-marketplace

ZempTime/zemptime-marketplace

Plugin

vanilla-rails

Repository

ZempTime/zemptime-marketplace
1stars

vanilla-rails/skills/models/SKILL.md

Last Verified

January 20, 2026

Install Skill

Select agents to install to:

Scope:
npx add-skill https://github.com/ZempTime/zemptime-marketplace/blob/main/vanilla-rails/skills/models/SKILL.md -a claude-code --skill vanilla-rails-models

Installation paths:

Claude
.claude/skills/vanilla-rails-models/
Powered by add-skill CLI

Instructions

# Vanilla Rails Models

Rich domain models with concerns, following production Basecamp/37signals patterns. Decompose with concerns, not services.

## State as Separate Records (NOT Booleans)

**Critical pattern:** Don't use boolean columns for state. Create state records that capture who/when.

```ruby
# ❌ BAD - boolean column
class AddStarredToCards < ActiveRecord::Migration[7.0]
  def change
    add_column :cards, :starred, :boolean, default: false
    add_column :cards, :starred_at, :datetime
  end
end

class Card < ApplicationRecord
  def star
    update(starred: true, starred_at: Time.current)
  end
end

# ✅ GOOD - state record
class CreateStars < ActiveRecord::Migration[7.0]
  def change
    create_table :stars, id: :uuid do |t|
      t.references :card, null: false, foreign_key: true, type: :uuid
      t.references :user, null: false, foreign_key: true, type: :uuid
      t.timestamps
    end
  end
end

class Star < ApplicationRecord
  belongs_to :card
  belongs_to :user
end

class Card < ApplicationRecord
  has_one :star, dependent: :destroy

  def star(user: Current.user)
    create_star!(user: user) unless starred?
  end

  def starred?
    star.present?
  end
end
```

**Why state records:**
- Captures who (user) and when (created_at) automatically
- Can add metadata later without altering main table
- Explicit presence/absence vs true/false ambiguity
- Database enforces referential integrity

**Use has_one for binary state, has_many for multi-user:**

```ruby
# Binary state (one per item) - use has_one
has_one :closure    # card is either closed or not
has_one :triage     # card is either triaged or not

# Multi-user actions - use has_many
has_many :pins      # multiple users can pin
has_many :watches   # multiple users can watch
has_many :assignments
```

**Common rationalizations to reject:**

| Excuse | Reality |
|--------|---------|
| "Boolean is simpler" | State records capture metadata you'll need later |
| "Just a flag" | Today's flag is tomorrow's

Validation Details

Front Matter
Required Fields
Valid Name Format
Valid Description
Has Sections
Allowed Tools
Instruction Length:
7663 chars

Issues Found:

  • name_directory_mismatch