# Tooling and Ecosystem Standards for Ruby
This document outlines the recommended tooling and ecosystem practices for Ruby development. Adhering to these standards ensures maintainable, performant, and secure applications, aligned with modern Ruby approaches.
## Dependency Management
Choosing and managing dependencies wisely is crucial to a project's stability and security.
### Bundler
Bundler is the standard dependency management tool for Ruby. It ensures consistent gem versions across different environments.
**Do This:**
* Always use Bundler for managing gem dependencies.
* Specify gem versions explicitly or use pessimistic version constraints ("'~> 3.0'") to avoid unexpected breaking changes.
* Regularly update dependencies with "bundle update" (ideally after thorough testing).
* Use ".bundle/config" to configure Bundler settings like mirror URLs and deployment options.
**Don't Do This:**
* Avoid using "gem install" directly without Bundler, as it can lead to dependency conflicts.
* Don't commit the "Gemfile.lock" file. It _should_ be committed. It is critical to repeatable builds.
* Don't leave gem versions unspecified (e.g., just "gem 'rails'").
**Why:** Bundler ensures consistency and avoids dependency conflicts, crucial for maintainability and preventing "works on my machine" issues.
**Example:**
"""ruby
# Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 7.0' # Restricts to versions 7.0.x
gem 'puma', '~> 5.0'
# .bundle/config
BUNDLE_MIRROR__RUBYGEMS_ORG: "https://some-internal-mirror.com" # Example internal mirror
"""
### Gemfile Structure and Organization
A well-structured "Gemfile" enhances readability and maintainability.
**Do This:**
* Organize gems into logical groups (e.g., ":development", ":test", ":production").
* Use comments to explain the purpose of gems when necessary.
* Keep the "Gemfile" alphabetized within each group.
* Prefer using named groups over inline conditional loading.
**Don't Do This:**
* Avoid long, uncommented lists of gems.
* Don't mix development and production dependencies in the default group.
* Don't hardcode environment-specific gems using "ENV['RACK_ENV']". Instead, use Bundler groups.
**Why:** A well-organized "Gemfile" makes it easier to understand dependencies and reduces the risk of including unnecessary gems in production.
**Example:**
"""ruby
# Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 7.0'
gem 'puma', '~> 5.0'
group :development do
gem 'pry'
gem 'rubocop', require: false
end
group :test do
gem 'rspec-rails'
gem 'capybara'
end
"""
### Private Gems
When using private gems, configure Bundler appropriately.
**Do This:**
* Use "bundle config" to set the authentication credentials for your private gem source.
* Consider using environment variables for sensitive credentials.
* Ensure the private gem source is HTTPS for security.
**Don't Do This:**
* Don't hardcode credentials directly in the "Gemfile".
* Don't use HTTP for private gem sources.
**Why:** Protects your private gems and ensures secure access to your gem repositories.
**Example:**
"""bash
bundle config gems.my_private_gem_source.com username:your_username
bundle config gems.my_private_gem_source.com password:your_password
# Use environment variables:
bundle config gems.my_private_gem_source.com username:$PRIVATE_GEM_USERNAME
bundle config gems.my_private_gem_source.com password:$PRIVATE_GEM_PASSWORD
"""
"""ruby
# Gemfile
source 'https://gems.my_private_gem_source.com'
gem 'my_private_gem'
"""
## Testing and Quality Assurance
Robust testing and quality assurance practices are essential.
### RSpec
RSpec is a popular and expressive testing framework for Ruby.
**Do This:**
* Write comprehensive tests covering various scenarios (unit, integration, system).
* Use descriptive test names that clearly explain the expected behavior.
* Follow the "Arrange, Act, Assert" (AAA) pattern in your tests.
* Use mocks and stubs judiciously to isolate units under test.
* Keep tests focused and avoid testing implementation details.
* Configure ".rspec" to enable useful flags (e.g., "--format documentation", "--color").
**Don't Do This:**
* Avoid having large, complex tests that are difficult to understand and maintain.
* Don't skip writing tests for critical functionality.
* Don't tightly couple tests to implementation details, making them brittle.
* Don't ignore failing tests; address them promptly.
**Why:** RSpec helps ensure code correctness, prevent regressions, and improve code quality.
**Example:**
"""ruby
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe '#full_name' do
it 'returns the full name of the user' do
user = User.new(first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
end
end
"""
### Code Coverage
Code coverage tools help identify untested code.
**Do This:**
* Use a code coverage tool like SimpleCov to measure test coverage.
* Set coverage thresholds to ensure sufficient test coverage (e.g., 80% or higher).
* Focus on increasing coverage for critical and complex code.
**Don't Do This:**
* Don't aim for 100% coverage blindly; focus on testing important logic.
* Don't ignore coverage reports; use them to identify gaps in testing.
**Why:** Code coverage helps you understand what parts of your code are being tested and where more tests might be necessary.
**Example:**
"""ruby
# spec/rails_helper.rb
require 'simplecov'
SimpleCov.start 'rails'
RSpec.configure do |config|
# ... other configurations
end
"""
### Static Analysis
Static analysis tools help identify potential issues before runtime.
**Do This:**
* Use RuboCop for code stylelinting based on the community Ruby Style Guide.
* Use tools like Reek to detect code smells.
* Integrate static analysis into your development workflow (e.g., using Git hooks or CI).
* Customize RuboCop rules to match your project's specific needs.
* Enable auto-correction where possible to automatically fix style issues.
**Don't Do This:**
* Don't ignore the warnings and errors reported by static analysis tools.
* Don't disable useful rules without a good reason.
* Don't commit code with style violations or code smells.
**Why:** Static analysis enforces code consistency, catches potential bugs, and improves code quality.
**Example:**
"""ruby
# .rubocop.yml
require:
- rubocop-rails
- rubocop-rspec
AllCops:
TargetRubyVersion: 3.2 # Specify target Ruby version
Exclude:
- 'db/**/*'
- 'config/**/*'
- 'vendor/**/*'
Style/Documentation:
Enabled: false # Disable documentation requirement globally. Prefer YARD
Metrics/BlockLength:
Exclude:
- 'spec/**/*'
Rails/SkipsModelValidations:
Enabled: false # Allow skips model validations by default
"""
## Logging
Effective logging is essential for debugging, monitoring, and auditing applications.
### Standard Logger
Use the standard "Logger" class in Ruby's standard library.
**Do This:**
* Configure the logger's severity level (e.g., "DEBUG", "INFO", "WARN", "ERROR", "FATAL").
* Use meaningful log messages that provide context and help with debugging.
* Log exceptions with their backtraces for detailed error information.
* Use structured logging (e.g., JSON) for easier parsing and analysis by logging tools.
* Consider using a logging library like Lograge for Rails to reduce noise.
* Sanitize sensitive data (e.g., passwords, API keys) before logging.
**Don't Do This:**
* Avoid using "puts" or "print" for logging in production.
* Don't log excessively, as it can impact performance.
* Don't log sensitive information in plain text.
* Don't ignore error logs; investigate and address them promptly.
**Why:** Proper logging provides valuable insights into application behavior, aids in debugging, and facilitates monitoring.
**Example:**
"""ruby
# config/application.rb
config.log_level = :info
# app/controllers/users_controller.rb
logger.info "Processing request for user with id: #{params[:id]}"
begin
@user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
logger.error "User not found: #{e.message}\n#{e.backtrace.join("\n")}" # Log the backtrace
render plain: "User not found", status: :not_found
end
#Structured logging example:
logger.info({event: 'user_login', user_id: @user.id, ip_address: request.remote_ip}.to_json)
"""
### Lograge
Lograge helps clean up Rails logs by consolidating the request information into a single line.
**Do This:**
* Include the "lograge" gem in your "Gemfile".
* Enable Lograge in your "config/application.rb".
* Customize the Lograge configuration to include relevant request parameters.
**Don't Do This:**
* Don't enable Lograge without customizing the parameters to be logged.
* Don't log sensitive data without sanitizing it within the Lograge configuration.
**Why:** Enhances log readability and makes it easier to analyze application performance.
**Example:**
"""ruby
# Gemfile
gem 'lograge'
# config/application.rb
config.lograge.enabled = true
config.lograge.formatter = Lograge::Formatters::Json.new #JSON formatter
config.lograge.custom_options = lambda do |event|
{
params: event.payload[:params].except('controller', 'action', 'password', 'password_confirmation') #Sanitize params
}
end
"""
## Documentation
Clear and comprehensive documentation is essential for maintainability and collaboration.
### YARD
YARD is the standard documentation generation tool for Ruby.
**Do This:**
* Use YARD syntax for documenting classes, modules, methods, and attributes.
* Provide clear and concise descriptions of the purpose and functionality of each documented element.
* Document the expected parameters and return values of methods.
* Use tags like "@param", "@return", "@raise", "@see", "@example" to add structured information.
* Generate YARD documentation regularly and host it in a readily accessible location (e.g., GitHub Pages).
* Ensure that public APIs are documented.
**Don't Do This:**
* Avoid incomplete or outdated documentation.
* Don't neglect documenting important aspects of your codebase.
* Don't document obvious things; focus on explaining the intent and usage.
**Why:** YARD-generated documentation provides a consistent and accessible reference for your codebase.
**Example:**
"""ruby
# app/models/user.rb
# Represents a user in the system.
class User < ApplicationRecord
# Returns the user's full name.
# @return [String] The user's full name.
def full_name
"#{first_name} #{last_name}"
end
# Sends a welcome email to the user.
# @param email [String] The email address to send the email to.
# @raise [ArgumentError] if the email is nil or empty.
# @return [Boolean] True if the email was sent successfully, false otherwise.
# @example
# user.send_welcome_email("user@example.com")
def send_welcome_email(email)
raise ArgumentError, "Email cannot be nil or empty" if email.nil? || email.empty?
#Logic to send email
end
end
"""
### README
The "README" file is the entry point to your project and should provide a high-level overview.
**Do This:**
* Include a clear and concise description of the project's purpose.
* Provide instructions for installation and setup.
* Include examples of how to use the project.
* List any dependencies and how to install them.
* Provide information on how to contribute to the project.
* Include license information.
**Don't Do This:**
* Don't leave the "README" file empty or generic.
* Don't provide incomplete or outdated information.
**Why:** A well-written "README" file makes it easier for others to understand and use your project.
## Continuous Integration and Deployment (CI/CD)
Automating the build, test, and deployment process is crucial for modern software development.
### GitHub Actions, GitLab CI, or Jenkins
Choose a CI/CD platform that integrates well with your source code repository.
**Do This:**
* Automate the build, test, and deployment process using a CI/CD pipeline.
* Run static analysis and code coverage tools as part of the CI pipeline.
* Use environment variables for sensitive information (e.g., API keys, database passwords).
* Implement automated testing for each commit and pull request.
* Use infrastructure as code to manage and provision infrastructure automatically.
**Don't Do This:**
* Don't manually deploy code to production.
* Don't skip automated testing in the CI/CD pipeline.
* Don't store sensitive information directly in the CI/CD configuration.
**Why:** CI/CD automates the development lifecycle, reducing errors and improving efficiency.
**Example (GitHub Actions):**
"""yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2 #specify ruby version
bundler-cache: true
- name: Run tests
run: bundle exec rspec
- name: Run RuboCop
run: bundle exec rubocop
"""
## Monitoring and Performance
Monitoring application performance and identifying bottlenecks is essential for delivering a good user experience.
### Skylight, New Relic, or Datadog
Use an APM (Application Performance Monitoring) tool to track application performance.
**Do This:**
* Monitor application performance metrics such as response time, throughput, and error rate.
* Identify and address performance bottlenecks.
* Set up alerts for critical performance issues.
* Use profiling tools to identify slow code.
**Don't Do This:**
* Don't ignore performance issues.
* Don't rely solely on manual testing for performance monitoring.
**Why:** APM tools provide insights into application performance, allowing you to optimize and improve the user experience.
## Security
Security should be a primary concern in all stages of development.
### Brakeman
Use Brakeman to scan your Rails code for security vulnerabilities.
**Do This:**
* Run Brakeman regularly to identify potential security issues.
* Address any vulnerabilities reported by Brakeman promptly.
* Keep Brakeman up-to-date.
* Integrate Brakeman into your CI/CD pipeline.
**Don't Do This:**
* Don't ignore security vulnerabilities reported by Brakeman.
* Don't disable Brakeman without a good reason.
**Why:** Brakeman helps you identify and fix security vulnerabilities in your Rails applications.
**Example:**
"""bash
brakeman -q -w1 #Quiet mode, warning level 1 (high confidence)
"""
### Dependency Scanning
Scan your dependencies for known vulnerabilities.
**Do This:**
* Use tools like "bundler-audit" to check your dependencies for vulnerabilities.
* Regularly update dependencies to patch security vulnerabilities.
* Implement a security policy for addressing vulnerabilities.
**Don't Do This:**
* Don't ignore dependency vulnerabilities.
* Don't use outdated or unsupported dependencies.
**Why:** Dependency scanning helps you identify and mitigate security risks associated with your dependencies.
**Example:**
"""bash
bundle audit check
"""
### General Security Practices
Implement general security best practices.
**Do This:**
* Use strong passwords and rotate them regularly.
* Sanitize user input to prevent cross-site scripting (XSS) and SQL injection attacks.
* Use HTTPS for all communication.
* Store sensitive data securely (e.g., using bcrypt for password hashing).
* Follow the principle of least privilege when granting access to resources.
* Be aware of common security vulnerabilities (e.g., OWASP Top 10).
**Don't Do This:**
* Don't store passwords in plain text.
* Don't trust user input without sanitizing it.
* Don't expose sensitive information in error messages or logs.
**Why:** Following security best practices protects your application and its users from security threats.
By adhering to these tooling and ecosystem standards, Ruby development teams can create maintainable, performant, and secure applications aligned with modern practices.
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.