- Ruby
- Rails
- PostgreSQL
- RSpec
- Keep lines under 120 characters
- Ruby version: 3.4.2
- Stick to Rails conventions for organizing controllers and models
- Comments should explain the "why" behind decisions, not describe what the code does
- Include business context in comments rather than just technical implementation details
- Write small, focused methods that do one thing well
- Aim for code that's terse yet readable
- Embrace Ruby idioms—for example, use
.first,.second,.lastinstead of array indices like[0] - Choose consistency over cleverness, even when a clever solution might seem "better"
- Standard patterns reduce cognitive load and make the codebase more approachable
- Start the server:
bin/rails server - Run all tests:
bundle exec rspec - Run a specific test:
bundle exec rspec path/to/spec.rb:line_number - Check code style:
bundle exec rubocop - Apply database migrations:
bin/rails db:migrate - Create a new migration:
bin/rails generate migration add_rating_to_restaurants rating:decimal - View all routes:
bin/rails routes - Generate controllers:
bin/rails generate controller Articles index --skip-routes - Generate models:
bin/rails generate model Article title:string - Create full scaffolds for rapid prototyping with pre-built models, controllers, and views
- Run a specific migration:
bin/rails db:migrate:up VERSION=yyyymmddhhmmss - Execute one-off commands:
bin/rails runner "employee = Employee.new; employee.email='[email protected]'; employee.save" - Rails 8's new script generator:
bin/rails generate script data_cleanup - Run scripts with:
bundle exec ruby script/data_cleanup.rb
- Controllers should be lean—business logic belongs in models
- Limit controller responsibilities to:
- Authentication and authorization checks
- Sanitizing incoming parameters
- Finding records for read operations
- Calling operations for create, update, and destroy actions
- Handling success and failure responses
- If your controller code doesn't fit these categories, extract it to a service object
- Name service objects after domain concepts and place them either globally or under related model namespaces (e.g.,
Employee::Onboarding)
- Prioritize server-side solutions over JavaScript
- Keep domain logic in Ruby where it belongs
- For simple interactions, use vanilla JavaScript or Stimulus when needed
- When JavaScript is necessary:
- Build reusable, generic components instead of tightly-coupled domain-specific ones
- Design functionality that other developers can easily reuse
- Deploy JavaScript strategically where it adds the most value
- Hotwire comes built into Rails, providing server-rendered HTML with dynamic updates through Turbo (for navigation, forms, and page updates) and Stimulus (for adding custom JavaScript behavior)
- Organize code into clear bounded contexts
- Each domain should have its own models, services, and business rules
- Follow this consistent structure for model organization:
- Module inclusions
- Attribute definitions
- Associations
- Validations
- Callbacks
- Scopes
- Public methods
- Private methods
- Always declare attribute types explicitly:
attribute :status, :string, default: 'pending' - Use inquiry methods for cleaner code:
inquirer :statusallowssubscription.status.active?instead ofsubscription.status == 'active' - Approach soft deletes thoughtfully
- Rather than using concerns to manage large models, consider breaking them into smaller, focused concepts
- Stick with ActiveRecord's built-in features like scopes and associations instead of introducing patterns like repositories or query objects
- Default to persisting data rather than computing it on the fly—store calculated values in the database for better query performance
- Choose clear, descriptive names that make sense in context
- Avoid redundant prefixes (use
amountnotpayment_amountin a Payment class) - Mirror the language your users actually use
- Embrace standard abbreviations for common terms (ssn, dob, ein, tin)
- Prefer namespaced models (
Restaurant::Review) over compound names (RestaurantReview) - Use namespaces to logically group related models
- Let domain terminology guide your method names
- Implement authorization using Action Policy (https://actionpolicy.evilmartians.io)
- Policies should handle both permissions and business rules:
- Can this user perform the action?
- Is the action valid for this object's current state?
- Embed business logic checks directly within policy methods
- For state transitions, verify both:
- Whether the object can move to the target state
- Whether the user has permission to trigger that transition
- Use
deny!for early exits when actions aren't allowed - Keep views and controllers clean with simple
allowed_to?checks
- Rails projects follow a standard directory structure: app, config, db, etc.
- Leverage modern Ruby features where they improve clarity (pattern matching, endless methods)
- Use
delegateto forward methods cleanly to associations - Prefer the safe navigation operator
&.over explicit nil checks - Follow Rails conventions: singular model names (e.g., Restaurant) map to plural tables (restaurants); controllers use plural names
- Skip Sprockets—it's outdated in Rails 8
- Use Ruby's concise hash syntax
- Query models using ActiveRecord's intuitive API:
Restaurant.allRestaurant.where(cuisine: 'Italian')Restaurant.order(rating: :desc)Article.where('title = ?', params[:title])Event.where('start_time >= :start AND start_time <= :end', { start: params[:start], end: params[:end] })
- Chain queries for complex lookups
- Use ranges for date queries:
Article.where(published_at: (Time.now.midnight - 1.week)..Time.now.midnight) - Handle
INclauses naturally:Employee.where(department_id: [1, 3, 5]) - Exclude with
where.not:Employee.where.not(department_id: [1, 3, 5]) - Combine conditions with
OR:Employee.where(title: 'Manager').or(Employee.where(years_of_service: 5..10)) - Sort by multiple columns:
Article.order(published_at: :desc, title: :asc) - Paginate with
limitandoffset:Restaurant.limit(20).offset(40) - Group records:
Order.group(:status).count - Filter grouped results:
Order.select('date(created_at) as order_date, sum(total) as daily_total').group('date(created_at)').having('sum(total) > ?', 1000) - Remove specific conditions with
unscope - Define reusable queries with scopes:
scope :published, -> { where(draft: false) }scope :featured, -> { published.where(featured: true) }
- Use default scopes sparingly—only when absolutely necessary
- Chain scopes to compose complex queries
- Find or create records atomically:
Employee.find_or_create_by(email: '[email protected]') - Resort to custom SQL only when necessary:
Restaurant.find_by_sql('SELECT * FROM restaurants WHERE ST_Distance(location, ...) < 1000') - Check existence efficiently:
Subscription.exists?(user_id: current_user.id) - Use
any?andmany?for collection checks - Add query annotations for debugging:
Event.annotate('finding upcoming events').where('start_time > ?', Time.current) - Process large datasets efficiently:
Subscription.where(active: true).find_each do |subscription| - Follow RESTful routing patterns with
resources :articles - Implement strong parameters to control allowed attributes:
params.require(:restaurant).permit(:name, :cuisine, :rating) - Add model validations for data integrity:
validates :email, presence: true, uniqueness: truevalidates :capacity, numericality: { greater_than: 0 }
- Define routes in
config/routes.rb:get '/events/upcoming', to: 'events#upcoming' - DRY up controllers with callbacks:
before_action :find_article, only: %i[show edit update destroy] - Use ActiveRecord callbacks judiciously:
after_create :send_welcome_emailbefore_validation :normalize_phone_number- Conditional callbacks:
after_save :update_search_index, if: :published? - Lambda conditions:
before_destroy :check_dependencies, unless: ->(record) { record.archived? }
- Handle callback failures appropriately—exceptions trigger rollbacks, use
throw :abortfor gentler failures - Define model relationships clearly:
has_many :reviews, dependent: :destroybelongs_to :restauranthas_one :subscriptionhas_many :attendees, through: :registrations
- Pass data to views explicitly:
render locals: { article: article }instead of relying on instance variables - Rails intelligently handles partials:
= render articleautomatically uses the_articlepartial - Render collections efficiently:
= render partial: 'review', collection: reviews - Add separators between items:
= render partial: employees, spacer_template: 'divider' - Use Rails helpers effectively:
- Links:
= link_to 'Create Event', new_event_path - Forms:
= form_with model: restaurant do |form| - Form fields:
= form.text_field :name
- Links:
- Rails 8's authentication generator creates a complete auth system:
bin/rails generate authentication - Handle logout properly:
= button_to 'Sign out', session_path, method: :delete if authenticated? - Allow public access selectively:
allow_unauthenticated_access only: %i[index show] - Use authentication helpers in views:
= link_to 'My Profile', profile_path if authenticated?= link_to 'Sign in', new_session_path unless authenticated?
- Leverage Active Storage for file uploads:
- Model:
has_one_attached :resume - Form:
= form.file_field :resume, accept: 'application/pdf' - Display:
= link_to 'Download Resume', employee.resume if employee.resume.attached?
- Model:
- Process uploaded files in controllers:
csv_file = params[:import_file] if csv_file.present? CSV.parse(csv_file.read, headers: true) do |row| # Process each row end end
- Send emails with Action Mailer:
- Generate mailer:
bin/rails g mailer Booking confirmation - Send from mailer:
mail to: booking.email, subject: 'Booking Confirmation' - Templates live in
app/views/booking_mailer/confirmation.html.erb
- Generate mailer:
- Generate secure tokens for various purposes:
- Model:
generates_token_for :password_reset, expires_in: 15.minutes - Find by token:
Employee.find_by_token_for(:password_reset, params[:token]) - Generate token:
employee.generate_token_for(:password_reset)
- Model:
- Write request specs instead of controller specs for more comprehensive testing