askill
vanilla-rails-data-modeling

vanilla-rails-data-modelingSafety 95Repository

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

2 stars
1.2k downloads
Updated 2/14/2026

Package Files

Loading files...
SKILL.md

Vanilla Rails Data Modeling

Database schema conventions from production 37signals patterns.

For state-as-records pattern details, see vanilla-rails-models.

UUID Primary Keys

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.

Multi-Tenancy via account_id

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.

No Foreign Key Constraints

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.

Index Strategy

PatternRule
Composite indexesLead with account_id
PolymorphicAlways [type, id]
Binary stateunique: true on parent_id
Per-user stateunique: [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

Join Table Patterns

NeedPatternHas ID?Has account_id?
Just link two thingsHABTM (id: false)NoNo
Track when/who linkedhas_many :throughYes (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

Polymorphic Associations

Use semantic names describing the relationship:

NameMeaning
eventablething the event is about
sourcewhere it came from
containerwhat holds it
searchablewhat is searchable
recordablewhat it's attached to

Counter Caches

Manual increment!/decrement!, not Rails counter_cache: option:

after_create :increment_account_counter

private
  def increment_account_counter
    account.increment!(:cards_count)
  end

Settings Tables

Polymorphic config with inheritance fallback:

class Board < ApplicationRecord
  def auto_postpone_period
    entropy&.auto_postpone_period || account.auto_postpone_period
  end
end

Migration Conventions

RuleExample
Prefer changedef change; add_column ...; end
Explicit UUID refst.uuid :card_id not t.references :card
Large table indexesadd_index :table, :col, algorithm: :concurrently
up/down only when irreversibleremove_column in up

Quick Reference

DecisionPattern
Primary keyid: :uuid always
Tenant columnaccount_id on all tenant tables
Foreign keysNone — app-level integrity
Simple joinid: false, no account_id
Rich joinid: :uuid, with account_id
Polymorphic index[type, id] compound
Query indexLead with account_id
Counter cacheManual increment!

Sharding (Advanced)

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.

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

91/100Analyzed 2/19/2026

High-quality technical reference skill for Rails data modeling. Comprehensive coverage of UUIDs, multi-tenancy, indexing, join tables, and migrations with clear code examples and tables. Well-structured with a clear "when to use" trigger in the description. Located in a dedicated skills folder with appropriate tags. Slightly deducts for being reference-style rather than step-by-step, but the density and accuracy of the content is excellent.

95
95
90
92
85

Metadata

Licenseunknown
Version-
Updated2/14/2026
PublisherZempTime

Tags

database