Rails Conventions
Opinionated Rails patterns for clean, maintainable code.
Core Philosophy
Duplication > Complexity: Simple duplicated code beats complex DRY abstractions
-
"I'd rather have four simple controllers than three complex ones"
Testability = Quality: If it's hard to test, structure needs refactoring
Adding controllers is never bad. Making controllers complex IS bad.
Turbo Streams
Simple turbo streams MUST be inline in controllers:
FAIL: Separate .turbo_stream.erb files for simple operations
render "posts/update"
PASS: Inline array
render turbo_stream: [ turbo_stream.replace("post_#{@post.id}", partial: "posts/post", locals: { post: @post }), turbo_stream.remove("flash") ]
Controller & Concerns
Business logic belongs in models or concerns, not controllers.
Concern structure
module Dispatchable extend ActiveSupport::Concern
included do scope :available, -> { where(status: "pending") } end
class_methods do def claim!(batch_size) # class-level behavior end end end
Service Extraction
Extract when you see MULTIPLE of:
-
Complex business rules (not just "it's long")
-
Multiple models orchestrated
-
External API interactions
-
Reusable cross-controller logic
Service structure:
-
Single public method
-
Namespace by responsibility (Extraction::RegexExtractor )
-
Constructor takes dependencies
-
Return data structures, not domain objects
Modern Ruby Style
Hash shorthand
{ id:, slug:, doc_type: kind }
Safe navigation
created_at&.iso8601 @setting ||= SlugSetting.active.find_by!(slug:)
Keyword arguments
def extract(document_type:, subject:, filename:) def process!(strategy: nil)
Enum Patterns
Frozen arrays with validation
STATUSES = %w[processed needs_review].freeze enum :status, STATUSES.index_by(&:itself), validate: true
Scope Patterns
Guard with .present?, chainable design
scope :by_slug, ->(slug) { where(slug:) if slug.present? } scope :from_date, ->(date) { where(created_at: Date.parse(date).beginning_of_day..) if date.present? }
def self.filtered(params) all.by_slug(params[:slug]).by_kind(params[:kind]) rescue ArgumentError all end
Error Handling
Domain-specific errors
class InactiveSlug < StandardError; end
Log with context, re-raise for upstream
def handle_exception!(error:) log_error("Exception #{error.class}: #{error.message}", error:) mark_failed!(error.message) raise end
Testing (Minitest + Fixtures)
test "describes expected behavior" do email = emails(:two) email.process email.reload assert_equal "finished", email.processing_status end
Principles:
-
Behavior-driven: Test what, not how
-
Fixture-based: Use emails(:two) for setup
-
Mock externals: Stub S3, APIs, PDFs
-
State verification: .reload after operations
-
Helper methods: build_valid_email , with_stubbed_download
Naming (5-Second Rule)
If you can't understand in 5 seconds:
FAIL
show_in_frame process_stuff
PASS
fact_check_modal _fact_frame
Performance
-
Consider scale impact
-
No premature caching
-
KISS - Keep It Simple
-
Indexes slow writes - add only when needed