rails/rails
Patterns and conventions
The conventions that span every Rails component. Drawn from AGENTS.md, .rubocop.yml, and the codebase itself.
Code style
File header
Every Ruby file starts with:
# frozen_string_literal: true.rubocop.yml enforces it. New apps generated by rails new (8.2+) also enable frozen string literals via config/bootsnap.rb.
assert_not over assert !
Rails/AssertNot cop forbids assert !condition. Use assert_not condition or refute condition.
Markdown comparisons
In view tests, prefer assert_dom_equal over plain string equality. It normalizes attribute ordering and whitespace.
Sentence case in headings
In guides and YARD/RDoc comments. ("Getting started with authentication", not "Getting Started With Authentication".)
Whitespace and naming
- 2-space indentation.
snake_casefor files, methods, and locals.CamelCasefor constants and modules.- Files match constants:
ActiveRecord::ConnectionAdapters::PostgreSQLAdapterlives inactive_record/connection_adapters/postgresql_adapter.rb(Zeitwerk requires this).
Configuration flags
Configuration options follow a consistent pattern.
1. Define on the base class
class ActionView::Base
cattr_accessor :remove_hidden_field_autocomplete, default: false
endcattr_accessor comes from Active Support (active_support/core_ext/module/attribute_accessors.rb).
2. Read the flag at the call site
@options.reverse_merge!(autocomplete: "off") unless ActionView::Base.remove_hidden_field_autocompleteRead the full constant path every time. Don't memoize it on the instance.
3. Wire into load_defaults
# railties/lib/rails/application/configuration.rb
case target_version.to_s
when "8.1"
action_view.remove_hidden_field_autocomplete = true
endload_defaults is how a Rails version opts an app into recommended behavior. Adding a flag without wiring it into load_defaults is fine for opt-in features but unusual for new defaults.
4. Document in the configuring guide
guides/source/configuring.md lists every framework configuration option. Add yours there.
Module composition
ActiveSupport::Concern
The standard mixin pattern. Use it whenever a module needs class methods, includes another concern, or runs hooks at inclusion time:
module Cacheable
extend ActiveSupport::Concern
included do
after_save :touch_cache
end
class_methods do
def cache_key_for(record)
"#{name}/#{record.id}"
end
end
endConcern (activesupport/lib/active_support/concern.rb) avoids the boilerplate of included(base) hooks and resolves dependency ordering automatically.
Lazy load hooks
Code in one component that needs to extend another (e.g., Active Storage adding methods to ActiveRecord::Base) uses ActiveSupport.on_load:
ActiveSupport.on_load(:active_record) do
include ActiveStorage::Reflection::ActiveRecordExtensions
endThe hook fires once ActiveRecord::Base is fully loaded. Implementation in activesupport/lib/active_support/lazy_load_hooks.rb.
File organization
Per AGENTS.md:
lib/— production code.test/— Minitest test files (always, neverspec/).bin/— executables (e.g.,bin/test).- Each component has its own
Gemfile-like dependencies via gemspec. - Shared tooling lives in
tools/.
Within Action View, helpers are split into three layers — modify all three for consistency:
actionview/lib/action_view/helpers/tags/<element>.rb— individual form elements (hidden_field,text_field, etc.).actionview/lib/action_view/helpers/form_helper.rb— high-level form builders.actionview/lib/action_view/helpers/form_tag_helper.rb— standalone tag helpers.
Within Active Record, related work usually splits into lib/active_record/<feature>.rb (top-level module) plus a lib/active_record/<feature>/ directory for sub-pieces.
Deprecation pattern
Each component owns a Deprecator:
# activerecord/lib/active_record/deprecator.rb
module ActiveRecord
def self.deprecator
@deprecator ||= ActiveSupport::Deprecation.new("8.2", "Rails")
end
endUse it like this:
ActiveRecord.deprecator.warn("`schema_order` is deprecated; use `schema_search_path`.")Per-component deprecators let applications silence or escalate deprecation warnings on a per-gem basis. The shared base class is ActiveSupport::Deprecation in activesupport/lib/active_support/deprecation.rb.
Changelog entries
Add to the top of <component>/CHANGELOG.md:
- Brief description of the change. Mention any user-facing API change
or migration step.
_Your Name_Existing entries set the tone — short, present-tense, focused on user-visible impact.
Rescue and error reporting
For framework code, prefer:
begin
do_thing
rescue SomeError => e
Rails.error.report(e, context: { foo: bar })
raise
endRails.error resolves to ActiveSupport::ErrorReporter, which fans out to every registered subscriber (Sentry, Honeybadger, application-specific reporters, etc.). The implementation is in activesupport/lib/active_support/error_reporter.rb.
Notification names
Notification events are named <verb>.<component_namespace>:
sql.active_recordprocess_action.action_controllerrender_template.action_viewenqueue.active_jobcache_read.active_support
When you publish a new event, follow this convention. The first segment is the action; the second is the component slug.
Testing patterns
See testing for the full list. The high-impact ones:
- Use
Object#withfor temporary configuration changes. - Use
freeze_time/travel_tofromActiveSupport::Testing::TimeHelpers. - Test both the default and the explicit-override case for any flag.
- Group related tests in the same file rather than splitting per class.
Documentation
API docs use YARD/RDoc style:
# Returns the cache key for the given record.
#
# user = User.find(1)
# User.cache_key_for(user) # => "User/1"
def self.cache_key_for(record)
...
endGuides under guides/source/ are Markdown. The configuring guide is the canonical place to document new configuration options.
For tooling that enforces these rules, see tooling.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.