mcpField
J
jr-rails-classic

jr-rails-classic

github.com/julianrubischDevelopment3046 installsUpdated 4w ago
Repository

About jr-rails-classic

jr-rails-classic is a coding-style skill for Rails developers, based on 37signals conventions. It emphasizes rich domain models, REST resource design, and Minitest with fixtures.

Key features

  1. 01Generator-first development workflow
  2. 02Rich domain models and thin controllers
  3. 03Map state changes to sub-resources
  4. 04Minitest and fixtures testing
  5. 05Guidance on database constraints and jobs

Use cases

  1. 01Build Rails features using 37signals conventions
  2. 02Plan REST controllers and data models
  3. 03Design tests and background jobs for Rails apps

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/julianrubisch/skills --skill jr-rails-classic

After installing, type/jr-rails-classicin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

reference/
agentic-worktrees.md
anti-patterns.md
coding-classic.md
coding-phlex.md
devops.md
patterns.md
reek.yml
reference/coding/
api.md
reference/refactorings/
000-template.md
001-extract-scope-from-controller.md
002-replace-conditional-with-polymorphism.md
003-replace-conditional-with-null-object.md
004-introduce-form-object.md
005-replace-subclasses-with-strategies.md
006-replace-mixin-with-composition.md
007-extract-validator.md
008-introduce-parameter-object.md
009-replace-callback-with-method.md
010-refactor-service-object-into-poro.md
extraction-signals.md
reference/shared/
architecture.md
authorization.md
callbacks.md
components.md
concerns.md
configuration.md
current_attributes.md
graphql.md
hotwire.md
instrumentation.md

Details

From the project README

Rails Classic Coding Style

Write Rails application code following 37signals conventions. Rich domain models, CRUD controllers, database-backed everything, Minitest with fixtures.

Core Workflow

  1. Use generators first — rails g model, rails g controller, rails g migration. Generators produce correct file structure, test stubs, and route entries in one shot.
  2. Implementation order — models → controllers → views → tests.
  3. Ship, Validate, Refine — prototype to production, learn from real usage.
  4. Let it crash — use bang methods, let Rails handle RecordInvalid with 422s.

Guardrails

  • No service objects — use domain models namespaced under app/models/
  • No custom controller actions — create new resources instead (REST mapping)
  • No RSpec, no factory_bot — Minitest with fixtures only
  • No Redis — Solid Queue, Solid Cache, Solid Cable
  • No strftime in views — custom DATE_FORMATS in initializers
  • Callbacks only for derived data and async dispatch — never business logic
  • Database constraints over model validations for hard guarantees
  • Pass IDs to jobs, not objects

Conventions Quick Reference

Naming
  • Verbs for state changes: card.close, board.publish
  • Predicates from record presence: card.closed?, card.golden?
  • Concerns as adjectives: Closeable, Publishable, Watchable
  • Controllers as nouns: Cards::ClosuresController
  • Scopes as business terms: scope :active, scope :chronologically
REST Mapping

No custom actions. Create sub-resources:

ActionRoute
close a cardPOST /cards/:id/closure
archive a cardPOST /cards/:id/archival
watch a boardPOST /boards/:id/watching
Models — State as Records

Instead of booleans, create state records (timestamps + authorship for free):

class Card::Closure < ApplicationRecord
  belongs_to :card
  belongs_to :creator, class_name: "User"
end

module Closeable
  extend ActiveSupport::Concern

  included do
    has_one :closure, dependent: :destroy
  end

  def closed? = closure.present?
  def close(creator: Current.user) = create_closure!(creator: creator)
  def reopen = closure&.destroy
end

# Querying
Card.joins(:closure)          # closed
Card.where.missing(:closure)  # open
Concerns — Horizontal Behavior

Self-contained (associations + scopes + methods). 50–150 lines. Named for capabilities, not organization.

class Card < ApplicationRecord
  include Assignable, Closeable, Golden, Watchable, Searchable
