Skip to content

Instantly share code, notes, and snippets.

@nicieja
Created June 16, 2025 22:08
Show Gist options
  • Select an option

  • Save nicieja/c4006b73fc58b959cbdf91d3bcedc1c3 to your computer and use it in GitHub Desktop.

Select an option

Save nicieja/c4006b73fc58b959cbdf91d3bcedc1c3 to your computer and use it in GitHub Desktop.

Technology Stack

  • Ruby
  • Rails
  • PostgreSQL
  • RSpec

Code Style and Conventions

  • 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, .last instead 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

Common Commands

  • 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

  • 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)

Frontend Architecture

  • Prioritize server-side solutions over JavaScript
    1. Keep domain logic in Ruby where it belongs
    2. 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)

Models

  • Organize code into clear bounded contexts
  • Each domain should have its own models, services, and business rules
  • Follow this consistent structure for model organization:
    1. Module inclusions
    2. Attribute definitions
    3. Associations
    4. Validations
    5. Callbacks
    6. Scopes
    7. Public methods
    8. Private methods
  • Always declare attribute types explicitly: attribute :status, :string, default: 'pending'
  • Use inquiry methods for cleaner code: inquirer :status allows subscription.status.active? instead of subscription.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

Naming Conventions

  • Choose clear, descriptive names that make sense in context
  • Avoid redundant prefixes (use amount not payment_amount in 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

Authorization Policies

  • 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 Best Practices

  • Rails projects follow a standard directory structure: app, config, db, etc.
  • Leverage modern Ruby features where they improve clarity (pattern matching, endless methods)
  • Use delegate to 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.all
    • Restaurant.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 IN clauses 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 limit and offset: 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? and many? 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: true
    • validates :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_email
    • before_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 :abort for gentler failures
  • Define model relationships clearly:
    • has_many :reviews, dependent: :destroy
    • belongs_to :restaurant
    • has_one :subscription
    • has_many :attendees, through: :registrations
  • Pass data to views explicitly: render locals: { article: article } instead of relying on instance variables
  • Rails intelligently handles partials: = render article automatically uses the _article partial
  • 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
  • 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?
  • 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 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)
  • Write request specs instead of controller specs for more comprehensive testing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment