# Deployment and DevOps Standards for Ruby
This document outlines the deployment and DevOps standards for Ruby projects, aiming to create robust, maintainable, and scalable applications. It covers build processes, CI/CD pipelines, production considerations, and relevant technologies specific to the Ruby ecosystem.
## 1. Build Processes and Dependency Management
### 1.1 Dependency Management with Bundler
Bundler is the standard dependency manager for Ruby projects. Using it correctly ensures consistent environments across development, testing, and production.
**Do This:** Always use Bundler to manage your project's dependencies.
**Don't Do This:** Manually install gems or rely on system-installed gems for production deployments.
**Why:** Using Bundler guarantees that all environments use the exact same gem versions, preventing "it works on my machine" issues.
**Example:**
"""ruby
# Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 7.0'
gem 'pg'
gem 'puma'
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
end
group :development do
gem 'web-console'
end
"""
* **Actionable Standard:** Ensure a "Gemfile" and "Gemfile.lock" are present in your project. The "Gemfile.lock" MUST be committed to version control.
* **Actionable Standard:** Use "bundle install" for installing dependencies. Run "bundle update" judiciously to update gem versions and test thoroughly.
* **Actionable Standard:** Configure Bundler to install gems into a "vendor/bundle" directory or use a system-wide gemset via "rvm" or "rbenv".
* **Actionable Standard:** Employ "bundle exec" when running executables like "rails server" or "rake db:migrate" to ensure they use the gems specified in the "Gemfile".
**Anti-pattern:** Forgetting to run "bundle install" after cloning a repository or after modifying the "Gemfile". This can lead to missing dependencies and runtime errors.
### 1.2 Build Automation with Rake
Rake is a Ruby-based build tool, similar to Make in other languages. Use it for automating common tasks such as database migrations, asset compilation, and code analysis.
**Do This:** Define Rake tasks for common deployment and maintenance tasks.
**Don't Do This:** Manually execute deployment steps or rely on scripts scattered throughout the project.
**Why:** Rake provides a consistent and repeatable way to perform tasks, reducing the risk of human error during deployments.
**Example:**
"""ruby
# Rakefile
require 'rake'
require 'rake/tasklib'
namespace :deploy do
desc "Deploy to production"
task :production do
puts "Deploying to production..."
# Add deployment steps here
end
desc "Migrate the database"
task :migrate do
puts "Migrating the database..."
sh 'bundle exec rails db:migrate' # Correct use of bundle exec
end
end
"""
* **Actionable Standard:** Create a "Rakefile" to define your project's build and deployment tasks.
* **Actionable Standard:** Use namespaces to organize related tasks (e.g., "deploy:production", "db:migrate").
* **Actionable Standard:** Utilize shell commands (e.g., "sh", "exec") within Rake tasks to execute system commands. Always use "bundle exec" prefix.
* **Actionable Standard:** Document each Rake task with a descriptive "desc" for clarity.
**Anti-Pattern:** Manually running database migrations on production servers can lead to inconsistencies and errors, especially in multi-server environments. Automate these tasks using Rake.
## 2. Continuous Integration and Continuous Deployment (CI/CD)
### 2.1 CI/CD Pipeline Setup
A CI/CD pipeline automates the process of building, testing, and deploying your Ruby application. Popular CI/CD tools include Jenkins, GitLab CI, GitHub Actions, CircleCI, and Travis CI.
**Do This:** Integrate your Ruby project with a CI/CD pipeline.
**Don't Do This:** Manually build, test, and deploy your application each time you make changes.
**Why:** CI/CD reduces deployment risks, speeds up the development process, and ensures that code is always in a deployable state.
**Example (GitHub Actions):**
"""yaml
# .github/workflows/main.yml
name: Ruby CI/CD
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
ports: ['5432:5432']
env:
POSTGRES_USER: postgres
POSTGRES_DB: your_app_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2 # Example Ruby version
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Set up database
run: |
bundle exec rails db:create
bundle exec rails db:schema:load
- name: Run tests
run: bundle exec rspec
deploy:
needs: build
if: github.ref == 'refs/heads/main' # Only deploy from main
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.12.12 # Use specific version
with:
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
heroku_app_name: your-heroku-app
heroku_email: your-email@example.com
"""
* **Actionable Standard:** Define CI/CD pipelines in YAML files (e.g., ".gitlab-ci.yml", ".github/workflows/main.yml").
* **Actionable Standard:** Automate the following steps in the pipeline: code checkout, dependency installation, code linting (using RuboCop), running tests (unit, integration, system), and deployment.
* **Actionable Standard:** Use environment variables for sensitive information like API keys, database passwords, and deployment credentials. Store these secrets securely via the CI/CD platform's secrets management.
* **Actionable Standard:** Implement automated rollbacks in case of deployment failures.
**Anti-pattern:** Skipping automated tests in the CI/CD pipeline can lead to deploying broken code to production, causing downtime and user frustration.
### 2.2 Code Analysis and Linting
RuboCop is a popular static code analyzer and formatter for Ruby. Using it helps enforce coding standards and identify potential code quality issues.
**Do This:** Integrate RuboCop into your CI/CD pipeline.
**Don't Do This:** Ignore RuboCop warnings or manually fix code style issues.
**Why:** RuboCop ensures consistent coding style, catches potential bugs, and maintains code quality, which is essential for maintainability.
**Example:**
"""yaml
# .github/workflows/main.yml (modified)
name: Ruby CI/CD
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Run RuboCop
run: bundle exec rubocop
- name: Set up database
run: |
bundle exec rails db:create
bundle exec rails db:schema:load
- name: Run tests
run: bundle exec rspec
"""
* **Actionable Standard:** Include a ".rubocop.yml" file in your project to configure RuboCop's rules and exclusions.
* **Actionable Standard:** Customize RuboCop rules according to your team's coding conventions (e.g., line length, indentation). Airbnb's Ruby style guide is also a good reference point.
* **Actionable Standard:** Automatically fix RuboCop violations where possible using "rubocop -a".
* **Actionable Standard:** If you choose to ignore specific RuboCop offenses, document the reason clearly in the code or ".rubocop.yml".
**Anti-pattern:** Disabling RuboCop entirely or ignoring its warnings undermines code quality and consistency.
## 3. Production Considerations
### 3.1 Application Servers – Puma and Unicorn
Puma and Unicorn are popular application servers for Ruby on Rails applications. Puma supports multi-threading due to Ruby's Global VM Lock (GVL) limitations, while Unicorn is a pre-forking server.
**Do This:** Choose an application server based on your application's requirements and infrastructure. Puma is generally recommended when using modern Ruby versions.
**Don't Do This:** Rely on the WEBrick server (the default in Rails development) for production deployments.
**Why:** Production-grade application servers like Puma and Unicorn are designed to handle high traffic, concurrency, and resource management, which WEBrick is not.
**Example (Puma Configuration):**
"""ruby
# config/puma.rb
workers Integer(ENV['WEB_CONCURRENCY'] || 2) # Number of worker processes
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count
preload_app!
rackup DefaultRackup
port ENV['PORT'] || 3000 # Utilize environment variable for port.
environment ENV['RAILS_ENV'] || 'development' # Set Rails environment
# On worker boot, add connections that pre-exist to the pool
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
before_fork do
require 'puma_worker_killer'
PumaWorkerKiller.config do |config|
config.ram = 1024 # mb
config.frequency = 5 # seconds
config.percent_usage = 0.98
config.rolling_restart_frequency = 6 * 3600 # 6 hours in seconds
config.reaper_status_logs = true # setting this to false will not log lines like:
# PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.
end
PumaWorkerKiller.start
end
"""
* **Actionable Standard:** Configure Puma with the appropriate number of workers and threads based on your server's CPU cores and memory.
* **Actionable Standard:** Use environment variables (e.g., "WEB_CONCURRENCY", "RAILS_MAX_THREADS", "PORT") to configure Puma in different environments.
* **Actionable Standard:** Integrate Puma with a process manager like Systemd or Supervisor to ensure automatic restarts in case of crashes.
* **Actionable Standard:** Consider using "puma-worker-killer" to automatically restart Puma workers that consume excessive memory. Helps prevent memory leaks from crashing the entire server.
**Anti-pattern:** Statically defining the number of workers and threads in the Puma configuration file makes it difficult to adapt to different environments and server sizes.
### 3.2 Database Connection Pooling
Database connection pooling improves the performance of Ruby applications by reusing database connections instead of creating new ones for each request.
**Do This:** Configure database connection pooling in your "database.yml" file.
**Don't Do This:** Use a single database connection for all requests.
**Why:** Connection pooling reduces the overhead of establishing new database connections, which can be a significant bottleneck for high-traffic applications.
**Example:**
"""yaml
# config/database.yml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: your_username
password: your_password
development:
<<: *default
database: your_app_development
test:
<<: *default
database: your_app_test
production:
<<: *default
database: your_app_production
url: <%= ENV['DATABASE_URL'] %> # Use database URL from environment variables
"""
* **Actionable Standard:** Set the "pool" size in "database.yml" to an appropriate value based on your application's concurrency and database server capacity. The pool size generally should be the same as the maximum number of threads you have configured for Puma or the expected total connections from all unicorn workers.
* **Actionable Standard:** Use a database URL format instead of hardcoding credentials directly inside "database.yml".
* **Actionable Standard:** Monitor your database connection usage to identify potential connection leaks or bottlenecks.
**Anti-pattern:** Not configuring database connection pooling can lead to slow response times and database server overload.
### 3.3 Logging and Monitoring
Proper logging and monitoring are crucial for understanding application behavior, identifying performance issues, and diagnosing errors.
**Do This:** Implement comprehensive logging and monitoring in your Ruby applications.
**Don't Do This:** Rely on "puts" statements for logging or ignore application metrics.
**Why:** Logging provides valuable insights into application behavior, while monitoring helps identify and diagnose performance issues and errors.
**Example (Logging with Rails):**
"""ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :log_request
private
def log_request
Rails.logger.info "Request: #{request.method} #{request.path}" # Detailed logging
Rails.logger.debug "Parameters: #{params.inspect}"
end
end
# Using logger inside model
class User < ApplicationRecord
after_create :log_creation
private
def log_creation
Rails.logger.info "User created with id: #{self.id}"
end
end
# In the console to log directly:
Rails.logger.info "Something happened here important"
"""
* **Actionable Standard:** Use Rails' built-in logger ( "Rails.logger" ) for structured logging at different levels ( "debug", "info", "warn", "error", "fatal" ).
* **Actionable Standard:** Log essential events, such as requests, database queries, errors, and background job executions.
* **Actionable Standard:** Integrate with a centralized logging system like ELK Stack (Elasticsearch, Logstash, Kibana), Splunk, or Papertrail.
* **Actionable Standard:** Implement application performance monitoring (APM) using tools like New Relic, Datadog, or Scout APM. Focus on metrics like response time, throughput, error rates, and database query performance.
* **Actionable Standard:** Use structured logging (e.g., JSON format) to faciliter parsing and analysis.
* **Actionable Standard:** Monitor system-level metrics, such as CPU usage, memory usage, disk I/O, and network traffic.
**Anti-pattern:** Logging sensitive information like passwords or API keys can create security vulnerabilities. Implement proper redaction or masking of sensitive data in logs.
### 3.4 Security Best Practices
Security is paramount in production environments. The following security best practices are essential:
* **Actionable Standard:** Keep Ruby, Rails, and all gems up-to-date with the latest security patches. Automate this process where possible.
* **Actionable Standard:** Follow secure coding practices to prevent common vulnerabilities like SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF).
* **Actionable Standard:** Use strong and unique passwords for all user accounts. Implement multi-factor authentication (MFA) where possible.
* **Actionable Standard:** Enforce HTTPS for all communication to protect data in transit. Configure HSTS (HTTP Strict Transport Security) to prevent man-in-the-middle attacks.
* **Actionable Standard:** Regularly review and update security configurations, including firewalls, intrusion detection systems, and access controls.
**Anti-pattern:** Ignoring security vulnerabilities or failing to apply security patches exposes applications to potential attacks. Use tools like Bundler Audit to check for known vulnerabilities in your dependencies.
### 3.5 Environment Variables
Using environment variables for configuration ensures flexibility and security in different environments.
**Do This:** Store configuration settings (e.g., database credentials, API keys) in environment variables.
**Don't Do This:** Hardcode configuration settings in code or configuration files.
**Why:** Environment variables provide a secure and flexible way to configure applications without modifying code.
**Example:**
"""ruby
# config/database.yml (as shown previously)
production:
<<: *default
database: your_app_production
url: <%= ENV['DATABASE_URL'] %>
# Rails secret key
# config/secrets.yml or config/credentials.yml.enc
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> #Example in older version of rails
"""
* **Actionable Standard:** Use a gem like "dotenv" in development to load environment variables from a ".env" file. *Never* commit the ".env" file to version control.
* **Actionable Standard:** Configure your deployment environment (e.g., Heroku, AWS) to set environment variables for production.
* **Actionable Standard:** Employ a secrets management solution like HashiCorp Vault or AWS Secrets Manager to securely store and access sensitive environment variables.
**Anti-pattern:** Committing sensitive credentials to version control poses a significant security risk.
## 4. Containerization and Infrastructure as Code (IaC)
### 4.1 Docker and Containerization
Docker allows you to package your Ruby application and its dependencies into a standardized unit for software development. This provides consistency across different environments.
**Do This:** Containerize your Ruby application using Docker.
**Don't Do This:** Deploy your application directly to virtual machines without containerization.
**Why:** Docker simplifies deployments, ensures consistency across environments, and improves resource utilization.
**Example (Dockerfile):**
"""dockerfile
# Dockerfile
FROM ruby:3.2-slim # Use a slim Ruby image
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4 --retry 3 --without development test
COPY . .
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000 # Example port
CMD ["rails", "server", "-b", "0.0.0.0"] #Or another production-appropriate command
"""
* **Actionable Standard:** Create a "Dockerfile" to define the environment for your Ruby application.
* **Actionable Standard:** Use multi-stage builds to reduce the size of the final Docker image by separating build dependencies from runtime dependencies when using interpreted languages like Ruby.
* **Actionable Standard:** Utilize a ".dockerignore" file to exclude unnecessary files and directories from the Docker image (e.g., "log/", "tmp/", "test/").
* **Actionable Standard:** Use Docker Compose for local development to manage multi-container applications (e.g., Rails app with a PostgreSQL database).
**Anti-pattern:** Building large Docker images with unnecessary dependencies increases deployment time and resource consumption.
### 4.2 Infrastructure as Code
Infrastructure as Code (IaC) involves managing and provisioning infrastructure through code rather than manual processes. Terraform and CloudFormation are popular IaC tools.
**Do This:** Manage your infrastructure using IaC tools.
**Don't Do This:** Manually provision and configure servers.
**Why:** IaC automates infrastructure management, improves consistency, and enables infrastructure to be version controlled along with application code.
* **Actionable Standard:** Define your infrastructure resources (e.g., servers, databases, networks) as code using Terraform, CloudFormation, or similar tools.
* **Actionable Standard:** Store your IaC code in version control along with your application code.
* **Actionable Standard:** Use CI/CD pipelines to automate infrastructure deployments and updates.
**Anti-pattern:** Manually configuring servers can lead to inconsistencies and configuration drift, making it difficult to manage and maintain infrastructure.
These standards provide a comprehensive guide for deploying and managing Ruby applications in production, ensuring robustness, scalability, and maintainability. By following these guidelines, development teams can improve their deployment processes, reduce risks, and deliver high-quality software.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# State Management Standards for Ruby This document outlines the standards for managing state in Ruby applications. It covers approaches to managing application state, data flow, reactivity, and how these principles apply specifically to Ruby. It also includes modern approaches and patterns based on the latest Ruby version and features. ## 1. Introduction to State Management in Ruby State management involves controlling and maintaining the application's data over time. In Ruby, this can range from simple variable assignments within a class to complex data flows in web applications using frameworks like Rails. Effective state management enables predictable application behavior, easier debugging, and improved performance. ### 1.1 Types of State Understanding different types of state is crucial: * **Local State:** State confined to a specific method, block, or object instance. * **Application State:** State shared across multiple parts of the application, accessible by different objects. * **Persistent State:** State stored in a database or other form of persistent storage. * **UI State:** State that manages the visual elements and user interactions in a graphical interface. ### 1.2 Managing Complexity As applications grow, state management becomes more complex. Poorly managed state can lead to: * **Bugs:** Difficult to reproduce or trace due to unpredictable state changes. * **Performance Issues:** Unnecessary state updates or inefficient data access. * **Maintainability Problems:** Code that is hard to understand, modify, or extend. ## 2. General Principles These principles guide state management in Ruby applications. * **Single Source of Truth:** Define and maintain a single, authoritative source for each piece of data. * **Do This:** Ensure that a particular piece of information has one clearly defined origin. Avoid duplication of data. * **Don't Do This:** Store the same information in multiple, unsynchronized locations. * **Why:** Reduces inconsistencies and simplifies updates. Improves data integrity. * **Immutability:** Prefer immutable data structures when possible. * **Do This:** Use "freeze" to make objects immutable after initialization. * **Don't Do This:** Mutate objects directly when the original value is still needed. * **Why:** Simplifies reasoning about state changes and prevents unintended side effects. * **Explicit State Transitions:** Clearly define how the application moves from one state to another. * **Do This:** Use state machines or similar patterns to model state transitions explicitly. * **Don't Do This:** Implicitly change state through loosely coupled actions. * **Why:** Improves traceability and control over application behavior. * **Minimize Shared State:** Reduce the amount of state shared between different parts of the application. * **Do This:** Use dependency injection to isolate components and pass necessary state explicitly. * **Don't Do This:** Rely on global variables or shared mutable objects. * **Why:** Reduces coupling and makes components more independent and reusable. ## 3. State Management Techniques in Ruby ### 3.1 Local State Local state resides within methods or object instances. Proper scoping and encapsulation are crucial. * **Variables:** Use local variables for temporary data. """ruby def calculate_area(width, height) area = width * height # local variable puts "Area: #{area}" area end """ * **Instance Variables:** Store object-specific data. """ruby class Rectangle def initialize(width, height) @width = width # instance variable @height = height # instance variable end def area @width * @height end end """ * **Encapsulation:** Restrict access to internal state using "private" and "protected" access modifiers. """ruby class BankAccount def initialize(balance) @balance = balance end def deposit(amount) @balance += amount end def withdraw(amount) @balance -= amount if amount <= @balance end private # Restrict direct access def current_balance @balance end end """ ### 3.2 Application State Management Managing global state requires careful consideration. * **Global Variables:** Avoid direct use of "$global_variables". They can lead to naming conflicts and unpredictable behavior. * **Don't Do This:** """ruby $app_name = "My Application" # Avoid """ * **Constants:** Suitable for immutable configuration values. """ruby APP_VERSION = "1.2.3" """ * **Singleton Pattern:** Ensure a single instance of a class with global access. """ruby require 'singleton' class Configuration include Singleton attr_accessor :settings def initialize @settings = {} end end # Usage Configuration.instance.settings[:api_key] = "YOUR_API_KEY" puts Configuration.instance.settings[:api_key] """ * **Why:** Provides a controlled way to access shared state. * **Dependency Injection:** Pass dependencies explicitly to avoid tight coupling and make classes testable. """ruby class ReportGenerator def initialize(data_source) @data_source = data_source end def generate data = @data_source.fetch_data # ... generate report end end # Usage data_source = DatabaseDataSource.new report_generator = ReportGenerator.new(data_source) report_generator.generate """ * **Service Objects:** Encapsulate business logic and manage related state transitions. """ruby class CreateUserService def call(params) user = User.new(params) if user.save # ... send welcome email, etc. Result.new(success: true, user: user) else Result.new(success: false, errors: user.errors) end end end # Usage result = CreateUserService.new.call(name: "John Doe", email: "john@example.com") if result.success? puts "User created: #{result.user.name}" else puts "Error: #{result.errors.full_messages.join(', ')}" end """ ### 3.3 Persistent State Management For data that needs to be stored and retrieved, databases are typically used. * **Active Record:** Rails' ORM handles much of the persistence logic. * **Do This:** Define models with appropriate attributes and validations. * **Don't Do This:** Bypass the ORM for common database operations without a compelling reason. """ruby class User < ApplicationRecord validates :email, presence: true, uniqueness: true has_many :posts end """ * **Data Migrations:** Use migrations to manage database schema changes. * **Do This:** Write idempotent migrations that can be run multiple times without errors. * **Don't Do This:** Manually modify database schemas without a migration. ### 3.4 Concurrency and State Managing state concurrently requires special attention. * **Threads:** Ruby's native threads are limited by the Global Interpreter Lock (GIL). Prefer using them for I/O-bound tasks. For CPU-bound parallel processing, consider using processes. """ruby threads = [] 10.times do |i| threads << Thread.new(i) do |thread_num| puts "Thread #{thread_num}: Starting" sleep(rand(1..3)) # Simulate some work puts "Thread #{thread_num}: Finishing" end end threads.each(&:join) puts "All threads finished." """ * **Mutexes:** Use mutexes to protect shared state from concurrent access. """ruby require 'thread' class Counter def initialize @count = 0 @mutex = Mutex.new end def increment @mutex.synchronize do @count += 1 end end def value @count end end counter = Counter.new threads = [] 10.times do threads << Thread.new do 1000.times do counter.increment end end end threads.each(&:join) puts "Counter value: #{counter.value}" """ * **Actors:** The "Celluloid" gem provides an actor model for concurrent state management. * **Why:** Actors provide a higher-level abstraction for concurrency, reducing the risk of race conditions and deadlocks. ## 4. State Management in Rails Rails offers several mechanisms for managing state. ### 4.1 ActiveRecord Models Models encapsulate the state of individual records in the database. * **Attributes:** Define attributes to represent the column values of a database table. * **Associations:** Manage relationships between different models. * **Callbacks:** Trigger actions before or after state changes (e.g., before_save, after_create). Use these judiciously as they can introduce hidden side effects and make debugging harder. """ruby class Article < ApplicationRecord belongs_to :author has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } before_save :normalize_title private def normalize_title self.title = title.titleize end end """ ### 4.2 Sessions Sessions store data related to a specific user across multiple requests. * **Cookies:** Sessions are typically implemented using cookies. * **Security:** Protect session data by setting secure and HttpOnly flags on cookies. * **Storage:** Rails supports various session storage options (e.g., cookies, database, memcached). Consider the performance and security implications of each option. """ruby # Setting a session value session[:user_id] = user.id # Retrieving a session value user_id = session[:user_id] # Clearing a session value session[:user_id] = nil # Resetting the entire session reset_session """ ### 4.3 Caching Caching improves performance by storing frequently accessed data in memory. * **Fragment Caching:** Cache parts of a view. """erb <% cache @article do %> <%= render @article %> <% end %> """ * **Action Caching:** Cache the entire response of an action. * **Low-Level Caching:** Use the "Rails.cache" API for more granular control. """ruby Rails.cache.fetch("article:#{@article.id}", expires_in: 12.hours) do @article.content end """ ### 4.4 Query Objects Encapsulate complex database queries to keep controllers and models clean. """ruby class ArticlesByAuthorQuery def initialize(author) @author = author end def call Article.where(author: @author).published.order(created_at: :desc) end end # Usage articles = ArticlesByAuthorQuery.new(current_user).call """ ### 4.5 State Machines For models with complex state transitions, use a state machine gem like "aasm" or "statesman". """ruby class Order < ApplicationRecord include AASM aasm column: :state do # default column: aasm_state state :pending, initial: true state :processing state :shipped state :delivered state :cancelled event :process do transitions from: :pending, to: :processing end event :ship do transitions from: :processing, to: :shipped end event :deliver do transitions from: :shipped, to: :delivered end event :cancel do transitions from: [:pending, :processing], to: :cancelled end end end """ ## 5. Modern Approaches Using more contemporary design patterns can drastically improve state in Ruby web applications and APIs. ### 5.1 Redux/Flux-inspired patterns While direct ports of Javascript frameworks like Redux or Flux are not common in Ruby (largely because Ruby is often server-side), the underlying principles of unidirectional data flow and centralized state management can be valuable. * **Centralized Store:** A single source of truth for application state. This is often implemented as a Ruby class or module. * **Actions:** Plain Ruby objects (or classes representing commands) that describe an intent to change the state. * **Reducers:** Pure functions that take the current state and an action and return the new state. These should be free of side effects. * **Subscriptions:** Mechanisms for components to be notified when the state changes. This allows parts of the application to react to state updates. """ruby # Example (simplified) # Central Store class AppStore attr_reader :state def initialize @state = { count: 0 } # Initial state @listeners = [] end def dispatch(action) @state = reducer(@state, action) publish_changes end def subscribe(listener) @listeners << listener end private def reducer(state, action) case action[:type] when 'INCREMENT' { count: state[:count] + 1 } when 'DECREMENT' { count: state[:count] - 1 } else state # Default: return current state end end def publish_changes @listeners.each { |listener| listener.call(@state) } end end # Example usage store = AppStore.new # Subscribe to state changes store.subscribe(lambda { |state| puts "Count updated: #{state[:count]}" }) # Dispatch actions store.dispatch({ type: 'INCREMENT' }) # Output: Count updated: 1 store.dispatch({ type: 'DECREMENT' }) # Output: Count updated: 0 """ * **Benefits:** Highly predictable state updates, easier debugging, improved testability. * **Drawbacks:** More boilerplate code, can be overkill for very simple applications. ### 5.2 Event Sourcing Instead of storing the current state of an application entity directly, event sourcing stores a sequence of events that represent changes to the state. The current state can be derived by replaying these events. * **Events:** Immutable records of something that happened in the application (e.g., "OrderCreated", "ItemAddedToCart"). * **Event Store:** A database or specialized storage system for persisting events. * **Projections:** Code that consumes events to build a read-optimized view of the data. This decouples the write (event) side from the read side. * **Benefits:** Auditability, temporal queries (e.g., "What was the state of the order 2 days ago?"), easier debugging of complex state transitions, ability to rebuild state accurately * **Drawbacks:** Increased complexity, requires a different mindset, potentially more read-side performance challenges ### 5.3 CQRS (Command Query Responsibility Segregation) Separates read and write operations, improving performance and scalability. * **Commands:** Objects that represent an intent to change the state. * **Queries:** Objects that retrieve data without modifying the state. * **Benefits:** Optimized read and write paths, improved performance, better scalability, simplified data models. * **Drawbacks:** Increased complexity, requires careful design. ## 6. Anti-Patterns Avoid these common pitfalls. * **God Classes:** Classes that manage too much state and logic. * **Do This:** Break down large classes into smaller, more focused classes. * **Don't Do This:** Keep adding responsibilities to a single class. * **Shotgun Surgery:** Making changes to multiple unrelated parts of the code to achieve a single goal. * **Do This:** Refactor code to reduce dependencies and improve cohesion. * **Don't Do This:** Scatter changes across the codebase. * **Global State Abuse:** Over-reliance on global variables or mutable shared objects. * **Do This:** Use dependency injection or service objects to manage dependencies. * **Don't Do This:** Directly access global state from multiple parts of the application. * **Ignoring Immutability:** Mutating objects when the original value is still needed. * **Do This:** Use immutable data structures or defensive copying. * **Don't Do This:** Modify objects in place. ## 7. Conclusion Effective state management is crucial for building maintainable, scalable, and reliable Ruby applications. By following these standards and best practices, developers can manage state effectively, reduce bugs, and improve application performance. Use the right tool for the job, and continuously refine your state management strategies as your application evolves.
# API Integration Standards for Ruby This document outlines the standards and best practices for integrating with backend services and external APIs in Ruby projects. It focuses on modern approaches, maintainability, performance, and security, tailored for the latest Ruby version. ## 1. Architectural Considerations ### 1.1 Centralized API Client Logic **Standard:** Encapsulate API interaction logic within dedicated modules or classes. Avoid scattering API calls throughout the application. **Do This:** """ruby # app/lib/external_api/user_service.rb module ExternalApi class UserService BASE_URL = 'https://external-api.com'.freeze def self.fetch_user(user_id) response = HTTParty.get("#{BASE_URL}/users/#{user_id}") raise "API Error: #{response.code}" unless response.success? JSON.parse(response.body) rescue => e Rails.logger.error("Error fetching user: #{e.message}") nil end def self.create_user(user_data) response = HTTParty.post("#{BASE_URL}/users", body: user_data.to_json, headers: { 'Content-Type' => 'application/json' }) raise "API Error: #{response.code}" unless response.success? JSON.parse(response.body) rescue => e Rails.logger.error("Error creating user: #{e.message}") nil end end end # Usage: user = ExternalApi::UserService.fetch_user(123) if user puts "User: #{user['name']}" else puts "Failed to fetch user." end new_user_data = { name: "John Doe", email: "john.doe@example.com" } created_user = ExternalApi::UserService.create_user(new_user_data) if created_user puts "New user ID: #{created_user['id']}" else puts "Failed to create user." end """ **Don't Do This:** """ruby # Avoid scattering API calls directly in controllers or models # Bad example: directly calling API from controller class UsersController < ApplicationController def show response = HTTParty.get("https://external-api.com/users/#{params[:id]}") @user = JSON.parse(response.body) # fragile to changes. render 'show' rescue => e flash[:error] = "Failed to fetch user: #{e.message}" redirect_to root_path end end """ **Why:** * **Maintainability:** Centralizing API logic makes it easier to update API endpoints, authentication methods, and error handling across the application. * **Testability:** Facilitates unit testing of API interaction logic without needing to mock external services throughout the codebase. * **Readability:** Isolates complex API interaction logic, making controllers and models cleaner and more focused on their respective responsibilities. ### 1.2 Abstracting the Client Interaction via Adapters **Standard:** Create Adapters to abstract interaction with external API clients. These Adapters should use common interfaces that are part of your application domain, to avoid tightly coupling with external API response structures. **Do This:** """ruby # app/adapters/user_adapter.rb module Adapters class UserAdapter def self.fetch_user(user_id) api_response = ExternalApi::UserService.fetch_user(user_id) return nil unless api_response build_user(api_response) end def self.create_user(user_data) api_response = ExternalApi::UserService.create_user(user_data) return nil unless api_response build_user(api_response) end private def self.build_user(api_response) # Map the external API response to internal data structures (e.g., a User object) { id: api_response['id'], name: api_response['name'], email: api_response['email'] } end end end # Usage user = Adapters::UserAdapter.fetch_user(123) if user puts "User Name: #{user[:name]}" else puts "Could not fetch user" end new_user_data = { name: "Jane Doe", email: "jane.doe@example.com" } created_user = Adapters::UserAdapter.create_user(new_user_data) if created_user puts "Created User ID: #{created_user[:id]}" else puts "Could not create user." end """ **Don't Do This:** """ruby # Avoid directly using the external API response everywhere in the application. # This locks your implementation to the external naming, which can change in the future. class UsersController < ApplicationController def show user = Adapters::UserAdapter.fetch_user(params[:id]) #Still using adapter correctly but... if user @user_name = user[:name] # Directly accessing external User's name @user_email = user[:email] # Directly accessing external User's email render 'show' else flash[:error] = "Failed to fetch user" redirect_to root_path end end end """ **Why:** * **Decoupling:** Insulates the application from direct dependence on the external API schema. Any changes in the external API only require modifying the adapter, not the entire application. * **Flexibility:** Simplifies the process of switching to a different API provider or version in the future. * **Data Transformation:** Provides a centralized location for transforming external API data into a format that aligns with the application's internal data models. ### 1.3 Asynchronous API Interactions **Standard:** Use asynchronous processing for long-running or non-critical API calls to avoid blocking the main application thread and improve responsiveness. **Do This:** """ruby # Using ActiveJob for asynchronous processing # app/jobs/process_user_creation_job.rb class ProcessUserCreationJob < ApplicationJob queue_as :default def perform(user_data) Adapters::UserAdapter.create_user(user_data) # API Interaction happens here. Log/retry in this job Rails.logger.info("User created asynchronously: #{user_data[:email]}") rescue => e Rails.logger.error("Error creating user asynchronously: #{e.message}") # Optionally, retry the job or notify administrators retry_job(wait: 5.minutes) if attempts < 5 #Example retries end end # In your controller or service: new_user_data = { name: "Async User", email: "async.user@example.com" } ProcessUserCreationJob.perform_later(new_user_data) # Enqueue job puts "User creation initiated asynchronously." """ **Don't Do This:** """ruby # Avoid making synchronous API calls in critical request paths class UsersController < ApplicationController def create user_data = params.require(:user).permit(:name, :email) user = Adapters::UserAdapter.create_user(user_data) # Synchronous and blocking, can cause performance issues if user redirect_to user_path(user[:id]), notice: 'User created successfully.' else flash[:error] = "Failed to create user" render :new end end end """ **Why:** * **Performance:** Prevents API calls from blocking the user interface or application workers. * **Scalability:** Allows the application to handle more concurrent requests by offloading API processing to background jobs. * **Resilience:** Enables retries or alternative processing strategies in case of API failures without directly impacting the user experience. ## 2. Implementation Details ### 2.1 HTTP Client Libraries **Standard:** Use a robust and well-maintained HTTP client library like "httparty", "faraday", or Ruby's built-in "Net::HTTP" for making API requests. "Faraday" is generally preferred for its flexibility and middleware support. **Do This (Faraday):** """ruby require 'faraday' require 'json' module ExternalApi class ApiClient def self.make_request(method, path, body: nil, params: nil, headers: {}) connection = Faraday.new(url: 'https://external-api.com') do |faraday| faraday.request :json # Encode request bodies as JSON faraday.response :json, content_type: /\bjson$/ # Parse JSON responses faraday.adapter Faraday.default_adapter # Use the default Net::HTTP adapter # Optional middleware for logging, retries, etc. end response = connection.public_send(method, path, body, headers) unless response.success? Rails.logger.error("API Error: #{response.status} - #{response.body}") raise "API Error: #{response.status}" end response.body rescue Faraday::Error => e Rails.logger.error("Faraday Error: #{e.message}") nil end end end # Usage user = ExternalApi::ApiClient.make_request(:get, "/users/123") puts user.inspect new_user_data = { name: "Faraday User", email: "faraday.user@example.com" } created_user = ExternalApi::ApiClient.make_request(:post, "/users", body: new_user_data) puts created_user.inspect """ **Don't Do This:** """ruby # Avoid manual socket handling or overly complex implementations require 'net/http' require 'uri' require 'json' # Example of overly complex manual request building def fetch_user(user_id) uri = URI("https://external-api.com/users/#{user_id}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true # Ensure SSL is used request = Net::HTTP::Get.new(uri.path) response = http.request(request) if response.is_a?(Net::HTTPSuccess) JSON.parse(response.body) else puts "Error: #{response.code} - #{response.message}" nil end rescue => e puts "Exception: #{e.message}" nil end user = fetch_user(123) puts user.inspect """ **Why:** * **Abstraction:** Handles low-level details of HTTP requests, simplifying the API interaction logic. * **Features:** Provides built-in support for features like request encoding, response parsing, timeouts, retries, and middleware. * **Security:** Offers secure HTTPS connections and protection against common vulnerabilities. ### 2.2 Data Serialization/Deserialization **Standard:** Use "JSON.parse" and "JSON.generate" (or corresponding methods in your chosen library) to serialize and deserialize data to/from JSON format. Ensure the data conforms to the API's expected structure. **Do This:** """ruby require 'json' user_data = { name: "Serialized User", email: "serialized.user@example.com" } json_data = JSON.generate(user_data) puts json_data # Output: {"name":"Serialized User","email":"serialized.user@example.com"} parsed_data = JSON.parse(json_data) puts parsed_data['name'] # Output: Serialized User """ **Don't Do This:** """ruby # Avoid manual string concatenation for building JSON payloads or parsing responses user_data = { name: "Concatenated User", email: "concatenated.user@example.com" } # Terrible Practice json_payload = "{ \"name\": \"" + user_data[:name] + "\", \"email\": \"" + user_data[:email] + "\" }" puts json_payload """ **Why:** * **Correctness:** Ensures data is properly formatted and encoded for transmission over the network. * **Security:** Prevents injection vulnerabilities by properly escaping and sanitizing data. * **Efficiency:** Optimized for performance, especially when handling large datasets. ### 2.3 Error Handling **Standard:** Implement robust error handling to gracefully manage API failures. Use "begin...rescue" blocks to catch exceptions, log errors, and provide informative feedback to the user or application. **Do This:** """ruby module ExternalApi class UserService def self.fetch_user(user_id) response = HTTParty.get("https://external-api.com/users/#{user_id}") raise "API Error: #{response.code}" unless response.success? JSON.parse(response.body) rescue => e Rails.logger.error("Error fetching user: #{e.message}") # Handle the error gracefully, e.g., return a default value or re-raise a custom exception nil end end end """ **Don't Do This:** """ruby # Example of swallowing errors without proper handling # This can lead to silent failures and hard-to-debug issues def fetch_user(user_id) begin response = HTTParty.get("https://external-api.com/users/#{user_id}") JSON.parse(response.body) rescue => e # Swallowing the exception. BAD! nil end end """ **Why:** * **Reliability:** Prevents API failures from crashing the application or corrupting data. * **Debuggability:** Provides detailed error messages and stack traces to help diagnose and resolve issues. * **User Experience:** Ensures users receive informative feedback when API calls fail, preventing confusion and frustration. ### 2.4 Authentication and Authorization **Standard:** Use secure authentication and authorization mechanisms when interacting with APIs that require it. Store API keys and credentials securely using environment variables, encrypted configuration files, or dedicated secrets management services, and use HTTPS when communicating with those services. **Do This:** """ruby # lib/external_api/secure_api.rb module ExternalApi class SecureApi API_KEY = ENV['EXTERNAL_API_KEY'] # Store API Key in ENV def self.fetch_data(resource) response = HTTParty.get("https://secure-api.com/#{resource}", headers: { 'X-API-Key' => API_KEY }) raise "API Error: #{response.code}" unless response.success? JSON.parse(response.body) rescue => e Rails.logger.error("Error fetching secure data: #{e.message}") nil end end end """ **Don't Do This:** """ruby # Avoid hardcoding API keys or credentials directly in source code def fetch_data(resource) api_key = "YOUR_API_KEY" # Hardcoded API Ket. DANGER! response = HTTParty.get("https://secure-api.com/#{resource}", headers: { 'X-API-Key' => api_key }) JSON.parse(response.body) rescue => e puts "Error: #{e.message}" nil end """ **Why:** * **Security:** Protects sensitive data and prevents unauthorized access to resources. * **Compliance:** Meets industry best practices and regulatory requirements for data protection. * **Maintainability:** Simplifies credential management and reduces the risk of accidentally exposing secrets. ### 2.5 Rate Limiting and Throttling **Standard:** Respect API rate limits and implement throttling mechanisms to prevent overloading external services. Use caching or queues to manage API requests and avoid exceeding rate limits. **Do This:** """ruby # Using a simple rate limiter with Redis require 'redis' module ExternalApi class RateLimitedApi RATE_LIMIT = 10 # requests per minute REDIS = Redis.new def self.fetch_data(resource) key = "api_rate_limit:#{Time.now.to_i / 60}" # Key for the current minute count = REDIS.incr(key) REDIS.expire(key, 60) unless REDIS.ttl(key) > -1 # Expire the key after 60 seconds if count > RATE_LIMIT Rails.logger.warn("Rate limit exceeded for API.") return "Rate limit exceeded. Try again later." end response = HTTParty.get("https://rate-limited-api.com/#{resource}") raise "API Error: #{response.code}" unless response.success? JSON.parse(response.body) rescue => e Rails.logger.error("Error fetching rate-limited data: #{e.message}") nil end end end """ **Don't Do This:** """ruby # Avoid making uncontrolled API requests without considering rate limits def fetch_data(resource) 100.times do # potentially exceeding rate limits response = HTTParty.get("https://rate-limited-api.com/#{resource}") puts response.code end rescue => e puts "Error: #{e.message}" nil end """ **Why:** * **Stability:** Prevents the application from being blocked or throttled by external services. * **Fairness:** Ensures that the application does not consume an unfair share of API resources. * **Cost Optimization:** Reduces the risk of incurring unexpected charges due to excessive API usage. ## 3. Testing ### 3.1 Unit Testing **Standard:** Use mocking and stubbing techniques to isolate API interaction logic for unit testing. Avoid making real API calls in unit tests. **Do This (RSpec + Webmock):** """ruby # spec/lib/external_api/user_service_spec.rb require 'rails_helper' require 'webmock/rspec' RSpec.describe ExternalApi::UserService do describe '.fetch_user' do it 'fetches a user from the API' do stub_request(:get, "https://external-api.com/users/123") .to_return(status: 200, body: '{ "id": 123, "name": "Test User" }', headers: { 'Content-Type' => 'application/json' }) user = ExternalApi::UserService.fetch_user(123) expect(user['name']).to eq('Test User') end it 'handles API errors' do stub_request(:get, "https://external-api.com/users/123").to_return(status: 500) expect(Rails.logger).to receive(:error).with(a_string_including("API Error: 500")) #Verify logging user = ExternalApi::UserService.fetch_user(123) expect(user).to be_nil end end end """ **Dont Do This:** """ruby # Example of making real API calls in unit tests # This creates brittle tests that are slow and dependent on external services RSpec.describe ExternalApi::UserService do describe '.fetch_user' do it 'fetches a user from the API' do # Making a real API call user = ExternalApi::UserService.fetch_user(123) expect(user).not_to be_nil end end end """ **Why:** * **Speed:** Unit tests execute quickly without the overhead of network communication. * **Reliability:** Unit tests are isolated from external dependencies, making them more predictable and less prone to failure. * **Consistency:** Unit tests provide consistent results regardless of the availability or state of external services. ### 3.2 Integration Testing **Standard:** Use integration tests (more limited in scope) to verify the interaction between the application and real or mocked external services. Be very selective about the number of 'real' integration tests, leaning on testing frameworks and mocks much moreso. **Do This:** """ruby # spec/integration/user_api_spec.rb require 'rails_helper' RSpec.describe 'User API Integration', type: :request do it 'fetches and displays a user' do # Assuming you have a test API endpoint that returns a known user get '/users/123' # Test actual routes with mocked out external requests. expect(response).to have_http_status(200) expect(response.body).to include('Test User') # Check data in full flow. end end """ **Don't Do This:** """ruby # Avoid relying solely on end-to-end tests that cover too much functionality # Rely on more granular tests with mocking unless absolutely necessary # This is usually too broad for a single integration test. RSpec.describe 'Full User Flow', type: :system do it 'creates, views, and updates a user' do # navigates through entire user flow, less testable and maintainable. visit '/users/new' fill_in 'Name', with: 'Test User' fill_in 'Email', with: 'test@example.com' click_button 'Save' expect(page).to have_content('User created successfully') # ... more interactions end end """ **Why:** * **Confidence:** Validates that the application correctly integrates with external services and handles real-world scenarios. * **Coverage:** Catches integration issues that may not be apparent in unit tests. * **Realism:** Provides a more realistic assessment of the application's behavior and performance. ## 4. Monitoring and Logging ### 4.1 Detailed Logging **Standard:** Implement detailed logging to track API requests, responses, errors, and performance metrics. Use structured logging formats (e.g., JSON) for easier analysis and aggregation. **Do This:** """ruby # Log API requests and responses with relevant details module ExternalApi class UserService def self.fetch_user(user_id) Rails.logger.info("Fetching user with ID: #{user_id}") response = HTTParty.get("https://external-api.com/users/#{user_id}") if response.success? Rails.logger.debug("API Response: #{response.body}") JSON.parse(response.body) else Rails.logger.error("API Error: #{response.code} - #{response.body}") nil end rescue => e Rails.logger.error("Exception while fetching user: #{e.message}") nil end end end """ **Don't Do This:** """ruby # Avoid minimal or inconsistent logging def fetch_user(user_id) response = HTTParty.get("https://external-api.com/users/#{user_id}") puts response.code # Insufficient logging JSON.parse(response.body) rescue => e puts "Error: #{e.message}" # Minimal information nil end """ **Why:** * **Visibility:** Provides insights into API usage patterns, performance bottlenecks, and potential issues. * **Troubleshooting:** Facilitates rapid identification and resolution of API-related problems. * **Auditing:** Enables tracking of API interactions for security and compliance purposes. ## 5. Versioning and Compatibility ### 5.1 API Versioning **Standard:** Use API versioning to manage changes to the API and maintain compatibility with older clients. Use URL-based versioning (e.g., "/api/v1/users") or header-based versioning (e.g., "Accept: application/vnd.api.v1+json"). **Do This (URL-based Versioning):** """ruby # config/routes.rb Rails.application.routes.draw do namespace :api do namespace :v1 do resources :users end end end """ **Don't Do This:** """ruby # Avoid making breaking changes to the API without versioning # This can cause compatibility issues with older clients get '/users/:id', to: 'users#show' # No versioning, risk of breaking changes """ **Why:** * **Compatibility:** Ensures that older clients continue to function correctly even after the API is updated. * **Flexibility:** Allows for introducing new features and changes without disrupting existing users. * **Clarity:** Provides a clear and consistent way to manage API evolution. ### 5.2 Graceful Degradation **Standard:** Implement graceful degradation strategies to handle situations where the API is unavailable or returns errors. Provide fallback mechanisms or informative error messages to minimize the impact on the user experience. **Do This:** """ruby # Example: implement retry logic with a fallback module ExternalApi class UserService def self.fetch_user(user_id, retries: 3) response = HTTParty.get("https://external-api.com/users/#{user_id}") JSON.parse(response.body) rescue => e if retries > 0 Rails.logger.warn("Retrying after error: #{e.message}, #{retries} retries remaining") sleep(2) # wait before retrying fetch_user(user_id, retries: retries - 1) # Recursive call with less retries else Rails.logger.error("Failed to fetch user after multiple retries: #{e.message}") return nil # Provide fallback when all retries fail end end end end """ **Don't Do This:** """ruby # Aoid failing silently without providing any fallback mechanisms def fetch_user(user_id) response = HTTParty.get("https://external-api.com/users/#{user_id}") JSON.parse(response.body) rescue => e # Catches the error but doesn't handle it. # Returning nil may break parts of the application expecting data. nil end """ **Why:** * **Resilience:** Ensures that the application remains functional even when external services are unavailable. * **User Experience:** Minimizes the impact of API failures on the user experience. * **Maintainability:** Simplifies the process of handling API-related issues and reduces the risk of cascading failures. ## 6. Security ### 6.1 Input Validation **Standard**: Always validate and sanitize data received from external APIs before using it within the application. This prevents vulnerability exploits and maintains data integrity. **Do This**: """ruby # Validate and sanitize data after receiving it from the API module ExternalApi class UserService def self.fetch_user(user_id) response = HTTParty.get("https://external-api.com/users/#{user_id}") user_data = JSON.parse(response.body) # Validate the user data (example) return nil unless user_data.is_a?(Hash) return nil unless user_data['name'].is_a?(String) && user_data['name'].length <= 255 # Can also use Rails validators for Models: # User.new(user_data).valid? # Sanitize the data (example) user_data['name'] = ActionController::Base.helpers.sanitize(user_data['name']) user_data # Return the validated / sanitized data rescue => e Rails.logger.error("Error fetching user: #{e.message}") nil end end end """ **Don't Do This**: """ruby # Blindly using external data without any validation or sanitization # Very dangerous def fetch_user(user_id) response = HTTParty.get("https://external-api.com/users/#{user_id}") user_data = JSON.parse(response.body) # Using data directly without any checks # Vulnerable to injection attacks and unexpected data types puts "User Name: #{user_data['name']}" puts "User Email: #{user_data['email']}" user_data rescue => e Rails.logger.error("Error fetching user: #{e.message}") nil end """ **Why**: * **Prevents Injection Attacks**: Ensures that malicious content from external sources does not compromise the application by validating string lengths, types, and formats. * **Data Integrity**: Maintains the consistency and reliability of the application's data by rejecting malformed or dangerous data. ### 6.2 Output Encoding **Standard**: When displaying or processing data received from an API, especially in views or templates, ensure it's properly encoded to prevent cross-site scripting (XSS) attacks. **Do This**: """ruby # View file (e.g., app/views/users/show.html.erb) <p>User Name: <%= h @user['name'] %></p> <p>User Email: <%= h @user['email'] %></p> # Note: "h" is a shortcut for "ERB::Util.html_escape" # Rails automatically escapes output by default. """ **Don't Do This**: """ruby # Directly embedding API data in views without encoding # Vulnerable to XSS attacks <p>User Name: <%= @user['name'] %></p> <p>User Email: <%= @user['email'] %></p> """ **Why**: * **Mitigates XSS Attacks**: Prevents scripts from external APIs from being executed in the user's browser by carefully sanitizing data. * **Data Integrity**: Ensures that the displayed data is correctly presented and no characters are misinterpreted, which can lead to security or functional issues. This document provides comprehensive coding standards for API integration in Ruby development, promoting maintainability, performance, and security. Adhering to these standards ensures the development team can produce high-quality, reliable, and secure Ruby applications.
# Security Best Practices Standards for Ruby This document outlines security best practices for Ruby development, aiming to guide developers in writing secure, maintainable, and performant code. It's designed to be used both by human developers and AI coding assistants. ## 1. Input Validation and Sanitization ### 1.1. Standard: Validate All User Inputs **Do This:** Validate all user inputs to ensure they conform to expected formats and constraints. **Don't Do This:** Trust user inputs without validation. **Why:** Prevents injection attacks, data corruption, and unexpected behavior. **Code Example:** """ruby # Good Example: Validating an email address def valid_email?(email) email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i end def process_email(email) if valid_email?(email) puts "Valid email: #{email}" else puts "Invalid email: #{email}" end end process_email("test@example.com") # Valid email: test@example.com process_email("invalid-email") # Invalid email: invalid-email """ **Anti-Pattern:** """ruby # Bad Example: Directly using user input without validation def process_name(name) puts "Hello, #{name}!" end process_name(params[:name]) # Vulnerable to injection """ ### 1.2. Standard: Sanitize User Inputs **Do This:** Sanitize user inputs before using them in contexts where they could be misinterpreted (e.g., HTML output, database queries). **Don't Do This:** Directly use un-sanitized user inputs in such contexts. **Why:** Prevents cross-site scripting (XSS) and SQL injection vulnerabilities. **Code Example:** """ruby # Good Example: Sanitizing HTML output using ERB's auto-escaping require 'erb' require 'cgi' def display_comment(comment) #ERB automatically escapes HTML content unless told otherwise ERB.new("<p><%= comment %></p>").result(binding) end comment = "<script>alert('XSS');</script>Hello!" puts display_comment(comment) # Outputs safely escaped HTML: <script>alert('XSS');</script>Hello! #To disable escaping use the raw output formatter <%%= raw_html_string %> def display_comment_unsafe(comment) ERB.new("<p><%%= comment %></p>").result(binding) end puts display_comment_unsafe(comment) #Outputs the unescaped HTML: <script>alert('XSS');</script>Hello! (THIS IS UNSAFE) """ **Anti-Pattern:** """ruby # Bad Example: Directly including user input in HTML def display_unsafe(comment) "<p>#{comment}</p>" end """ **Technology-Specific Detail:** Ruby's ERB templating engine (especially in Rails) auto-escapes HTML output by default. Understand when and how to override this behavior (only when absolutely necessary). Never disable auto-escaping for user-provided content. ### 1.3 Standard: Use Parameterized Queries or ORM Features for Database Interactions **Do This:** Utilize parameterized queries or ORM (Object-Relational Mapping) systems with built-in protections against SQL injection when interacting with databases. **Don't Do This:** Construct SQL queries by directly concatenating user input. **Why:** Parameterized queries and ORMs escape user input, preventing attackers from injecting malicious SQL code. **Code Example:** """ruby # Good Example: Using ActiveRecord's parameterized query feature # Assuming you are using Rails with ActiveRecord class User < ApplicationRecord end def find_user_by_username(username) User.where("username = ?", username).first end user = find_user_by_username(params[:username]) """ **Anti-Pattern:** """ruby # Bad Example: Directly concatenating user input into SQL queries def find_user_by_username_unsafe(username) ActiveRecord::Base.connection.execute("SELECT * FROM users WHERE username = '#{username}'") end find_user_by_username_unsafe(params[:username]) # Vulnerable to SQL injection """ ## 2. Authentication and Authorization ### 2.1. Standard: Properly Hash and Salt Passwords **Do This:** Use bcrypt or Argon2 (with a sufficient cost factor) to hash and salt passwords before storing them. **Don't Do This:** Store passwords in plaintext or use outdated hashing algorithms like MD5 or SHA1. **Why:** Hashing and salting protects passwords from being easily compromised if the database is breached. Strong hashing algorithms make brute-force attacks computationally infeasible. **Code Example:** """ruby # Good Example: Using bcrypt to hash passwords require 'bcrypt' def create_user(username, password) password_hash = BCrypt::Password.create(password) # Store username and password_hash in the database end def authenticate_user(username, password, stored_hash) bcrypt_password = BCrypt::Password.new(stored_hash) bcrypt_password == password # Returns true if the password matches end # Usage (simulated) password = "securePassword123" password_hash = BCrypt::Password.create(password) puts "Hashed password: #{password_hash}" if authenticate_user("user1", password, password_hash) puts "Authentication successful!" else puts "Authentication failed." end if authenticate_user("user1", "wrongPassword", password_hash) puts "Authentication successful!" else puts "Authentication failed." #This branch will execute end """ **Anti-Pattern:** """ruby # Bad Example: Storing passwords in plaintext def create_user_unsafe(username, password) # Store username and password directly in the database (INSECURE) end """ ### 2.2. Standard: Implement Strong Authentication Methods **Do This:** Consider implementing multi-factor authentication (MFA) where appropriate (based on risk). Use secure session management practices and regularly rotate session keys. **Don't Do This:** Rely solely on single-factor authentication (username/password). **Why:** MFA adds an extra layer of security, making it harder for attackers to gain unauthorized access. **Code Example (Conceptual):** """ruby # Example (Conceptual) Integrating with a 2FA service (e.g., Google Authenticator) # In a Rails application, you would typically use a gem like 'devise-two-factor' # Step 1: Generate a secret key for the user # Step 2: Display the QR code or secret key to the user for setup # Step 3: Verify the OTP (One-Time Password) provided by the user during login. #This requires server-side storage of the secret key. def verify_otp(user, otp_code) #Logic to verify the OTP against the user's stored secret, using a gem like 'rotp' #Ensure proper validation of the time window and handling of edge cases. #Returns true if valid, false otherwise end #Authentication controller: #if user.authenticate(params[:password]) # if user.mfa_enabled? # if verify_otp(user, params[:otp_code]) # session[:user_id] = user.id # redirect_to dashboard_path # else # flash[:error] = "Invalid OTP code." # render :login # end # else # session[:user_id] = user.id # redirect_to dashboard_path # end #else # flash[:error] = "Invalid username or password" # render :login #end """ **Anti-Pattern:** """ruby # Bad Example: Only checking username and password def authenticate_user_unsafe(username, password) # Check username and password against the database (INSUFFICIENT) # Should include hashing as per above end """ ### 2.3. Standard: Implement Role-Based Access Control (RBAC) **Do This:** Define roles and permissions within your application and enforce access control based on these roles. **Don't Do This:** Grant users excessive privileges or rely on ad-hoc authorization checks. **Why:** RBAC provides a structured way to manage access control and reduces the risk of privilege escalation. **Code Example (Rails with CanCanCan gem):** """ruby # app/models/ability.rb (using CanCanCan gem) class Ability include CanCan::Ability def initialize(user) user ||= User.new # guest user (not logged in) if user.admin? can :manage, :all elsif user.editor? can :read, Article can :update, Article, :user_id => user.id can :create, Article else can :read, Article end end end # app/controllers/articles_controller.rb class ArticlesController < ApplicationController load_and_authorize_resource def show # @article is already loaded and authorized by load_and_authorize_resource end def update if @article.update(article_params) redirect_to @article, notice: 'Article was successfully updated.' else render :edit end end private def article_params params.require(:article).permit(:title, :content) end end """ **Anti-Pattern:** """ruby # Bad Example: Conditional authorization checks scattered throughout the code def update_article_unsafe(user, article) if user.admin? || article.user_id == user.id # Allow update else # Deny update end end """ ## 3. Secure Configuration and Secrets Management ### 3.1. Standard: Store Sensitive Information Securely **Do This:** Store sensitive information such as API keys, database passwords, and encryption keys in environment variables or encrypted configuration files. Use tools like "dotenv" (for development) and secure vault solutions( such as HashiCorp Vault or AWS Secrets Manager) for production. **Don't Do This:** Hardcode secrets directly in the codebase or commit them to version control. **Why:** Prevents accidental exposure of sensitive information. **Code Example:** """ruby # Good Example: Using environment variables # Ensure the .env file is NOT committed to the repo. Add to .gitignore require 'dotenv/load' #only for local development. Do NOT enable this in production. database_password = ENV['DATABASE_PASSWORD'] #For AWS secrets manager #require 'aws-sdk-secretsmanager' #client = Aws::SecretsManager::Client.new(region: 'your-aws-region') #response = client.get_secret_value(secret_id: 'your-secret-name') #database_password = JSON.parse(response.secret_string)['database_password'] def connect_to_database(password) # Connect to the database using the password puts "Using password: " + password end connect_to_database(database_password) """ **Anti-Pattern:** """ruby # Bad Example: Hardcoding secrets in the codebase database_password = "mySuperSecretPassword" # VERY BAD """ ### 3.2. Standard: Rotate Secrets Regularly **Do This:** Establish a process for regularly rotating secrets (especially API keys and encryption keys). **Don't Do This:** Use the same secrets indefinitely. **Why:** Limits the impact of compromised secrets. **Code Example (Conceptual):** """ruby # Conceptual example using a secret rotation service # This is HIGHLY DEPENDENT on the chosen secret management solution (e.g., AWS Secrets Manager, HashiCorp Vault) def rotate_api_key(api_key_name) # 1. Generate a new API key. This step depends on the API provider. new_api_key = generate_new_api_key() # 2. Update the API key in the secret management service. update_secret_in_vault(api_key_name, new_api_key) # 3. Revoke the old API key (if possible). This also depends on the API provider. revoke_old_api_key(api_key_name, get_old_key(api_key_name)) puts "API key rotated successfully." end def generate_new_api_key # Implement API key generation logic specific to the provider. # This might involve calling an API endpoint. SecureRandom.hex(32) #Example end """ ### 3.3. Standard: Limit Exposure of Sensitive Data in Logs **Do This:** Scrub logs and applications outputs so that sensitive information is never output to plain-text logs. Store logs securely using appropriate access controls. **Don't Do This:** Log sensitive data (e.g., passwords, credit card numbers, personally identifiable information (PII)). **Why:** Prevents sensitive data leakage through logs. **Code Example:** """ruby # Good Example: Filtering sensitive data from logs Rails.application.config.filter_parameters += [:password, :credit_card] # Or, for more complex scenarios: require 'logger' class CustomFormatter < Logger::Formatter def call(severity, time, program_name, message) # Scrub sensitive data from the message scrubbed_message = scrub_sensitive_data(message) super(severity, time, program_name, scrubbed_message) end private def scrub_sensitive_data(message) message.gsub(/(password=)[^&]+/, '\1[FILTERED]') # Example: filter password end end Rails.logger.formatter = CustomFormatter.new """ **Anti-Pattern:** """ruby # Bad Example: Logging raw password parameters Rails.logger.info "User login attempt: #{params[:username]}, password: #{params[:password]}" # VERY BAD """ ## 4. Vulnerability Scanning and Dependency Management ### 4.1. Standard: Regularly Scan for Vulnerabilities **Do This:** Use automated vulnerability scanning tools (e.g., Brakeman, bundler-audit, Snyk, Dependabot etc.) to identify potential security flaws in the codebase and dependencies. **Don't Do This:** Ignore potential vulnerabilities or delay patching. **Why:** Helps identify and address security weaknesses before they can be exploited. **Tool Example:** """shell # Using Brakeman (static analysis) brakeman """ ### 4.2. Standard: Keep Dependencies Up-to-Date **Do This:** Regularly update dependencies (gems) to the latest versions to patch known vulnerabilities. Use tools like "bundle update" or "dependabot" to automate this process. **Don't Do This:** Use outdated dependencies with known security flaws. **Why:** Ensures that the application is protected against known vulnerabilities in third-party libraries. **Code Example:** """ruby #Check for outdated gems bundle outdated #Update all gems to the latest versions, within the dependencies bundle update """ **Technology-Specific Detail:** Be aware of the trade-offs between staying current and maintaining compatibility. Thoroughly test updates in a staging environment before deploying to production. Pin gem versions in the Gemfile to prevent unexpected breaking changes due to auto-updates in minor versions. Use semantic versioning ("~> 1.2.3") appropriately. ### 4.3. Standard: Use a Software Bill of Materials (SBOM) **Do This:** Generate and maintain a Software Bill of Materials for each application, especially when deploying to production, so that any components with vulnerabilities can be easily traced back to the application. **Don't Do This:** Depend on outdated dependencies that don't receive security backports. **Why:** Helps organizations track the provenance of software components and identify vulnerabilities. ## 5. Cross-Site Scripting (XSS) Prevention ### 5.1. Standard: Escape All Output **Do This:** Always escape output to prevent XSS vulnerabilities, especially when displaying user-generated content. Use libraries or frameworks that provide automatic escaping mechanisms. **Don't Do This:** Output user input directly without escaping. **Why:** Prevents attackers from injecting malicious JavaScript code into the application. **Code Example (as in 1.2):** """ruby # Good Example: Sanitizing HTML output using ERB's auto-escaping require 'erb' require 'cgi' def display_comment(comment) #ERB automatically escapes HTML content unless told otherwise ERB.new("<p><%= comment %></p>").result(binding) end comment = "<script>alert('XSS');</script>Hello!" puts display_comment(comment) # Outputs safely escaped HTML: <script>alert('XSS');</script>Hello! #To disable escaping use the raw output formatter <%%= raw_html_string %> def display_comment_unsafe(comment) ERB.new("<p><%%= comment %></p>").result(binding) end puts display_comment_unsafe(comment) #Outputs the unescaped HTML: <script>alert('XSS');</script>Hello! (THIS IS UNSAFE) """ ### 5.2. Standard: Use Content Security Policy (CSP) **Do This:** Implement a Content Security Policy (CSP) to control the resources that the browser is allowed to load. **Don't Do This:** Rely solely on escaping; use CSP as an additional layer of defense. **Why:** CSP helps prevent XSS attacks by restricting the sources from which the browser can load resources. **Code Example (Rails):** """ruby # config/initializers/content_security_policy.rb Rails.application.config.content_security_policy do |policy| policy.default_src :self, :https policy.script_src :self, :https, :unsafe_inline # Avoid unsafe-inline if possible policy.style_src :self, :https policy.img_src :self, :https, :data policy.font_src :self, :https # Report CSP violations # policy.report_uri "/csp_reports" end """ ## 6. Cross-Site Request Forgery (CSRF) Prevention ### 6.1. Standard: Protect Against CSRF Attacks **Do This:** Use CSRF protection mechanisms provided by frameworks like Rails. Ensure that all state-changing requests require a valid CSRF token. **Don't Do This:** Disable CSRF protection or implement custom CSRF protection without thoroughly understanding the risks. **Why:** Prevents attackers from forging requests on behalf of authenticated users. **Code Example (Rails):** """ruby # Rails automatically includes CSRF protection # Ensure that "protect_from_forgery with: :exception" is present in the ApplicationController # In views, use form helpers that automatically include the CSRF token: # <%= form_with(model: @article) do |form| %> # ... # <% end %> """ **Technology-Specific Detail:** Ensure the "protect_from_forgery" setting is configured correctly in your Rails application controller. Pay attention to API endpoints that might require disabling CSRF protection (and implement alternative authentication mechanisms). When building single-page applications or APIs that interact with external clients, consider using alternative authentication schemes that are inherently resistant to CSRF (e.g., token-based authentication with proper CORS configuration). ## 7. Secure File Uploads ### 7.1. Standard: Validate File Uploads **Do This:** Validate file uploads by checking the file extension, MIME type, and file size. Use allowlists instead of denylists. Sanitize the filename to prevent directory traversal vulnerabilities. **Don't Do This:** Trust the file extension or MIME type provided by the client. **Why:** Prevents attackers from uploading malicious files (e.g., executable code, viruses). **Code Example:** """ruby # Good Example: Validating file uploads def valid_file_upload?(file) allowed_extensions = ['.jpg', '.jpeg', '.png', '.gif'] extension = File.extname(file.original_filename).downcase allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif'] mime_type = file.content_type if allowed_extensions.include?(extension) && allowed_mime_types.include?(mime_type) && file.size < 1.megabyte return true else return false end end def process_file_upload(file) if valid_file_upload?(file) sanitized_filename = sanitize_filename(file.original_filename) else puts "Invalid file upload!" end end def sanitize_filename(filename) File.basename(filename).gsub(/[^\w\._-]/, '') end """ **Anti-Pattern:** """ruby # Bad Example: Directly saving file without validation and sanitization def process_file_upload_unsafe(file) File.open("uploads/#{file.original_filename}", 'wb') do |f| f.write(file.read) end end """ ### 7.2. Standard: Store Uploaded Files Securely **Do This:** Store uploaded files outside the web root to prevent direct access. Use a unique, non-guessable name for the file. Implement access control mechanisms to restrict access to authorized users only. **Don't Do This:** Store uploaded files directly in the web root or use predictable filenames. **Why:** Prevents unauthorized access to uploaded files. ## 8. Error Handling and Logging ### 8.1. Standard: Handle Errors Gracefully **Do This:** Handle exceptions gracefully and provide informative but non-revealing error messages to users. **Don't Do This:** Display stack traces or sensitive information in error messages. **Why:** Prevents attackers from gaining insights into the application's internal workings. **Code Example:** """ruby # Good Example: Handling exceptions gracefully begin # Code that might raise an exception result = perform_operation rescue StandardError => e Rails.logger.error "Error: #{e.message}" # Log the error flash[:error] = "An error occurred. Please try again later." # Display a user-friendly message redirect_to some_path end """ **Anti-Pattern:** """ruby # Bad Example: Displaying stack traces to users begin # Code that might raise an exception result = perform_operation rescue StandardError => e puts e.backtrace # DON'T DO THIS! end """ ### 8.2. Standard: Log Security-Related Events **Do This:** Log security-related events such as authentication attempts, authorization failures, and data modifications. **Don't Do This:** Fail to log security-related events. **Why:** Provides audit trails for security investigations and helps detect suspicious activity. **Code Example:** """ruby def authenticate_user(username, password) user = User.find_by(username: username) if user && user.authenticate(password) Rails.logger.info "Successful login: username=#{username}" session[:user_id] = user.id return true else Rails.logger.warn "Failed login attempt: username=#{username}, IP=#{request.remote_ip}" return false end end """ ## 9. Security Audits and Penetration Testing ### 9.1. Standard: Conduct Regular Security Audits **Do This:** Conduct regular security audits of the codebase and infrastructure. **Don't Do This:** Neglect security audits. **Why:** Helps identify and address security weaknesses. ### 9.2. Standard: Perform Penetration Testing **Do This:** Perform penetration testing by security professionals to identify vulnerabilities that may not be found by automated tools. **Don't Do This:** Rely solely on automated tools; penetration testing provides a more comprehensive assessment. ## 10. Session Store Encryption ### 10.1 Standard: Encrypt Session Data **Do This**: Ensure Rails session data is automatically encrypted using "config.action_controller.cookies_serializer = :json" and setting a strong "secret_key_base". If storing sensitive data in the session manually, consider encrypting it at the application level, as well, to prevent tampering. **Don't Do This**: Store unencrypted sensitive information in sessions that can be read or modified by the client. **Why**: Prevents session hijacking and tampering, protecting user data. **Code Example:** """ruby # config/initializers/session_store.rb Rails.application.config.session_store :cookie_store, key: '_app_session', expire_after: 14.days, secure: Rails.env.production? #For production apps Rails.application.config.action_controller.cookies_serializer = :json #Encrypt session values #Encrypt additional user information (if necessary) require 'openssl' require 'base64' def encrypt(data, key) cipher = OpenSSL::Cipher.new('aes-256-cbc') cipher.encrypt cipher.key = key iv = cipher.random_iv encrypted_data = cipher.update(data) + cipher.final Base64.strict_encode64(iv + encrypted_data) end def decrypt(encrypted_data, key) decoded_data = Base64.strict_decode64(encrypted_data) iv = decoded_data[0..15] encrypted_data = decoded_data[16..-1] cipher = OpenSSL::Cipher.new('aes-256-cbc') cipher.decrypt cipher.key = key cipher.iv = iv cipher.update(encrypted_data) + cipher.final end #Example usage. Get the secret key from Rails.application.credentials.encryption_key (or env var) #encrypted_user_data = encrypt(user.to_json, Rails.application.credentials.encryption_key) #decrypted_user_data = decrypt(encrypted_user_data, Rails.application.credentials.encryption_key) """ This comprehensive document provides a solid foundation for developing secure Ruby applications. It is critical to stay informed on the latest security threats and adapt these guidelines accordingly. Remember to always prioritize security throughout the entire development lifecycle.
# Core Architecture Standards for Ruby This document outlines the core architectural standards for Ruby projects, focusing on maintainability, performance, and security. These standards are designed to guide development teams and provide context for AI coding assistants to generate high-quality, consistent Ruby code. This standard is tailored specifically to the latest version of Ruby and emphasizes modern best practices. ## 1. Architectural Patterns ### 1.1 Model-View-Controller (MVC) **Standard:** Adopt the MVC architectural pattern for web applications. * **Do This:** Structure your application into Models (data and logic), Views (presentation), and Controllers (handling user input and interactions). * **Don't Do This:** Avoid tightly coupling Models, Views, and Controllers. Fat models, skinny controllers are generally preferred. **Why:** MVC promotes separation of concerns, making applications more maintainable, testable, and scalable. **Code Example (Rails MVC):** """ruby # app/models/user.rb class User < ApplicationRecord validates :email, presence: true, uniqueness: true has_many :posts end # app/views/users/show.html.erb <h1><%= @user.email %></h1> <% @user.posts.each do |post| %> <p><%= post.title %></p> <% end %> # app/controllers/users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) end end """ **Anti-Pattern:** Placing business logic directly within views or controllers, leading to code duplication and making the application difficult to reason about. ### 1.2 Service Objects **Standard:** Encapsulate complex business logic into Service Objects. * **Do This:** Create dedicated classes to handle specific business operations, keeping controllers lean. * **Don't Do This:** Put complex logic directly within models or controllers. **Why:** Service Objects promote code reusability, testability, and maintainability by isolating specific responsibilities. **Code Example:** """ruby # app/services/user_creator.rb class UserCreator def initialize(params) @params = params end def call user = User.new(@params) if user.save # Send welcome email UserMailer.welcome_email(user).deliver_later user else false end end end # app/controllers/users_controller.rb class UsersController < ApplicationController def create user = UserCreator.new(params[:user]).call if user redirect_to user_path(user), notice: 'User created successfully' else render :new, status: :unprocessable_entity end end end """ **Anti-Pattern:** Overloading models with unrelated business logic, leading to "fat models" that are hard to maintain. ### 1.3 Event-Driven Architecture **Standard:** Use an event-driven architecture for asynchronous tasks and decoupled components. * **Do This:** Implement background processing using tools such as Sidekiq, Resque, or ActiveJob. Utilize Ruby's "concurrent" gem or standard "Thread" class for threading. Utilize "Hanami::Events" or "wisper´ for in-process events. * **Don't Do This:** Perform long-running tasks within the request-response cycle. **Why:** Event-driven architecture improves application responsiveness and scalability by offloading tasks to background processes. **Code Example (Sidekiq):** """ruby # app/workers/email_worker.rb class EmailWorker include Sidekiq::Worker def perform(user_id) user = User.find(user_id) UserMailer.welcome_email(user).deliver_now end end # app/controllers/users_controller.rb class UsersController < ApplicationController def create user = User.new(params[:user]) if user.save EmailWorker.perform_async(user.id) # Enqueue the email sending redirect_to user_path(user), notice: 'User created successfully' else render :new, status: :unprocessable_entity end end end """ **Anti-Pattern:** Blocking the main application thread with synchronous tasks, leading to poor user experience. ### 1.4 API-First Design **Standard:** Design applications with an API-first approach. * **Do This:** Define clear API contracts (e.g., using OpenAPI/Swagger) before implementing the UI or other client-facing components. * **Don't Do This:** Build APIs as an afterthought or directly tied to the UI. **Why:** API-first design promotes reusability, interoperability, and scalability by decoupling the backend from the frontend. **Code Example (Rails API mode):** """ruby # config/routes.rb Rails.application.routes.draw do namespace :api do namespace :v1 do resources :users, only: [:index, :show, :create, :update, :destroy] end end end # app/controllers/api/v1/users_controller.rb module Api module V1 class UsersController < ApplicationController def index @users = User.all render json: @users end # ... other actions end end end """ **Anti-Pattern:** Creating tightly coupled APIs that are difficult to evolve independently. ## 2. Project Structure and Organization ### 2.1 Directory Structure **Standard:** Follow a consistent and logical directory structure. * **Do This:** Organize code into "app", "lib", "config", "db", "spec" (or "test") directories. * **Don't Do This:** Scatter files randomly throughout the project. **Why:** A well-defined directory structure improves code discoverability, maintainability, and collaboration. **Example:** """ my_project/ ├── app/ │ ├── models/ │ ├── views/ │ ├── controllers/ │ ├── services/ │ ├── mailers/ │ └── helpers/ ├── lib/ │ └── my_gem/ # External libraries & utilities ├── config/ │ ├── routes.rb │ ├── application.rb │ └── database.yml ├── db/ │ ├── migrate/ │ └── seeds.rb ├── spec/ # or test/ │ ├── models/ │ ├── controllers/ │ └── support/ ├── Gemfile ├── README.md └── .gitignore """ **Anti-Pattern:** Putting unrelated code in a single directory, making it difficult to find and understand. ### 2.2 Module Organization **Standard:** Use modules to group related classes and functions. * **Do This:** Create modules to encapsulate related functionality and prevent namespace collisions. * **Don't Do This:** Define global functions or classes without proper namespacing. **Why:** Modules improve code organization, readability, and maintainability. **Code Example:** """ruby # lib/payment_gateway.rb module PaymentGateway class Client def self.charge(amount, credit_card) # ... implementation end end module Adapters class Stripe def self.charge(amount, token) # ... Stripe specific impl end end end end # Usage PaymentGateway::Client.charge(100, credit_card_info) PaymentGateway::Adapters::Stripe.charge(100, stripe_token) """ **Anti-Pattern:** Defining global classes or functions that can clash with other libraries or code. ### 2.3 Dependency Injection **Standard:** Utilize dependency injection to decouple components. * **Do This:** Pass dependencies into classes or functions instead of hardcoding them. * **Don't Do This:** Create tight coupling through global variables or direct instantiation of dependencies within classes. **Why:** Dependency injection increases code testability and flexibility by allowing dependencies to be easily swapped or mocked. **Code Example:** """ruby # Injecting a logger dependency class MyService def initialize(logger:) @logger = logger end def do_something @logger.info "Doing something..." # ... implementation end end # Usage with different loggers my_service = MyService.new(logger: Logger.new($stdout)) my_service.do_something my_service = MyService.new(logger: MockLogger.new) # For testing Purposes my_service.do_something """ **Anti-Pattern:** Hardcoding dependencies within classes, making it difficult to test or reuse them in different contexts. ## 3. Code-Level Standards ### 3.1 Naming Conventions **Standard:** Follow consistent naming conventions. * **Do This:** Use descriptive names for classes, methods, and variables. Use snake_case for variables and methods, PascalCase for classes and modules. * **Don't Do This:** Use cryptic or ambiguous names. **Why:** Clear naming improves code readability and understanding. **Code Example:** """ruby class UserProfile # Use PascalCase for Class Names def calculate_age # Use snake_case for method names @date_of_birth # Example of a variable name end end # Avoid: # class UP # def calc # x = ... """ **Anti-Pattern:** Using single-letter variable names or unclear abbreviations. ### 3.2 Modularity **Standard:** Write modular code. * **Do This:** Break down complex methods into smaller, self-contained functions. * **Don't Do This:** Write long, monolithic functions that are difficult to understand and test. **Why:** Modular code improves readability, testability, and reusability. **Code Example:** """ruby def process_order(order) validate_order(order) calculate_total(order) charge_customer(order) send_confirmation_email(order) end private def validate_order(order) # ... validation logic end def calculate_total(order) # ... calculation logic end # ... other private methods """ **Anti-Pattern:** Writing large, complex methods that perform multiple unrelated tasks. ### 3.3 Error Handling **Standard:** Implement robust error handling mechanisms. * **Do This:** Use "begin...rescue...end" blocks to handle exceptions. Raise exceptions when necessary. * **Don't Do This:** Ignore exceptions or swallow errors silently. **Why:** Proper error handling prevents application crashes and provides meaningful feedback to users. **Code Example:** """ruby def process_file(filename) begin file = File.open(filename) # ... process file rescue Errno::ENOENT => e Rails.logger.error "File not found: #{e.message}" # Raise a custom exception or return an error status. raise CustomFileNotFoundError, "File not found: #{filename}" rescue StandardError => e Rails.logger.error "An unexpected error occurred: #{e.message}" # Handle other errors appropriately ensure file.close if file # Ensure file close to prevent resource leaks end end """ **Anti-Pattern:** Ignoring exceptions or using bare "rescue" blocks without specifying the exception type. ### 3.4 Performance Optimization **Standard:** Optimize code for performance. Use tools such as "benchmark", "memory_profiler" or "flamegraph" to assist! * **Do This:** Use efficient data structures and algorithms. Avoid unnecessary database queries. Consider using caching strategies via Rails built-in caching or external caches like Redis. * **Don't Do This:** Write code that performs poorly or consumes excessive resources. Premature optimization! **Why:** Performance optimization improves application responsiveness and scalability. **Code Example:** """ruby # Instead of: users.each { |user| puts user.name } # Use map(&:name) for performance: names = users.map(&:name) # More efficient as you pre-allocate memory for a dedicated array # SQL Optimization # Instead of: orders.each { |order| puts order.customer.name } # N+1 query problem # Use includes to eager load associations: orders = Order.includes(:customer) orders.each { |order| puts order.customer.name } # Reduces database queries """ **Anti-Pattern:** Ignoring performance issues or writing inefficient code with little to no thought of optimization. ### 3.5 Security Considerations **Standard:** Implement security best practices. * **Do This:** Sanitize user input. Protect against SQL injection, cross-site scripting (XSS), and other common vulnerabilities. Utilize tools like Brakeman for static analysis. * **Don't Do This:** Store sensitive data in plain text. **Why:** Security best practices protect the application and its users from threats. **Code Example:** """ruby # Sanitize user input params[:search_term] = ActionController::Base.helpers.sanitize(params[:search_term]) # Use parameterized queries to prevent SQL injection User.where("email = ?", params[:email]) # Use bcrypt for password hashing def password=(new_password) @password = BCrypt::Password.create(new_password) self.password_hash = @password end """ **Anti-Pattern:** Storing passwords in plain text or failing to sanitize user input. ## 4. Specific Ruby Features (Latest Version) ### 4.1 Pattern Matching **Standard:** Utilize pattern matching for concise and expressive code. * **Do This:** Use "case...in" statements to match complex data structures. * **Don't Do This:** Rely solely on traditional "if...else" statements for complex conditional logic. **Why:** Pattern matching simplifies complex conditional logic and makes code more readable. **Code Example:** """ruby result = {status: :ok, data: {name: "John", age: 30}} case result in {status: :ok, data: {name: String => name, age: Integer => age}} puts "Name: #{name}, Age: #{age}" in {status: :error, message: String => message} puts "Error: #{message}" else puts "Unknown result" end """ **Anti-Pattern:** Missing opportunities to use powerful pattern matching features in suitable situations. ### 4.2 Fiber Scheduler/Concurrency **Standard:** Use fibers for concurrent code execution * **Do This:** Use "Fiber.schedule" to execute functions concurrently without native threads. * **Don't Do This:** Use many OS-level threads unless specifically needed. **Why:** Fibers greatly improve concurrency performance. **Code Example:** """ruby Fiber.schedule do # concurrent code! puts "Running in a fiber!" end """ **Anti-Pattern:** Ignoring the existance and performance benefits of fibers. ### 4.3 Ractor **Standard**: Use Ractor for parallel code execution. * **Do This**: Isolate state between "Ractor"s to avoid data races. * **Don't Do This**: Share mutable state directly between "Ractor"s. **Why**: Helps achieve true parallelism in Ruby code. **Code Example:** """ruby r = Ractor.new { puts "Hello from a ractor!" } r.take #=> "Hello from a ractor!" """ **Anti-Pattern**: Sharing mutable data between actors without synchronization. ## 5. Tooling and Automation ### 5.1 Linters and Code Analysis **Standard:** Use Linters (Rubocop), Security Scanners (Brakeman) and code analysis tools * **Do This:** Configure Rubocop with a configuration file (".rubocop.yml") to enforce the coding standards defined in this document. * **Do This:** Integrate Brakeman into the CI/CD pipeline. * **Don't Do This:** Ignore warnings from the linter and static analysis tools. ### 5.2 Testing Framework **Standard**: Implement automated test suites using RSpec or Minitest * **Do This**: Aim for high test coverage. Write unit, integration and end-to-end tests. * **Don't Do This**: Skip writing tests or commit code with failing tests. ### 5.3 Continuous Integration & Continuous Deployment (CI/CD) **Standard:** Use CI/CD pipelines for automated builds, tests, and deployments. * **Do This:** Integrate the CI/CD pipeline with code repositories (e.g., GitHub, GitLab). * **Don't Do This:** Deploy code manually without automated testing or validation. **Why:** CI/CD automates the software development lifecycle, resulting in faster delivery, fewer bugs, and higher quality. **Code Example (GitHub Actions):** """yaml # .github/workflows/main.yml name: Ruby CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:13 ports: ['5432:5432'] env: POSTGRES_USER: ruby POSTGRES_PASSWORD: password POSTGRES_DB: test_db options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 3.2 # specify version of Ruby to use (eg: 2.7.2) bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Configure application run: cp config/database.yml.example config/database.yml - name: Create and Migrate Database run: | bundle exec rails db:create bundle exec rails db:migrate - name: Run tests run: bundle exec rspec """ This comprehensive document provides a foundation for building robust, maintainable, and high-performing Ruby applications, aligning with the latest features and best practices of the language.
# Tooling and Ecosystem Standards for Ruby This document outlines the recommended tooling and ecosystem practices for Ruby development. Adhering to these standards ensures maintainable, performant, and secure applications, aligned with modern Ruby approaches. ## Dependency Management Choosing and managing dependencies wisely is crucial to a project's stability and security. ### Bundler Bundler is the standard dependency management tool for Ruby. It ensures consistent gem versions across different environments. **Do This:** * Always use Bundler for managing gem dependencies. * Specify gem versions explicitly or use pessimistic version constraints ("'~> 3.0'") to avoid unexpected breaking changes. * Regularly update dependencies with "bundle update" (ideally after thorough testing). * Use ".bundle/config" to configure Bundler settings like mirror URLs and deployment options. **Don't Do This:** * Avoid using "gem install" directly without Bundler, as it can lead to dependency conflicts. * Don't commit the "Gemfile.lock" file. It _should_ be committed. It is critical to repeatable builds. * Don't leave gem versions unspecified (e.g., just "gem 'rails'"). **Why:** Bundler ensures consistency and avoids dependency conflicts, crucial for maintainability and preventing "works on my machine" issues. **Example:** """ruby # Gemfile source 'https://rubygems.org' gem 'rails', '~> 7.0' # Restricts to versions 7.0.x gem 'puma', '~> 5.0' # .bundle/config BUNDLE_MIRROR__RUBYGEMS_ORG: "https://some-internal-mirror.com" # Example internal mirror """ ### Gemfile Structure and Organization A well-structured "Gemfile" enhances readability and maintainability. **Do This:** * Organize gems into logical groups (e.g., ":development", ":test", ":production"). * Use comments to explain the purpose of gems when necessary. * Keep the "Gemfile" alphabetized within each group. * Prefer using named groups over inline conditional loading. **Don't Do This:** * Avoid long, uncommented lists of gems. * Don't mix development and production dependencies in the default group. * Don't hardcode environment-specific gems using "ENV['RACK_ENV']". Instead, use Bundler groups. **Why:** A well-organized "Gemfile" makes it easier to understand dependencies and reduces the risk of including unnecessary gems in production. **Example:** """ruby # Gemfile source 'https://rubygems.org' gem 'rails', '~> 7.0' gem 'puma', '~> 5.0' group :development do gem 'pry' gem 'rubocop', require: false end group :test do gem 'rspec-rails' gem 'capybara' end """ ### Private Gems When using private gems, configure Bundler appropriately. **Do This:** * Use "bundle config" to set the authentication credentials for your private gem source. * Consider using environment variables for sensitive credentials. * Ensure the private gem source is HTTPS for security. **Don't Do This:** * Don't hardcode credentials directly in the "Gemfile". * Don't use HTTP for private gem sources. **Why:** Protects your private gems and ensures secure access to your gem repositories. **Example:** """bash bundle config gems.my_private_gem_source.com username:your_username bundle config gems.my_private_gem_source.com password:your_password # Use environment variables: bundle config gems.my_private_gem_source.com username:$PRIVATE_GEM_USERNAME bundle config gems.my_private_gem_source.com password:$PRIVATE_GEM_PASSWORD """ """ruby # Gemfile source 'https://gems.my_private_gem_source.com' gem 'my_private_gem' """ ## Testing and Quality Assurance Robust testing and quality assurance practices are essential. ### RSpec RSpec is a popular and expressive testing framework for Ruby. **Do This:** * Write comprehensive tests covering various scenarios (unit, integration, system). * Use descriptive test names that clearly explain the expected behavior. * Follow the "Arrange, Act, Assert" (AAA) pattern in your tests. * Use mocks and stubs judiciously to isolate units under test. * Keep tests focused and avoid testing implementation details. * Configure ".rspec" to enable useful flags (e.g., "--format documentation", "--color"). **Don't Do This:** * Avoid having large, complex tests that are difficult to understand and maintain. * Don't skip writing tests for critical functionality. * Don't tightly couple tests to implementation details, making them brittle. * Don't ignore failing tests; address them promptly. **Why:** RSpec helps ensure code correctness, prevent regressions, and improve code quality. **Example:** """ruby # spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do describe '#full_name' do it 'returns the full name of the user' do user = User.new(first_name: 'John', last_name: 'Doe') expect(user.full_name).to eq('John Doe') end end end """ ### Code Coverage Code coverage tools help identify untested code. **Do This:** * Use a code coverage tool like SimpleCov to measure test coverage. * Set coverage thresholds to ensure sufficient test coverage (e.g., 80% or higher). * Focus on increasing coverage for critical and complex code. **Don't Do This:** * Don't aim for 100% coverage blindly; focus on testing important logic. * Don't ignore coverage reports; use them to identify gaps in testing. **Why:** Code coverage helps you understand what parts of your code are being tested and where more tests might be necessary. **Example:** """ruby # spec/rails_helper.rb require 'simplecov' SimpleCov.start 'rails' RSpec.configure do |config| # ... other configurations end """ ### Static Analysis Static analysis tools help identify potential issues before runtime. **Do This:** * Use RuboCop for code stylelinting based on the community Ruby Style Guide. * Use tools like Reek to detect code smells. * Integrate static analysis into your development workflow (e.g., using Git hooks or CI). * Customize RuboCop rules to match your project's specific needs. * Enable auto-correction where possible to automatically fix style issues. **Don't Do This:** * Don't ignore the warnings and errors reported by static analysis tools. * Don't disable useful rules without a good reason. * Don't commit code with style violations or code smells. **Why:** Static analysis enforces code consistency, catches potential bugs, and improves code quality. **Example:** """ruby # .rubocop.yml require: - rubocop-rails - rubocop-rspec AllCops: TargetRubyVersion: 3.2 # Specify target Ruby version Exclude: - 'db/**/*' - 'config/**/*' - 'vendor/**/*' Style/Documentation: Enabled: false # Disable documentation requirement globally. Prefer YARD Metrics/BlockLength: Exclude: - 'spec/**/*' Rails/SkipsModelValidations: Enabled: false # Allow skips model validations by default """ ## Logging Effective logging is essential for debugging, monitoring, and auditing applications. ### Standard Logger Use the standard "Logger" class in Ruby's standard library. **Do This:** * Configure the logger's severity level (e.g., "DEBUG", "INFO", "WARN", "ERROR", "FATAL"). * Use meaningful log messages that provide context and help with debugging. * Log exceptions with their backtraces for detailed error information. * Use structured logging (e.g., JSON) for easier parsing and analysis by logging tools. * Consider using a logging library like Lograge for Rails to reduce noise. * Sanitize sensitive data (e.g., passwords, API keys) before logging. **Don't Do This:** * Avoid using "puts" or "print" for logging in production. * Don't log excessively, as it can impact performance. * Don't log sensitive information in plain text. * Don't ignore error logs; investigate and address them promptly. **Why:** Proper logging provides valuable insights into application behavior, aids in debugging, and facilitates monitoring. **Example:** """ruby # config/application.rb config.log_level = :info # app/controllers/users_controller.rb logger.info "Processing request for user with id: #{params[:id]}" begin @user = User.find(params[:id]) rescue ActiveRecord::RecordNotFound => e logger.error "User not found: #{e.message}\n#{e.backtrace.join("\n")}" # Log the backtrace render plain: "User not found", status: :not_found end #Structured logging example: logger.info({event: 'user_login', user_id: @user.id, ip_address: request.remote_ip}.to_json) """ ### Lograge Lograge helps clean up Rails logs by consolidating the request information into a single line. **Do This:** * Include the "lograge" gem in your "Gemfile". * Enable Lograge in your "config/application.rb". * Customize the Lograge configuration to include relevant request parameters. **Don't Do This:** * Don't enable Lograge without customizing the parameters to be logged. * Don't log sensitive data without sanitizing it within the Lograge configuration. **Why:** Enhances log readability and makes it easier to analyze application performance. **Example:** """ruby # Gemfile gem 'lograge' # config/application.rb config.lograge.enabled = true config.lograge.formatter = Lograge::Formatters::Json.new #JSON formatter config.lograge.custom_options = lambda do |event| { params: event.payload[:params].except('controller', 'action', 'password', 'password_confirmation') #Sanitize params } end """ ## Documentation Clear and comprehensive documentation is essential for maintainability and collaboration. ### YARD YARD is the standard documentation generation tool for Ruby. **Do This:** * Use YARD syntax for documenting classes, modules, methods, and attributes. * Provide clear and concise descriptions of the purpose and functionality of each documented element. * Document the expected parameters and return values of methods. * Use tags like "@param", "@return", "@raise", "@see", "@example" to add structured information. * Generate YARD documentation regularly and host it in a readily accessible location (e.g., GitHub Pages). * Ensure that public APIs are documented. **Don't Do This:** * Avoid incomplete or outdated documentation. * Don't neglect documenting important aspects of your codebase. * Don't document obvious things; focus on explaining the intent and usage. **Why:** YARD-generated documentation provides a consistent and accessible reference for your codebase. **Example:** """ruby # app/models/user.rb # Represents a user in the system. class User < ApplicationRecord # Returns the user's full name. # @return [String] The user's full name. def full_name "#{first_name} #{last_name}" end # Sends a welcome email to the user. # @param email [String] The email address to send the email to. # @raise [ArgumentError] if the email is nil or empty. # @return [Boolean] True if the email was sent successfully, false otherwise. # @example # user.send_welcome_email("user@example.com") def send_welcome_email(email) raise ArgumentError, "Email cannot be nil or empty" if email.nil? || email.empty? #Logic to send email end end """ ### README The "README" file is the entry point to your project and should provide a high-level overview. **Do This:** * Include a clear and concise description of the project's purpose. * Provide instructions for installation and setup. * Include examples of how to use the project. * List any dependencies and how to install them. * Provide information on how to contribute to the project. * Include license information. **Don't Do This:** * Don't leave the "README" file empty or generic. * Don't provide incomplete or outdated information. **Why:** A well-written "README" file makes it easier for others to understand and use your project. ## Continuous Integration and Deployment (CI/CD) Automating the build, test, and deployment process is crucial for modern software development. ### GitHub Actions, GitLab CI, or Jenkins Choose a CI/CD platform that integrates well with your source code repository. **Do This:** * Automate the build, test, and deployment process using a CI/CD pipeline. * Run static analysis and code coverage tools as part of the CI pipeline. * Use environment variables for sensitive information (e.g., API keys, database passwords). * Implement automated testing for each commit and pull request. * Use infrastructure as code to manage and provision infrastructure automatically. **Don't Do This:** * Don't manually deploy code to production. * Don't skip automated testing in the CI/CD pipeline. * Don't store sensitive information directly in the CI/CD configuration. **Why:** CI/CD automates the development lifecycle, reducing errors and improving efficiency. **Example (GitHub Actions):** """yaml # .github/workflows/ci.yml name: CI on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 3.2 #specify ruby version bundler-cache: true - name: Run tests run: bundle exec rspec - name: Run RuboCop run: bundle exec rubocop """ ## Monitoring and Performance Monitoring application performance and identifying bottlenecks is essential for delivering a good user experience. ### Skylight, New Relic, or Datadog Use an APM (Application Performance Monitoring) tool to track application performance. **Do This:** * Monitor application performance metrics such as response time, throughput, and error rate. * Identify and address performance bottlenecks. * Set up alerts for critical performance issues. * Use profiling tools to identify slow code. **Don't Do This:** * Don't ignore performance issues. * Don't rely solely on manual testing for performance monitoring. **Why:** APM tools provide insights into application performance, allowing you to optimize and improve the user experience. ## Security Security should be a primary concern in all stages of development. ### Brakeman Use Brakeman to scan your Rails code for security vulnerabilities. **Do This:** * Run Brakeman regularly to identify potential security issues. * Address any vulnerabilities reported by Brakeman promptly. * Keep Brakeman up-to-date. * Integrate Brakeman into your CI/CD pipeline. **Don't Do This:** * Don't ignore security vulnerabilities reported by Brakeman. * Don't disable Brakeman without a good reason. **Why:** Brakeman helps you identify and fix security vulnerabilities in your Rails applications. **Example:** """bash brakeman -q -w1 #Quiet mode, warning level 1 (high confidence) """ ### Dependency Scanning Scan your dependencies for known vulnerabilities. **Do This:** * Use tools like "bundler-audit" to check your dependencies for vulnerabilities. * Regularly update dependencies to patch security vulnerabilities. * Implement a security policy for addressing vulnerabilities. **Don't Do This:** * Don't ignore dependency vulnerabilities. * Don't use outdated or unsupported dependencies. **Why:** Dependency scanning helps you identify and mitigate security risks associated with your dependencies. **Example:** """bash bundle audit check """ ### General Security Practices Implement general security best practices. **Do This:** * Use strong passwords and rotate them regularly. * Sanitize user input to prevent cross-site scripting (XSS) and SQL injection attacks. * Use HTTPS for all communication. * Store sensitive data securely (e.g., using bcrypt for password hashing). * Follow the principle of least privilege when granting access to resources. * Be aware of common security vulnerabilities (e.g., OWASP Top 10). **Don't Do This:** * Don't store passwords in plain text. * Don't trust user input without sanitizing it. * Don't expose sensitive information in error messages or logs. **Why:** Following security best practices protects your application and its users from security threats. By adhering to these tooling and ecosystem standards, Ruby development teams can create maintainable, performant, and secure applications aligned with modern practices.