ruby-on-rails-best-practices

安装量: 54
排名: #13837

安装

npx skills add https://github.com/sergiodxa/agent-skills --skill ruby-on-rails-best-practices

Ruby on Rails Best Practices Architecture patterns and coding conventions extracted from Basecamp's production Rails applications (Fizzy and Campfire). Contains 16 rules across 6 categories focused on code organization, maintainability, and following "The Rails Way" with Basecamp's refinements. When to Apply Reference these guidelines when: Organizing models, concerns, and controllers Writing background jobs Implementing real-time features with Turbo Streams Deciding where code should live Writing tests for Rails applications Reviewing Rails code for architectural consistency Rules Summary Model Organization (HIGH) model-scoped-concerns - @rules/model-scoped-concerns.md Place model-specific concerns in app/models/model_name/ not app/models/concerns/ .

Directory structure

app /models/ ├── card . rb ├── card / │ ├── closeable . rb

Card::Closeable

│ ├── searchable . rb

Card::Searchable

│ └── assignable . rb

Card::Assignable

app/models/card.rb

class Card < ApplicationRecord include Closeable , Searchable , Assignable

Ruby resolves from Card:: namespace first

end concern-naming - @rules/concern-naming.md Use -able suffix for behavior concerns, nouns for feature concerns.

Behaviors: -able suffix

module Card :: Closeable

Can be closed

module Card :: Searchable

Can be searched

module User :: Mentionable

Can be mentioned

Features: nouns

module User :: Avatar

Has avatar

module User :: Role

Has role

module Card :: Mentions

Has @mentions

template-method-concerns - @rules/template-method-concerns.md Use template methods in shared concerns for customizable behavior.

app/models/concerns/searchable.rb (shared)

module Searchable def search_title raise NotImplementedError end end

app/models/card/searchable.rb (model-specific)

module Card :: Searchable include :: Searchable def search_title title

Implement the hook

end end Background Jobs (HIGH) paired-async-methods - @rules/paired-async-methods.md Pair sync methods with _later variants that enqueue jobs.

app/models/card/readable.rb

def remove_inaccessible_notifications

Sync implementation

end private def remove_inaccessible_notifications_later Card :: RemoveInaccessibleNotificationsJob . perform_later ( self ) end

app/jobs/card/remove_inaccessible_notifications_job.rb

class Card :: RemoveInaccessibleNotificationsJob < ApplicationJob def perform ( card ) card . remove_inaccessible_notifications end end thin-jobs - @rules/thin-jobs.md Jobs call model methods. All logic lives in models.

Bad: Logic in job

class ProcessOrderJob < ApplicationJob def perform ( order ) order . items . each { | i | i . product . decrement ! ( :stock ) } order . update ! ( status : :processing ) end end

Good: Job delegates to model

class ProcessOrderJob < ApplicationJob def perform ( order ) order . process

Single method call

end end Controllers (HIGH) resource-controllers - @rules/resource-controllers.md Create resource controllers for state changes, not custom actions.

Bad: Custom actions

resources :cards do post :close post :reopen end

Good: Resource controllers

resources :cards do resource :closure , only : [ :create , :destroy ] end

app/controllers/cards/closures_controller.rb

class Cards :: ClosuresController < ApplicationController def create @card . close end def destroy @card . reopen end end scoping-concerns - @rules/scoping-concerns.md Use concerns like CardScoped for nested resource setup.

app/controllers/concerns/card_scoped.rb

module CardScoped extend ActiveSupport :: Concern included do before_action :set_card end private def set_card @card = Current . user . accessible_cards . find_by ! ( number : params [ :card_id ] ) end end

Usage

class Cards :: CommentsController < ApplicationController include CardScoped end thin-controllers - @rules/thin-controllers.md Controllers call rich model APIs directly. No service objects.

Good: Thin controller, rich model

class Cards :: ClosuresController < ApplicationController include CardScoped def create @card . close

All logic in model

end end Request Context (MEDIUM) current-attributes - @rules/current-attributes.md Use Current for request-scoped data with cascading setters. class Current < ActiveSupport :: CurrentAttributes attribute :session , :user , :account def session = ( value ) super ( value ) self . user = session &. user end end current-in-other-contexts - @rules/current-in-other-contexts.md Current is only auto-populated in web requests. Jobs, mailers, and channels need explicit setup.

Jobs: extend ActiveJob to serialize/restore Current.account

Mailers from jobs: wrap in Current.with_account

Channels: set Current in Connection#connect

Associations & Callbacks (MEDIUM) association-extensions - @rules/association-extensions.md Choose between association extensions and model class methods based on context needs.

Use extension when you need parent context (proxy_association.owner)

has_many :accesses do def grant_to ( users ) board = proxy_association . owner Access . insert_all ( users . map { | u | { user_id : u . id , board_id : board . id , account_id : board . account_id } } ) end end

Use class method when operation is independent

class Access def self . grant ( board : , users : ) insert_all ( users . map { | u | { user_id : u . id , board_id : board . id } } ) end end callbacks-patterns - @rules/callbacks-patterns.md Use after_commit for jobs, inline lambdas for simple ops.

Jobs: after_commit

after_create_commit :notify_recipients_later

Simple ops: inline lambda

after_save

{ board . touch } , if : :published?

Conditional: remember and check pattern

before_update :remember_changes after_update_commit :process_changes , if : :should_process? Turbo & Real-time (MEDIUM) turbo-broadcasts - @rules/turbo-broadcasts.md Explicit broadcasts from controllers, not callbacks.

app/models/message/broadcasts.rb

module Message :: Broadcasts def broadcast_create broadcast_append_to room , :messages , target : [ room , :messages ] end end

Controller calls explicitly

def create @message = @room . messages . create ! ( message_params ) @message . broadcast_create end Testing (MEDIUM) fixtures-testing - @rules/fixtures-testing.md Use fixtures, not factories. Mirror concern structure in tests.

test/fixtures/cards.yml

logo : title : The logo isn't big enough board : writebook creator : david

test/models/card/closeable_test.rb

class Card :: CloseableTest < ActiveSupport :: TestCase test "close creates closure" do card = cards ( :logo ) assert_difference -

{ Closure . count } do card . close end end end Code Organization (LOW-MEDIUM) nested-service-objects - @rules/nested-service-objects.md Place service objects under model namespace, not app/services .

Good: app/models/card/activity_spike/detector.rb

class Card :: ActivitySpike :: Detector def initialize ( card ) @card = card end def detect

...

end end code-style - @rules/code-style.md Prefer expanded conditionals, order methods by invocation.

Expanded conditionals

def find_record if record = find_by_id ( id ) record else NullRecord . new end end

Method ordering: caller before callees

def process step_one step_two end private def step_one ; end def step_two ; end Philosophy These patterns embody "Vanilla Rails" - using Rails conventions with minimal additions: Rich models, thin controllers - Domain logic in models and concerns No service object layer - Controllers talk to models directly Co-located code - Concerns, jobs, and services near the models they serve Explicit over implicit - Call broadcasts explicitly, not via callbacks Convention over configuration - Follow naming patterns for predictability

返回排行榜