ruby
Ruby programming patterns and idioms
What this skill does
# Ruby
## Overview
Ruby programming patterns including blocks, metaprogramming, and idiomatic Ruby code.
---
## Core Ruby Patterns
### Classes and Modules
```ruby
# Class definition
class User
attr_accessor :name, :email
attr_reader :id
attr_writer :password
# Class variable
@@count = 0
# Class method
def self.count
@@count
end
# Initialize
def initialize(name, email)
@id = SecureRandom.uuid
@name = name
@email = email
@@count += 1
end
# Instance method
def display_name
"#{name} <#{email}>"
end
# Private methods
private
def validate_email
email.include?('@')
end
end
# Inheritance
class Admin < User
attr_accessor :permissions
def initialize(name, email, permissions = [])
super(name, email)
@permissions = permissions
end
def has_permission?(perm)
permissions.include?(perm)
end
end
# Modules for mixins
module Timestampable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def timestamped_attrs
[:created_at, :updated_at]
end
end
def touch
@updated_at = Time.now
end
def created_at
@created_at ||= Time.now
end
end
# Including module
class Document
include Timestampable
include Comparable
attr_accessor :title, :content
def <=>(other)
title <=> other.title
end
end
# Module for namespacing
module MyApp
module Services
class UserService
def create(params)
# ...
end
end
end
end
```
### Blocks, Procs, and Lambdas
```ruby
# Block usage
[1, 2, 3].each { |n| puts n }
[1, 2, 3].map do |n|
n * 2
end
# Yield to block
def with_timing
start = Time.now
result = yield
elapsed = Time.now - start
puts "Elapsed: #{elapsed}s"
result
end
with_timing { sleep(0.1) }
# Block with arguments
def transform_items(items)
items.map { |item| yield(item) }
end
transform_items([1, 2, 3]) { |n| n * 2 }
# Check if block given
def optional_block
if block_given?
yield
else
"No block provided"
end
end
# Convert block to proc
def with_block(&block)
block.call(42)
end
# Proc
my_proc = Proc.new { |x| x * 2 }
my_proc.call(21) # => 42
# Lambda
my_lambda = ->(x) { x * 2 }
my_lambda.call(21) # => 42
# Proc vs Lambda differences
proc_example = Proc.new { |x, y| x }
proc_example.call(1) # Works, y is nil
lambda_example = ->(x, y) { x }
# lambda_example.call(1) # ArgumentError
# Symbol to proc
['a', 'b', 'c'].map(&:upcase)
# Equivalent to: ['a', 'b', 'c'].map { |s| s.upcase }
```
### Enumerable and Iterators
```ruby
# Array operations
numbers = [1, 2, 3, 4, 5]
# Map/collect
doubled = numbers.map { |n| n * 2 }
# Select/filter
evens = numbers.select(&:even?)
# Reject
odds = numbers.reject(&:even?)
# Reduce/inject
sum = numbers.reduce(0) { |acc, n| acc + n }
sum = numbers.reduce(:+)
# Each with index
numbers.each_with_index do |n, i|
puts "#{i}: #{n}"
end
# Find
found = numbers.find { |n| n > 3 }
# Any/all/none
numbers.any?(&:even?) # true
numbers.all? { |n| n > 0 } # true
numbers.none? { |n| n > 10 } # true
# Group by
users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' }
]
grouped = users.group_by { |u| u[:role] }
# => { 'admin' => [...], 'user' => [...] }
# Partition
passed, failed = scores.partition { |s| s >= 60 }
# Flat map
nested = [[1, 2], [3, 4]]
flat = nested.flat_map { |arr| arr.map { |n| n * 2 } }
# Lazy evaluation
(1..Float::INFINITY).lazy
.select(&:even?)
.map { |n| n * 2 }
.take(10)
.to_a
# Custom iterator
class Countdown
include Enumerable
def initialize(start)
@start = start
end
def each
@start.downto(0) { |n| yield n }
end
end
Countdown.new(5).to_a # => [5, 4, 3, 2, 1, 0]
```
---
## Metaprogramming
```ruby
# Method missing
class DynamicProxy
def initialize(target)
@target = target
end
def method_missing(method, *args, &block)
puts "Calling #{method} with #{args}"
@target.send(method, *args, &block)
end
def respond_to_missing?(method, include_private = false)
@target.respond_to?(method) || super
end
end
# Define method dynamically
class User
ROLES = %w[admin moderator user]
ROLES.each do |role|
define_method("#{role}?") do
@role == role
end
end
end
# Class macro
class MyModel
def self.attribute(name, type)
define_method(name) do
instance_variable_get("@#{name}")
end
define_method("#{name}=") do |value|
instance_variable_set("@#{name}", value)
end
end
attribute :name, :string
attribute :age, :integer
end
# Hook methods
class Base
def self.inherited(subclass)
puts "#{subclass} inherits from #{self}"
end
def self.method_added(method_name)
puts "Method #{method_name} added"
end
end
# Instance eval / class eval
class Config
def self.configure(&block)
instance_eval(&block)
end
def self.setting(name, value)
define_singleton_method(name) { value }
end
end
Config.configure do
setting :api_key, 'abc123'
setting :timeout, 30
end
# Send / public_send
obj.send(:private_method) # Can call private
obj.public_send(:public_method) # Only public
# Refinements (safer monkey patching)
module StringExtensions
refine String do
def to_slug
downcase.gsub(/\s+/, '-')
end
end
end
class MyClass
using StringExtensions
def process(title)
title.to_slug
end
end
```
---
## Error Handling
```ruby
# Basic exception handling
begin
risky_operation
rescue StandardError => e
puts "Error: #{e.message}"
puts e.backtrace.first(5).join("\n")
ensure
cleanup
end
# Multiple rescue clauses
begin
parse_file(path)
rescue Errno::ENOENT
puts "File not found"
rescue JSON::ParserError => e
puts "Invalid JSON: #{e.message}"
rescue => e
puts "Unknown error: #{e.class}"
end
# Retry
attempts = 0
begin
attempts += 1
connect_to_server
rescue ConnectionError
retry if attempts < 3
raise
end
# Custom exceptions
class AppError < StandardError
attr_reader :code
def initialize(message, code: nil)
super(message)
@code = code
end
end
class ValidationError < AppError
attr_reader :errors
def initialize(errors)
super("Validation failed")
@errors = errors
end
end
# Raise with custom exception
raise ValidationError.new({ email: ['is invalid'] })
# Re-raise with context
begin
process_order(order)
rescue => e
raise "Failed to process order #{order.id}: #{e.message}"
end
# Result object pattern
class Result
attr_reader :value, :error
def self.success(value)
new(value: value)
end
def self.failure(error)
new(error: error)
end
def initialize(value: nil, error: nil)
@value = value
@error = error
end
def success?
error.nil?
end
def failure?
!success?
end
def then
return self if failure?
yield(value)
end
end
# Usage
def create_user(params)
return Result.failure('Email required') unless params[:email]
user = User.create(params)
Result.success(user)
rescue ActiveRecord::RecordInvalid => e
Result.failure(e.message)
end
```
---
## Testing with RSpec
```ruby
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'validations' do
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_uniqueness_of(:email) }
end
describe 'associations' do
it { is_expected.to have_many(:posts) }
it { is_expected.to belong_to(:organization) }
end
describe '#display_name' do
subject(:user) { build(:user, name: 'John', email: '[email protected]') }
it 'returns formatted name with email' do
expect(user.display_name).to eq('John <[email protected]>')
end
end
describe '.active' do
let!(:active_user) { create(:user, active: true) }
let!(:inactive_user) { create(:user, active: false) }
it 'returns only active users' do
expect(User.active).to contain_exactly(active_user)
end
end
contexRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.