thibautbaissac/rails_ai_agents/model-patterns

Claude Code and agent skill by thibautbaissac/rails_ai_agents

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Builds rich domain models with business logic, concerns, and proper associations following the fat-models-over-service-objects philosophy. Use when creating models, adding validations, scopes, callbacks, business logic methods, or associations. WHEN NOT: Controller/routing work (use crud-patterns), concern extraction (use concern-patterns), state record design (use state-records).

Install

npx vskill@latest install thibautbaissac/rails_ai_agents/model-patterns

SKILL.md

Model Patterns (37signals)

Rich domain models over service objects. Business logic lives in models, not in separate service classes.

Project knowledge

Tech Stack: Rails 8.2 (edge), UUIDs everywhere, database-backed everything (no Redis) Patterns: Heavy use of concerns, default values via lambdas, Current for context

Commands:

bash
bin/rails generate model Card title:string body:text board:references:uuid
bin/rails generate migration AddColorToCards color:string
bin/rails db:migrate
bin/rails test test/models/
bin/rails console

Rich model vs service object

ruby
# BAD -- service object
class CloseCardService
def initialize(card, user)
@card = card
@user = user
end
def call
ActiveRecord::Base.transaction do
@card.create_closure!(user: @user)
@card.track_event("card_closed", user: @user)
end
end
end
# GOOD -- rich model
class Card < ApplicationRecord
include Closeable
def close(user: Current.user)
create_closure!(user: user)
track_event "card_closed", user: user
notify_recipients_later
end
end
# Controller simply calls:
@card.close

Model structure

Order within a model:

ruby
class Card < ApplicationRecord
# 1. Concern includes
include Assignable, Closeable, Eventable, Searchable, Watchable
# 2. Associations
belongs_to :account, default: -> { board.account }
belongs_to :board, touch: true
belongs_to :column, touch: true
belongs_to :creator, class_name: "User", default: -> { Current.user }
has_many :comments, dependent: :destroy
has_many :assignments, dependent: :destroy
has_one :closure, dependent: :destroy
# 3. Validations
validates :title, presence: true
validates :status, inclusion: { in: %w[draft published archived] }
# 4. Enums
enum :status, { draft: "draft", published: "published", archived: "archived" }, default: :draft
# 5. Scopes
scope :recent, -> { order(created_at: :desc) }
scope :positioned, -> { order(:position) }
scope :active, -> { open.published.where.missing(:not_now) }
# 6. Delegations
delegate :name, to: :board, prefix: true, allow_nil: true
# 7. Callbacks (sparingly)
after_create_commit :broadcast_creation
# 8. Business logic methods
def publish
update!(status: :published)
track_event "card_published"
end
def move_to_column(new_column)
update!(column: new_column)
track_event "card_moved", particulars: {
from_column_id: column_id_before_last_save,
to_column_id: new_column.id
}
end
private
def broadcast_creation
broadcast_prepend_to board, :cards, target: "cards", partial: "cards/card"
end
end

Association patterns

belongs_to with defaults
ruby
belongs_to :account, default: -> { board.account }
belongs_to :creator, class_name: "User", default: -> { Current.user }
belongs_to :board, touch: true # Updates parent's updated_at
has_many / has_one
ruby
has_many :comments, dependent: :destroy
has_many :assignees, through: :assignments, source: :assignee
has_one :closure, dependent: :destroy
Polymorphic
ruby
has_many :attachments, as: :attachable, dependent: :destroy
has_many :events, as: :eventable, dependent: :destroy
belongs_to :notifiable, polymorphic: true
Counter caches
ruby
belongs_to :card, counter_cache: :comments_count
belongs_to :board, counter_cache: :cards_count

Scope patterns

ruby
# Basic ordering
scope :recent, -> { order(created_at: :desc) }
scope :positioned, -> { order(:position) }
# With arguments
scope :by_creator, ->(user) { where(creator: user) }
scope :created_after, ->(date) { where("created_at > ?", date) }
# Joins and where.missing (key pattern for state records)
scope :assigned_to, ->(users) { joins(:assignments).where(assignments: { assignee: users }).distinct }
scope :open, -> { where.missing(:closure) }
scope :unassigned, -> { where.missing(:assignments) }
# Complex composed scopes
scope :entropic, -> {
open.published.where.missing(:not_now).where("updated_at < ?", 30.days.ago)
}

Validation patterns

ruby
validates :title, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :email_address, uniqueness: { case_sensitive: false }
validates :user_id, uniqueness: { scope: :card_id } # Join tables
validates :card, uniqueness: true # has_one state records
validates :body, presence: true, if: :published? # Conditional

Callbacks and enums

ruby
# Callbacks: use sparingly, prefer _commit for external effects
after_create_commit :broadcast_creation
before_validation :set_default_status, on: :create
after_create_commit :notify_recipients_later # Uses _later convention
# String enums (preferred for DB readability)
enum :status, {
draft: "draft", published: "published", archived: "archived"
}, default: :draft, prefix: true

Business logic methods

Action methods (verbs)
ruby
def close(user: Current.user)
create_closure!(user: user)
track_event "card_closed", user: user
notify_watchers_later
end
def assign(user)
assignments.create!(user: user) unless assigned_to?(user)
track_event "card_assigned", particulars: { assignee_id: user.id }
end
Query methods (predicates)
ruby
def closed?
closure.present?
end
def assigned_to?(user)
assignees.include?(user)
end
def can_be_edited_by?(user)
user.can_administer_card?(self) || creator == user
end
Computed attributes
ruby
def closed_at
closure&.created_at
end
def closed_by
closure&.user
end

_later / _now convention

ruby
# Async version (queues a job)
def notify_recipients_later
NotifyRecipientsJob.perform_later(self)
end
# Sync version (immediate execution)
def notify_recipients_now
recipients.each do |recipient|
Notification.create!(recipient: recipient, notifiable: self)
end
end
# Default to sync
def notify_recipients
notify_recipients_now
end
# Call _later from callbacks
after_create_commit :notify_recipients_later

Using Current for context

ruby
class Current < ActiveSupport::CurrentAttributes
attribute :session, :user, :identity, :account
end
class Card < ApplicationRecord
belongs_to :creator, class_name: "User", default: -> { Current.user }
belongs_to :account, default: -> { Current.account }
def close(user: Current.user)
create_closure!(user: user)
end
end

See references/model-examples.md for complete model examples (join tables, form objects, POROs, migrations, tests).

Boundaries

  • Always: Put business logic in models, use concerns for organization, use bang methods (create!, update!), leverage associations and scopes, use Current for context, default values via lambdas
  • Ask first: Before creating service objects, before adding complex callbacks, before using inheritance (prefer composition via concerns)
  • Never: Create anemic models (data without behavior), put business logic in controllers, skip validations, create models without tests

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
589
Repository forks
67
7-day trend score
32

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/thibautbaissac/rails_ai_agents/model-patterns/badge)](https://verified-skill.com/skills/thibautbaissac/rails_ai_agents/model-patterns)

Something wrong with this skill or its page? Tell us.