rails-best-practices-core
GitHubRails开发核心最佳实践规范,涵盖架构、命名、路由、安全及可维护性。作为默认基准用于编码、重构、调试、功能实现、迁移及代码审查,确保代码风格一致与高质量。
Trigger Scenarios
Install
npx skills add lylo/pagecord --skill rails-best-practices-core -g -y
SKILL.md
Frontmatter
{
"name": "rails-best-practices-core",
"description": "Apply core Ruby on Rails best practices for architecture, naming, RESTful routing, authorisation, safety, and maintainability. Use by default for Rails coding, refactoring, debugging, feature implementation, migrations, controller\/model changes, and code review tasks so baseline standards stay consistent."
}
Rails Best Practices Core
Use this as the default baseline for Rails work. It is distilled from 37signals codebases such as Campfire and Fizzy, plus DHH's review patterns.
Core Defaults
- Prefer clear, explicit code over clever abstractions. Abstractions must earn their keep; if you cannot point to three or more variations that need it, inline it.
- Keep controllers thin and put domain behaviour in models.
- Prefer Rails conventions and built-ins before adding gems.
- Model state and behaviour with domain concepts, not ad hoc flags.
- Scope tenant and user data through ownership boundaries.
- Favour database constraints for hard invariants; validate in Active Record when user-facing error messages are needed.
- Keep interfaces small; do not add public methods that are not used anywhere.
- Prefer write-time computation over expensive read-time composition, such as counter caches, delegated types, precomputed rollups, and
dependent: :delete_allwhen callbacks are not needed. - Use
params.expect(...)for strong params in modern Rails. - Let unexpected failures crash. Use bang methods such as
create!and handle exceptions at boundaries. Only use!when a non-bang counterpart exists. - Fix root causes, not symptoms, such as
enqueue_after_transaction_commitinstead of retry logic for transaction races. - Ship tests in the same PR as behaviour changes.
Modelling Patterns
- Model state as records, not booleans. Prefer a
Closurerecord with creator and timestamps overclosed: boolean:
has_one :closure, dependent: :destroy
scope :closed, -> { joins(:closure) }
scope :open, -> { where.missing(:closure) }
- Slice large models into concerns named for capabilities, such as
Closeable,Watchable, orAssignable. Keep each concern cohesive, self-contained, and roughly 50-150 lines. - Prefer nested modules under the model namespace, such as
Card::Closeableinapp/models/card/closeable.rb, for domain slices. Reserveapp/models/concerns/for genuinely cross-model behaviour. - Never extract concerns that contain only private methods.
- Put POROs in
app/models/, notapp/services/, when they are model-adjacent: presentation objects (Event::Description), complex operations (SystemCommenter), and view-context bundles (User::Filtering). - Use default lambdas for contextual associations, such as
belongs_to :creator, class_name: "User", default: -> { Current.user }. - Use
Currentattributes for request context (Current.user,Current.account) and cascading setters when one context value resolves another. - Use callbacks for setup and cleanup, not core business logic. Keep callback counts low.
- Reach for Rails shortcuts:
normalizes,store_accessor,delegated_type,generates_token_for, string enums,after_save_commit,touch: true, anddelegate. - Use association extensions for bulk domain operations. Put operations like
grant_toorreviseon thehas_manyproxy; useinsert_allfor bulk creates. - Prefer human-friendly URLs. Override
to_paramwith a per-tenant number when that fits better than exposing raw IDs or UUIDs.
Naming
- Treat naming as design.
ClosurebeatsCardClose;MentionbeatsUserReference. - Prefer positive names:
activeovernot_deleted,visibleovernot_hidden. - Name semantic associations by role, such as
belongs_to :creator, class_name: "User", notbelongs_to :user. - Prefer domain language over technical phrasing:
quota.depleted?overquota.over_limit?. - Use business-focused scopes such as
:active,:unassigned, and:golden, not SQL-ish names such as:without_pop. - Keep domain language consistent. Do not mix
source,resource, andcontainerfor one concept.
REST And Routing
- Treat everything as CRUD. Turn verbs into nouns: close becomes
resource :closure; publish becomesresource :publication. Avoid custom member actions when resource modelling is clearer. - Use singular
resourcefor one-per-parent state. - Use
scope module:to group nested controllers, such asCards::ClosuresController. - Prefer shallow nesting for deep hierarchies.
- Use resource-scoping controller concerns, such as
CardScoped, to set parent records through the current ownership boundary. - Use
resolve "Comment"when polymorphic URL generation should point to a parent with an anchor. - Let the same controllers serve HTML, Turbo, and JSON via
respond_towhen possible. Do not create a separate API namespace unless the API has distinct semantics.
Authorisation
- Avoid Pundit and CanCanCan by default. Prefer simple predicate methods on models, such as
card.editable_by?(user)anduser.can_administer_board?(board). - Controllers enforce access; models define what the permission means.
- Prefer declarative controller macros for authentication posture, such as
allow_unauthenticated_accessandensure_can_administer.
Dependencies
Before adding a gem, ask whether vanilla Rails can do it and whether 50-150 lines in-repo would be simpler than a dependency. Commonly skipped defaults include Devise, Pundit, ViewComponent, RSpec, FactoryBot, Redis when Solid Queue/Cache/Cable can use the database, service objects, form objects, decorators, GraphQL, SPA frameworks, and Tailwind when the app has a different styling convention.
Review Priorities
- Correctness and data safety.
- Multi-tenant and security boundaries.
- Maintainability and readability.
- Performance hot spots.
- Style and polish.
Always Flag
- Unscoped record lookups in tenant-aware flows, such as
Comment.find(params[:id]). - New dependencies without strong justification.
- In-memory filtering or sorting that belongs in SQL, and
.map(&:name)where.pluck(:name)works. - Service objects replacing straightforward model methods.
- Non-RESTful custom actions when resource modelling is clearer.
- Boolean state columns where a record would capture who and when.
- Pages with forms using HTTP caching (
fresh_whenor ETags), because stale CSRF tokens cause 422s. - String status checks (
status == "x") when predicate-style APIs are available. validates :x, uniqueness: truewithout a backing unique index.- Helpers depending on implicit instance variables instead of explicit arguments.
- Unescaped interpolation into
html_safestrings. Escape first:"<b>#{h(input)}</b>".html_safe. - Metaprogramming for two or three cases. Just write the methods.
- Private-only concerns. Inline them.
Review Output
- Start with the highest-severity findings.
- For each finding, include the issue, impact, concrete fix, and file:line reference.
- Be direct and practical.
- End with either
Ship itor a short prioritised fix list.
Version History
- 386b8a3 Current 2026-08-20 13:57


