From vanilla-rails
Use when designing database schema, writing migrations, or making data storage decisions - enforces UUIDs, account_id multi-tenancy, no foreign keys, and proper index patterns
How this skill is triggered — by the user, by Claude, or both
Slash command
/vanilla-rails:data-modelingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Database schema conventions from production 37signals patterns.
Database schema conventions from production 37signals patterns.
For state-as-records pattern details, see vanilla-rails-models.
All tables use UUIDs. No auto-incrementing integers.
create_table :cards, id: :uuid do |t|
t.uuid :account_id, null: false
t.string :title
t.timestamps
end
UUIDv7 (timestamp-ordered), base36 encoded as 25-character strings. No ID enumeration, merge-safe, no sequence contention.
Every tenant-scoped table has account_id. No exceptions for user data.
Tables WITHOUT account_id (global/cross-tenant): identities, sessions, magic_links
Scope queries via Current.account:
class ApplicationRecord < ActiveRecord::Base
def self.default_scope
where(account_id: Current.account.id) if Current.account
end
end
Don't forget account_id on join tables.
Use application-level integrity, not database constraints.
# Bad
t.references :card, foreign_key: true
# Good
t.uuid :card_id, null: false
add_index :table, :card_id
Prevents deadlocks during bulk operations. Maintain integrity via dependent: :destroy.
| Pattern | Rule |
|---|---|
| Composite indexes | Lead with account_id |
| Polymorphic | Always [type, id] |
| Binary state | unique: true on parent_id |
| Per-user state | unique: [parent_id, user_id] |
| Tenant uniqueness | [:account_id, :field] |
add_index :cards, [:account_id, :status]
add_index :events, [:eventable_type, :eventable_id]
add_index :closures, :card_id, unique: true
add_index :pins, [:card_id, :user_id], unique: true
| Need | Pattern | Has ID? | Has account_id? |
|---|---|---|---|
| Just link two things | HABTM (id: false) | No | No |
| Track when/who linked | has_many :through | Yes (id: :uuid) | Yes |
HABTM naming: plural_plural alphabetically (boards_filters)
Through naming: Singular noun (taggings, assignments)
# HABTM - no metadata needed
create_table :boards_filters, id: false do |t|
t.uuid :board_id, null: false
t.uuid :filter_id, null: false
end
# Through - timestamps, account scoping
create_table :taggings, id: :uuid do |t|
t.uuid :account_id, null: false
t.uuid :card_id, null: false
t.uuid :tag_id, null: false
t.timestamps
end
Use semantic names describing the relationship:
| Name | Meaning |
|---|---|
eventable | thing the event is about |
source | where it came from |
container | what holds it |
searchable | what is searchable |
recordable | what it's attached to |
Manual increment!/decrement!, not Rails counter_cache: option:
after_create :increment_account_counter
private
def increment_account_counter
account.increment!(:cards_count)
end
Polymorphic config with inheritance fallback:
class Board < ApplicationRecord
def auto_postpone_period
entropy&.auto_postpone_period || account.auto_postpone_period
end
end
| Rule | Example |
|---|---|
Prefer change | def change; add_column ...; end |
| Explicit UUID refs | t.uuid :card_id not t.references :card |
| Large table indexes | add_index :table, :col, algorithm: :concurrently |
up/down only when irreversible | remove_column in up |
| Decision | Pattern |
|---|---|
| Primary key | id: :uuid always |
| Tenant column | account_id on all tenant tables |
| Foreign keys | None — app-level integrity |
| Simple join | id: false, no account_id |
| Rich join | id: :uuid, with account_id |
| Polymorphic index | [type, id] compound |
| Query index | Lead with account_id |
| Counter cache | Manual increment! |
For large tables, shard by account using CRC32:
def shard_for(account_id)
Zlib.crc32(account_id.to_s) % 16
end
16 identical tables, MySQL native fulltext across shards.
npx claudepluginhub zemptime/zemptime-marketplace --plugin vanilla-railsReviews and designs database schemas with normalization (1NF-BCNF), denormalization, multi-tenancy patterns, PK strategies (UUID v7, ULID, KSUID), soft deletes, temporal tables (SCD), audit trails, evolution, naming, data types, anti-patterns. For new schemas, reviews, migrations.
Designs relational and NoSQL database schemas including normalization, indexing, migrations, temporal modeling, audit trails, and partitioning strategies.
Designs database schemas, indexing strategies, query optimization, and migration patterns for SQL and NoSQL databases. Use for schema design, N+1 fixes, normalization, and performance tuning.