end
POROs Under Model Namespace

Business logic that doesn't fit a concern:

# app/models/event/description.rb
class Event::Description
  def initialize(event) = @event = event
  def to_s = # ...
end
Validations

Minimal on model (data integrity), contextual on form objects (UI flows):

# Model — minimal
class User < ApplicationRecord
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  normalizes :email, with: ->(e) { e.strip.downcase }
end

# Migration — hard constraints
add_index :users, :email, unique: true
add_foreign_key :cards, :boards
Association Design
Instead ofPreferWhen
has_and_belongs_to_manyhas_many :throughJoin model needs attributes or callbacks
1:NM:N (has_many :through)Relationship may grow
polymorphic: truedelegated_typeType-safe variants with different schemas

Always set :dependent on has_many/has_one. Use :inverse_of when Rails can't infer it.

Rails 7.1+ Patterns
# Delegated types
class Message < ApplicationRecord
  delegated_type :messageable, types: %w[Comment Reply Announcement]
end

# store_accessor for JSON/JSONB
class User < ApplicationRecord
  store_accessor :settings, :theme, :notifications_enabled
end
Database
# UUIDs (UUIDv7 — time-sortable)
create_table :cards, id: :uuid do |t|
  t.references :board, type: :uuid, foreign_key: true
end

# Counter caches
class Comment < ApplicationRecord
  belongs_to :card, counter_cache: true
end

# Default values with Current
class Card < ApplicationRecord
  belongs_to :creator, class_name: "User", default: -> { Current.user }
end
Controllers

Thin. Use concerns for shared behavior:

module CardScoped
  extend ActiveSupport::Concern
  included { before_action :set_card }

  private
    def set_card
      @card  = Card.find(params[:card_id])
      @board = @card.board
    end
    def render_card_replacement
      render turbo_stream: turbo_stream.replace(@card)
    end
end

class Cards::ClosuresController < ApplicationController
  include CardScoped
  def create  = @card.close   && render_card_replacement
  def destroy = @card.reopen  && render_card_replacement
end
Current Attributes
class Current < ActiveSupport::CurrentAttributes
  attribute :session, :user, :account, :request_id
  delegate :user, to: :session, allow_nil: true
end
Background Jobs

Thin wrappers — logic lives on the model:

module Watchable
  def notify_watchers_later = NotifyWatchersJob.perform_later(self)
  def notify_watchers
    watchers.each { |w| WatcherMailer.notification(w, self).deliver_later }
  end
end

class NotifyWatchersJob < ApplicationJob
  def perform(card) = card.notify_watchers
end
View Helpers

Date formatting via initializer (never strftime in views):

# config/initializers/date_formats.rb
Date::DATE_FORMATS[:default]    = "%d.%m.%Y"
Time::DATE_FORMATS[:default]    = "%d.%m.%Y %H:%M"
Time::DATE_FORMATS[:time_only]  = "%H:%M"

Use active_link_to gem for navigation active states.

Testing
  • Minitest with fixtures, integration tests for controllers
  • Test observable behavior, not implementation
  • Don't mock what you can test for real
  • VCR for external APIs
Preferred Stack
ConcernGem
Frontendturbo-rails, stimulus-rails, importmap-rails
Assetspropshaft
JobsSolid Queue
Cache/CableSolid Cache, Solid Cable
AuthorizationPundit
DeploymentKamal + Thruster
Gem Selection
  1. Can vanilla Rails do this?
  2. Is it the app's core concern? If yes, own the code
  3. Does it add infrastructure? Database-backed alternatives exist
  4. Is the complexity worth it?
  5. Is it from someone you trust?

Deep Reference Files

Read these on demand when the task requires deeper guidance:

For frontend patterns (Stimulus controllers, Turbo Frames/Streams), invoke the relevant hwc-* skill alongside this one.

Comments

Markdown supported