# 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 = "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: 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 = "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: 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.
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'
# Component Design Standards for Ruby This document outlines the component design standards for Ruby. It aims to provide guidance on creating reusable, maintainable, and testable components within Ruby applications. These standards leverage modern Ruby idioms, the latest language features, and generally accepted best practices to ensure high-quality, robust code. ## 1. Principles of Component Design ### 1.1. Single Responsibility Principle (SRP) * **Do This:** Ensure each component has one, and only one, reason to change. A component should be focused on performing one specific task. * **Don't Do This:** Create "god classes" or modules that perform a wide variety of unrelated functions. * **Why:** SRP enhances maintainability and reduces the risk of unintended side effects when modifying code. """ruby # Good: Separate classes for calculating price and applying discount class PriceCalculator def calculate(item, quantity) item.price * quantity end end class DiscountApplicator def apply_discount(price, discount) price * (1 - discount) end end # Bad: Single class handling both price calculation and discount class OrderProcessor def calculate_price(item, quantity) item.price * quantity end def apply_discount(price, discount) price * (1 - discount) end end """ ### 1.2. Open/Closed Principle (OCP) * **Do This:** Design components that are open for extension but closed for modification. Use inheritance or composition to add new functionality without altering existing code. * **Don't Do This:** Modify existing classes directly to add new features if unintended effects for existing users may occur. * **Why:** Prevents introducing bugs and ensures that new functionality doesn't break existing features. """ruby # Good: Using inheritance for different notification types class Notifier def send(message, recipient) raise NotImplementedError, "Subclasses must implement the send method" end end class EmailNotifier < Notifier def send(message, recipient) # Implementation for sending email puts "Sending email to #{recipient}: #{message}" end end class SMSNotifier < Notifier def send(message, recipient) # Implementation for sending SMS puts "Sending SMS to #{recipient}: #{message}" end end # Usage email_notifier = EmailNotifier.new email_notifier.send("Hello!", "user@example.com") sms_notifier = SMSNotifier.new sms_notifier.send("Hello!", "+15551234567") # Bad: Modifying the original notifier class for each new method class Notifier def send(message, recipient, type = :email) if type == :email # Email Logic elsif type == :sms # SMS Logic end end end """ ### 1.3. Liskov Substitution Principle (LSP) * **Do This:** Ensure that subtypes are substitutable for their base types without altering the correctness of the program. * **Don't Do This:** Introduce subtypes with behavior that violates the expectations set by the base type. * **Why:** Maintains predictable behavior and simplifies code reasoning. """ruby # Good: Rectangle and Square adhering to LSP class Rectangle attr_accessor :width, :height def initialize(width, height) @width = width @height = height end def area @width * @height end end class Square < Rectangle def initialize(side) super(side, side) # Call the parent class's initialization @width = side @height = side end def width=(value) @width = value @height = value end def height=(value) @height = value @width = value end end def process_rectangle(rectangle) rectangle.width = 5 rectangle.height = 4 puts "Area: #{rectangle.area}" # Expected: 20 end rectangle = Rectangle.new(2, 3) process_rectangle(rectangle) # Output: Area: 20 square = Square.new(2) process_rectangle(square) # Output: Area: 20 # Bad: Square not adhering to LSP # If Square only had a single @side property and overrode width= and height= to set @side, then # the process_rectangle expectation would be violated. """ ### 1.4. Interface Segregation Principle (ISP) * **Do This:** Favor many client-specific interfaces over one general-purpose interface. * **Don't Do This:** Force components to implement methods they don't need. * **Why:** Reduces dependencies and improves cohesion within components. """ruby # Good: Separate interfaces for different operations module Readable def read raise NotImplementedError end end module Writable def write(data) raise NotImplementedError end end class FileReader include Readable def read # Implementation of reading from a file puts "Reading from file" end end class FileWriter include Writable def write(data) # Implementation of writing to a file puts "Writing to file: #{data}" end end class ReadWriter include Readable include Writable def read puts "Reading from ReadWriter" end def write(data) puts "Writing to ReadWriter: #{data}" end end # Bad: Single interface with unnecessary methods module FileOperations def read raise NotImplementedError end def write(data) raise NotImplementedError end def execute raise NotImplementedError end end """ ### 1.5. Dependency Inversion Principle (DIP) * **Do This:** Depend on abstractions (interfaces/abstract classes) rather than concrete implementations. * **Don't Do This:** Create tightly coupled components that directly depend on each other. * **Why:** Promotes flexibility, testability, and reusability by decoupling components. """ruby # Good: Depending on an abstraction class PaymentProcessor def initialize(payment_gateway) @payment_gateway = payment_gateway end def process_payment(amount) @payment_gateway.charge(amount) end end # Abstraction (Interface) class PaymentGateway def charge(amount) raise NotImplementedError end end # Concrete implementations class StripeGateway < PaymentGateway def charge(amount) puts "Charging #{amount} via Stripe" end end class PayPalGateway < PaymentGateway def charge(amount) puts "Charging #{amount} via PayPal" end end stripe_gateway = StripeGateway.new payment_processor = PaymentProcessor.new(stripe_gateway) payment_processor.process_payment(100) paypal_gateway = PayPalGateway.new payment_processor = PaymentProcessor.new(paypal_gateway) payment_processor.process_payment(50) # Bad: Directly depending on a concrete implementation class PaymentProcessor def process_payment(amount) stripe_gateway = StripeGateway.new # Tight coupling stripe_gateway.charge(amount) end end """ ## 2. Component Definition and Structure ### 2.1. Classes vs. Modules * **Do This:** Use classes for defining objects with state and behavior. Use modules for grouping related methods and constants or to implement mixins. * **Don't Do This:** Use classes for purely static methods or constants. Use modules to create objects with independent state. * **Why:** Follows Ruby conventions and improves code clarity. """ruby # Good: Class for an object, module for functions class User attr_accessor :name, :email def initialize(name, email) @name = name @email = email end end module StringUtils def self.titleize(string) string.split.map(&:capitalize).join(' ') end end puts StringUtils.titleize("hello world") # Output: Hello World # Bad: Class for static methods class MathUtils def self.add(a, b) a + b end end """ ### 2.2. Namespaces * **Do This:** Use namespaces (modules) to organize related classes and avoid naming conflicts. * **Don't Do This:** Define classes in the global namespace carelessly. It leads to naming collisions. * **Why:** Enhances code organization and maintainability, especially in large projects. """ruby # Good: Using namespaces module MyLibrary class User attr_accessor :name end class Product attr_accessor :price end end user = MyLibrary::User.new user.name = "Alice" puts user.name # Output: Alice # Bad: No namespace class User attr_accessor :name end """ ### 2.3. Component Interfaces * **Do This:** Define clear public interfaces for components using well-documented methods. Use private and protected methods to encapsulate implementation details. * **Don't Do This:** Expose internal implementation details as public methods, or improperly use "private" which affects inheritance. * **Why:** Increases component usability and reduces the risk of breaking code when internal implementations change. """ruby # Good: Clear public interface with private helper methods class Calculator def add(a, b) validate_numbers(a, b) perform_addition(a, b) end private def validate_numbers(a, b) raise ArgumentError, "Inputs must be numbers" unless a.is_a?(Numeric) && b.is_a?(Numeric) end def perform_addition(a, b) a + b end end # Usage: calculator = Calculator.new puts calculator.add(5, 3) # => 8 # Attempting to call private methods directly will raise errors: # calculator.validate_numbers(5, 3) # NoMethodError: private method "validate_numbers' called """ **Notes on Private Methods:** In Ruby, "private" methods can only be called within the instances of the same class. This means that derived classes cannot directly call "private" methods of the base class. Use "protected" methods if access needs to be granted to instances of derived classes. ### 2.4. Attributes and Encapsulation * **Do This:** Use "attr_accessor", "attr_reader", and "attr_writer" judiciously to control access to component attributes. Prefer immutability when appropriate. * **Don't Do This:** Expose all attributes as read-write without considering the implications. * **Why:** Controls state changes and maintains component integrity. """ruby # Good: Controlled attribute access class Book attr_reader :title, :author # Only readable attr_accessor :price # Read and write def initialize(title, author, price) @title = title @author = author @price = price end def expensive? @price > 50 end end book = Book.new("The Ruby Way", "Hal Fulton", 60) puts book.title book.price = 70 # OK #book.title = "New Title" # Error: No writer method #Immutability example (using .freeze in Ruby) class ImmutablePoint attr_reader :x, :y def initialize(x, y) @x = x @y = y freeze # Make the object immutable end end immutable_point = ImmutablePoint.new(1, 2) # immutable_point.x = 3 # Raises a TypeError because the object is frozen. """ ### 2.5. Initializers * **Do This:** Use initializers ("initialize" method) to set up the initial state of a component. Use keyword arguments for clarity and readability. * **Don't Do This:** Perform heavy computation or I/O operations in the initializer. Use factory methods or lazy initialization for complex setup. * **Why:** Provides a predictable way to instantiate components. """ruby # Good: Keyword arguments for clarity class Order attr_accessor :order_id, :customer_id, :items def initialize(order_id:, customer_id:, items: []) @order_id = order_id @customer_id = customer_id @items = items end end order = Order.new(order_id: 123, customer_id: 456, items: ['A', 'B']) # Bad: Long list of positional arguments without clear meaning. class OldOrder def initialize(a, b, c, d, e) #Hard to grok what these parameters are without documentation. end end """ ## 3. Component Interactions and Design Patterns ### 3.1. Composition over Inheritance * **Do This:** Prefer composition (has-a relationship) over inheritance (is-a relationship) for code reuse and flexibility. * **Don't Do This:** Create deep inheritance hierarchies, as these can lead to the fragile base class problem. * **Why:** Composition promotes loose coupling and allows components to be easily combined and reused. """ruby # Good: Composition class Engine def start puts "Engine started" end end class Wheels def rotate puts "Wheels rotating" end end class Car def initialize(engine, wheels) @engine = engine @wheels = wheels end def start @engine.start @wheels.rotate puts "Car moving" end end engine = Engine.new wheels = Wheels.new car = Car.new(engine, wheels) car.start # Bad: Deep inheritance hierarchy class Vehicle def start puts "Vehicle starting" end end class Car < Vehicle #Inherits and extends Vehicle def rotate_wheels #Specific to car puts "Rotating wheels" end def start super() #Call the parent version rotate_wheels() end end """ ### 3.2. Observer Pattern * **Do This:** Use the Observer pattern for loosely coupled communication between components. Define a subject (observable) and observers that react to state changes. * **Don't Do This:** Create tight dependencies where components directly call each other's methods across distinct logical boundaries. * **Why:** Decouples components and allows for flexible event handling. """ruby # Good: Observer Pattern require 'observer' #Using built-in Ruby library. class Employee include Observable attr_reader :name, :salary def initialize(name, salary) @name = name @salary = salary end def salary=(new_salary) old_salary = @salary @salary = new_salary changed # Tell the observer notify_observers(self, old_salary, new_salary) end end class Payroll def update(employee, old_salary, new_salary) puts "Cut a new check for #{employee.name}! from #{old_salary} to #{new_salary}" end end class TaxMan def update(employee, old_salary, new_salary) puts "Send #{employee.name} a new tax form!" end end fred = Employee.new("Fred", 50000) payroll = Payroll.new tax_man = TaxMan.new fred.add_observer(payroll) fred.add_observer(tax_man) fred.salary = 60000 """ ### 3.3. Strategy Pattern * **Do This:** Use the Strategy pattern to encapsulate interchangeable algorithms or behaviors. Define a context that uses a strategy object to perform a task. * **Don't Do This:** Embed conditional logic directly within components to handle different behaviors. * **Why:** Provides a flexible way to switch algorithms at runtime. """ruby # Good: Strategy Pattern class Report attr_accessor :formatter def initialize(formatter) @formatter = formatter end def output_report(title, text) @formatter.output_report(title, text) end end class HTMLFormatter def output_report(title, text) puts "<html><head><title>#{title}</title></head><body><h1>#{title}</h1><p>#{text}</p></body></html>" end end class PlainTextFormatter def output_report(title, text) puts "*** #{title} ***\n#{text}" end end html_formatter = HTMLFormatter.new report = Report.new(html_formatter) report.output_report("My Report", "This is the report content.") plain_text_formatter = PlainTextFormatter.new report.formatter = plain_text_formatter report.output_report("My Report", "This is the report content.") """ ### 3.4. Factory Pattern * **Do This:** Use factory patterns to encapsulate object creation logic. Use a factory method or factory class to create instances of components. * **Don't Do This:** Directly instantiate classes throughout the code, especially when the creation logic is complex. * **Why:** Decouples object creation from usage improves code maintainability and adheres to the Dependency Inversion Principle. """ruby # Good: Factory Pattern class DatabaseConnection def self.create(type) case type when :mysql MySQLConnection.new when :postgresql PostgreSQLConnection.new else raise ArgumentError, "Unknown database type: #{type}" end end end class MySQLConnection def connect puts "Connecting to MySQL database" end end class PostgreSQLConnection def connect puts "Connecting to PostgreSQL database" end end connection = DatabaseConnection.create(:mysql) connection.connect connection = DatabaseConnection.create(:postgresql) connection.connect # Bad: Direct instantiation spread throughout the code def connect_to_database(type) if type == "mysql" MySQLConnection.new.connect elsif type == "postgresql" PostgreSQLConnection.new.connect end end """ ## 4. Error Handling and Logging ### 4.1. Exception Handling * **Do This:** Use "begin...rescue...end" blocks to handle exceptions gracefully. Raise specific exceptions rather than generic "Exception" or "StandardError". * **Don't Do This:** Swallow exceptions without proper handling or logging. * **Why:** Prevents application crashes and provides meaningful error messages for debugging. """ruby # Good: Specific exception handling def process_file(filename) begin file = File.open(filename, 'r') data = file.read # Process data rescue Errno::ENOENT => e puts "File not found: #{e.message}" rescue IOError => e puts "I/O error: #{e.message}" ensure file.close if file end end # Bad: Catching generic exceptions def process_file(filename) begin file = File.open(filename, 'r') data = file.read # Process data rescue => e puts "An error occurred: #{e.message}" #Too generic and unhelpful ensure file.close if file end end """ ### 4.2. Logging * **Do This:** Use a logging library (e.g., "Logger", "Rails.logger") to record important events, errors, and warnings. Use appropriate log levels (debug, info, warn, error, fatal). * **Don't Do This:** Rely solely on "puts" statements for logging. * **Why:** Facilitates debugging and monitoring of application behavior. """ruby # Good: Using Logger require 'logger' logger = Logger.new(STDOUT) #Or a file. logger.level = Logger::DEBUG #Setting the level. logger.debug("This is a debug message") logger.info("This is an info message") logger.warn("This is a warning message") logger.error("This is an error message") logger.fatal("This is a fatal message") begin # Risky operation result = 10 / 0 rescue => e logger.error("Error during division: #{e.message}") end # Bad: Using puts def some_method puts "Starting some_Method" #Lack of context, level, etc. end """ ## 5. Testing ### 5.1. Unit Testing * **Do This:** Write unit tests for all components to verify their behavior in isolation. Use a testing framework like RSpec or Minitest. Aim for high test coverage. * **Don't Do This:** Skip writing unit tests or write tests that are superficial and don't thoroughly exercise the component. * **Why:** Increases confidence in code quality and simplifies debugging. """ruby # Example RSpec Unit test require 'rspec' require_relative 'calculator' #Referring to the Calculator class from earlier RSpec.describe Calculator do describe '#add' do it 'returns the sum of two numbers' do calculator = Calculator.new expect(calculator.add(2, 3)).to eq(5) end it 'raises an error if inputs are not numbers' do calculator = Calculator.new expect { calculator.add(2, 'a') }.to raise_error(ArgumentError) end end end """ ### 5.2. Integration Testing * **Do This:** Write integration tests to verify the interaction between components. * **Don't Do This:** Assume that components will work together correctly without integration testing. * **Why:** Ensures that components function properly as a system. """ruby #Example interaction test that exercises interactions. require 'rspec' require_relative 'order_processing_service' require_relative 'payment_gateway' require_relative 'inventory_service' RSpec.describe OrderProcessingService do let(:payment_gateway) { instance_double(PaymentGateway, charge: true) } let(:inventory_service) { instance_double(InventoryService, reserve_items: true) } let(:order_processing_service) { OrderProcessingService.new(payment_gateway: payment_gateway, inventory_service: inventory_service) } let(:order_details) { { customer_id: 1, items: [{ product_id: 101, quantity: 1 }] } } describe '#process_order' do it 'processes the order successfully' do order_processing_service.process_order(order_details) expect(payment_gateway).to have_received(:charge) expect(inventory_service).to have_received(:reserve_items).with(order_details[:items]) end it 'raises an error if payment fails' do allow(payment_gateway).to receive(:charge).and_raise(PaymentError) expect { order_processing_service.process_order(order_details) }.to raise_error(PaymentError) expect(inventory_service).not_to have_received(:reserve_items) # Inventory should not be reserved end end end """ ## 6. Concurrency and Parallelism For Ruby 3+, consider using "Ractor"s for true parallelism. Note that the older "Thread" class has limitations due to the Global Interpreter Lock (GIL). ### 6.1 Ractors (Ruby 3+) * **Do This:** Use Ractors for CPU-intensive tasks to achieve true parallel execution. * **Don't Do This:** Rely solely on Threads for computationally intensive tasks that require true parallelism. * **Why:** Ractors allow for isolating state and improve concurrency while avoiding GIL limitations. """ruby #Ractor example: require 'ractor' def expensive_calculation(n) result = 0 (1..n).each do |i| result += Math.sqrt(i) end result end numbers = [100_000, 200_000, 300_000] ractors = numbers.map do |n| Ractor.new n do |number| expensive_calculation(number) end end results = ractors.map(&:take) #Collect the results puts "Results: #{results}" """ ### 6.2 Threads * **Do This:** Use threads for I/O-bound operations to improve responsiveness * **Don't Do This:** Create threads indiscriminately, as this can lead to performance problems due to context switching overhead. * **Why:** Allows concurrency and efficient use of resources when waiting for I/O. """ruby # Using threads for concurrent I/O operations threads = [] ['file1.txt', 'file2.txt', 'file3.txt'].each do |filename| threads << Thread.new(filename) do |f| begin content = File.read(f) puts "Processed: #{f}" #Process the contents rescue => e puts "Error processing #{f}: #{e.message}" end end end threads.each(&:join) #Wait for all threads to complete """ ## 7. Documentation ### 7.1. Code Comments * **Do This:** Write clear, concise, and up-to-date comments to explain complex logic, non-obvious decisions, and component interfaces. * **Don't Do This:** Write redundant comments that simply restate the code. * **Why:** Facilitates understanding and maintaining the code. ### 7.2. YARD Documentation * **Do This:** Use YARD syntax to document classes, methods, and modules. Generate documentation using the "yard" command. * **Don't Do This:** Neglect documenting code or provide incomplete or outdated documentation. * **Why:** Creates consistent, standardized documentation that can updated and shared. """ruby # Example YARD Documentation # Adds two numbers. # @param a [Integer] The first number. # @param b [Integer] The second number. # @return [Integer] The sum of a and b. def add(a, b) a + b end """ This document is intended to serve as a comprehensive guide for Ruby component design. By following these standards, development teams can create robust, maintainable, and scalable Ruby applications. Remember to stay up-to-date with the latest Ruby versions and best practices to continuously improve code quality.
# 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.
# 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.
# 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.
# Performance Optimization Standards for Ruby This document outlines performance optimization standards for Ruby code. It aims to improve application speed, responsiveness, and resource usage by providing actionable guidelines and examples. We'll focus on modern Ruby practices and patterns, with an emphasis on the latest Ruby versions. ## I. Architectural Considerations ### 1. Choosing the Right Ruby Implementation **Do This:** Use Ruby MRI (CRuby) or optimize your selection for specific performance needs based on your workload. **Don't Do This:** Blindly use an implementation without considering its strengths and weaknesses. **Why:** Different Ruby implementations offer varying performance characteristics. * **CRuby (MRI):** The standard implementation, generally well-optimized. Most gem compatibility. * **JRuby:** Runs on the JVM, providing benefits from the JVM's JIT compiler and garbage collector, and integration with Java libraries. Well-suited for I/O heavy operations and scenarios requiring Java interop. * **TruffleRuby:** An optimizing just-in-time (JIT) compiler built on GraalVM. Delivers significant speed improvements in CPU-bound applications but may have compatibility issues with some gems. * **RubyMotion:** (Not actively maintained) Compiles Ruby to native iOS and macOS applications. Not suitable for general purpose backend development. **Code Example (Checking Ruby Implementation):** """ruby puts RUBY_ENGINE # => "ruby" (for MRI), "jruby", "truffleruby" etc. """ ### 2. Load Testing and Benchmarking **Do This:** Load test your application under expected production traffic and benchmark critical code paths. **Don't Do This:** Make performance assumptions without empirical evidence. **Why:** Identifying bottlenecks early is crucial. Tools like "wrk", "ab", and "Benchmark" help find those areas. **Code Example (Benchmarking):** """ruby require 'benchmark' n = 50000 Benchmark.bm do |x| x.report("string interpolation:") { n.times { "result: #{1 + 1}" } } x.report("string concatenation: ") { n.times { "result: " + (1 + 1).to_s } } end # Expected output: # user system total real # string interpolation: 0.040485 0.000000 0.040485 ( 0.040626) # string concatenation: 0.072514 0.000000 0.072514 ( 0.072675) """ ### 3. Choosing the Right Framework **Do This:** Carefully select a framework (e.g., Rails, Sinatra, Hanami) that aligns with your application's complexity and performance needs. **Don't Do This:** Use a heavyweight solution for a lightweight job. **Why:** Frameworks provide structure but impose overhead. Rails, the most popular framework, has conventions and features which can aid speed of development at the expense of runtime performance. For simple API applications, Sinatra or Hanami might be more performant choices. ### 4. Microservices vs. Monolith **Do This:** For large complex systems, consider breaking down the system into microservices to improve scalability and fault isolation. **Don't Do This:** Prematurely break down an application into microservices if this adds excessive overhead of network traffic and inter-service dependencies. **Why:** Decoupling components enables independent scaling and deployment, optimizing resource allocation. ## II. Coding Practices ### 1. Minimize Object Creation **Do This:** Reuse objects whenever possible, especially within loops. Use object pooling techniques for frequently used objects. **Don't Do This:** Create excessive temporary objects, especially inside loops which can trigger frequent garbage collection. **Why:** Object allocation is relatively expensive in Ruby. Reducing it can significantly improve performance. **Code Example (Avoiding unnecessary object creation):** """ruby # Anti-pattern: Creating a new array in each iteration def anti_pattern(data) result = [] data.each { |item| result << item.to_s } result end # Correct: Pre-allocate the array def correct_pattern(data) result = Array.new(data.size) data.each_with_index { |item, index| result[index] = item.to_s } result end """ ### 2. String Manipulation **Do This:** Use efficient string manipulation methods. Prefer "<<" or "concat" over "+" for appending. Consider using frozen strings where appropriate to avoid duplication. **Don't Do This:** Use inefficient string concatenation methods, especially within loops. **Why:** String operations can be performance-intensive. **Code Example:** """ruby # Efficient string = "hello" string << " world" # Less efficient (creates a new string object each time) string = "hello" string = string + " world" # Ruby 2.3+, use frozen string literals when possible # frozen_string_literal: true string = "hello".freeze #Avoids duplicating this string in memory """ ### 3. Iteration **Do This:** Choose the right iteration method based on the task. Use "each" for simple iteration, "map" for transforming elements, and "select" for filtering. For performance-critical loops, consider using "while" or "for" loops, or the C-accelerated versions when operating on "NArray" **Don't Do This:** Use "each" when "map" or "select" is more appropriate, or vice-versa. **Why:** Using correct iterators improves readability and offers performance gains. "while" and "for" loops can offer marginal performance increases in very hot loops. **Code Example:** """ruby # Efficient mapping numbers = [1, 2, 3, 4, 5] squares = numbers.map { |n| n * n } # Efficient filtering even_numbers = numbers.select { |n| n.even? } # Efficient each numbers.each { |n| puts n } """ ### 4. Regular Expressions **Do This:** Compile regular expressions and reuse them. Use non-capturing groups "(?:...)" when capturing is not needed. Be mindful of backtracking complexity. **Don't Do This:** Create regular expressions every time they are needed or neglect complex regexes that can lead to excessive backtracking. **Why:** Compiling regular expressions is expensive. Backtracking can catastrophically degrade performance in complex regular expressions. **Code Example:** """ruby # Compile and reuse EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.freeze def valid_email?(email) EMAIL_REGEX.match?(email) end """ ### 5. Memoization **Do This:** Memoize the results of expensive function calls, especially pure functions. Use "||=" operator or dedicated memoization libraries. **Don't Do This:** Memoize excessively or without a strategy to invalidate the cache when the underlying data changes. **Why:** Avoid recomputing values already calculated. **Code Example:** """ruby def expensive_calculation(n) @cache ||= {} return @cache[n] if @cache.key?(n) puts "Calculating..." result = (1..n).sum # Simulate heavy computation @cache[n] = result result end puts expensive_calculation(10) # Calculates puts expensive_calculation(10) # Retrieves from cache """ ### 6. Lazy Evaluation **Do This:** Use lazy evaluation (e.g., using "Enumerator::Lazy") for large collections and complex operations to avoid unnecessary computations. **Don't Do This:** Use lazy evaluation indiscriminately; it adds overhead that can outweigh its benefits for small datasets. **Why:** Only compute results when and if they are actually needed. **Code Example:** """ruby numbers = (1..Float::INFINITY).lazy.select { |n| n.even? }.map { |n| n * 2 }.first(10) puts numbers # => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] """ ### 7. Concurrency and Parallelism **Do This:** Use threads, fibers, or processes for concurrent tasks. Employ parallel processing for CPU-bound tasks using gems like "concurrent-ruby" or "Parallel". Consider using actor-based concurrency (e.g., celluloid). **Don't Do This:** Introduce concurrency without proper synchronization, which can lead to race conditions and deadlocks. Ignore the limitations of MRI's Global Interpreter Lock (GIL). **Why:** Leverage multi-core processors to perform tasks simultaneously. **Code Example (using "concurrent-ruby"):** """ruby require 'concurrent' executor = Concurrent::FixedThreadPool.new(4) # 4 threads futures = 10.times.map do |i| executor.post do puts "Processing task #{i} on thread: #{Thread.current.name}" sleep(rand(1..3)) # Simulate work "Result #{i}" end end results = futures.map(&:value) # Wait for all tasks to complete puts "Results: #{results}" executor.shutdown executor.wait_for_termination """ ### 8. Database Interactions **Do This:** Use efficient database queries, indexes, and caching. Optimize N+1 queries. Utilize connection pooling. **Don't Do This:** Perform inefficient queries or ignore database-level optimizations. **Why:** Database interactions often represent a major performance bottleneck. **Code Example (Addressing N+1 Query with "includes" in Rails):** """ruby # Inefficient # @orders = Order.all # @orders.each { |order| puts order.customer.name } # N+1 queries # Efficient @orders = Order.includes(:customer).all # Eager loads customer data @orders.each { |order| puts order.customer.name } # Single query """ ### 9. Garbage Collection **Do This:** Be aware of the impact of garbage collection. Trigger garbage collection manually when necessary (e.g., after large data processing). Control object allocation, prefer immutable objects or using object pools. Consider using garbage collection tuning options. **Don't Do This:** Ignore garbage collection and let it become a source of performance problems due to excessive pauses. **Why:** Excessive garbage collection pauses can significantly impact performance. **Code Example:** """ruby # Force garbage collection GC.start # Disable garbage collection GC.disable # Enable garbage collection GC.enable """ However, manually triggering GC is generally best avoided unless you *know* there has been a lot of dead object creation and the GC hasn't kicked in. ### 10. Use Profilers **Do This:** Use tools like RubyProf, stackprof, or flamegraph to identify hotspots in your code. **Don't Do This:** Guess where performance bottlenecks lie; use profiling to locate them. **Why:** Pinpoint the precise location of performance bottlenecks for focused optimization. **Code Example (using RubyProf):** """ruby require 'ruby-prof' RubyProf.start # Your code here def my_slow_method 100000.times { Math.sqrt(rand) } end my_slow_method result = RubyProf.stop printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT) # Prints profiling results """ ## III. Memory Management ### 1. Object Allocation **Do This:** Minimize unnecessary object allocations, especially within loops or frequently called methods. Use object pooling or reuse existing objects when possible. **Don't Do This:** Create many short-lived objects. These will be collected frequently & cause overhead on the garbage collector. **Why:** Allocating objects is time-consuming. Minimizing it contributes directly to performance improvements. **Code example:** """ruby # Inefficient def generate_strings(n) n.times.map { |i| "String #{i}" } # Each iteration creates a string end # Efficient def generate_strings_efficient(n) base_string = "String ".dup # Create one string n.times.map { |i| base_string.dup << i.to_s } # Copy and append the index number end """ ### 2. Immutable Objects **Do This**: Use immutable objects whenever applicable. Since they can't be modified, they can be safely shared and cached without concern for unintended modifications. **Don't Do This**: Avoid using mutable objects when you need to share state or data as they need to be duplicated to prevent issues. **Why**: Immutable objects simplify reasoning about program state and allow optimizations such as sharing or reusing them. Consider using libraries like "hamster" for immutable data structures. ### 3. Weak References **Do This**: Use Weak References if memory is low and some objects are not always necessary, but cached 'just in case'. **Don't Do This**: Do it without the WeakRef support. **Why**: Weak References are garbage collected if memory is needed by ruby and it won't complain about it. Code Example: """ruby require 'weakref' obj = Object.new weak_ref = WeakRef.new(obj) puts weak_ref.weakref_alive? # => true obj = nil # Remove reference to the object GC.start # Force garbage collection puts weak_ref.weakref_alive? # => false """ ## IV. Specific Concerns: Rails Applications ### 1. Asset Pipeline **Do This:** Precompile assets for production, use a CDN for static assets, and minimize asset size (e.g., using minification and compression). **Don't Do This:** Serve uncompiled assets in production, or include large and unoptimized assets. **Why:** Optimize the delivery of assets significantly improves page load times. ### 2. Query Optimization **Do This:** Optimize database queries using indexes, eager loading ("includes"), and avoiding N+1 queries. Utilize database-specific features (e.g., PostgreSQL's JSONB indexes). **Don't Do This:** Ignore slow queries or rely on inefficient database access patterns. ### 3. Caching **Do This:** Implement caching strategies at various levels (e.g., fragment caching, page caching, low-level caching with "Rails.cache"). Use a cache store like Redis or Memcached. **Don't Do This:** Cache inappropriately (e.g., caching personal data), or use overly aggressive or non-expiring caches. ### 4. Background Jobs **Do This:** Offload long-running tasks to background jobs (e.g., using Sidekiq, Resque, or Delayed Job) to improve web response times. **Don't Do This:** Perform complex and time-consuming operations within request-response cycles. ### 5. Middleware **Do This:** Carefully select and configure middleware components. Removing unnecessary middleware can reduce request processing overhead. **Don't Do This:** Add a lot of middlewares without actually seeing the benefits, as they can impact the performance negatively. **Conclusion** These performance optimization standards serve as a comprehensive guide for Ruby developers. By adhering to these guidelines, developers can create efficient, responsive, and scalable Ruby applications. Remember that continuous profiling, benchmarking, and monitoring are essential for identifying and addressing performance bottlenecks. Regularly revisiting and updating these standards will ensure they remain relevant with evolving Ruby versions and best practices.