ruby-patterns

Tier 1: Quick Reference - Common Idioms

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ruby-patterns" with this command: npx skills add geoffjay/claude-plugins/geoffjay-claude-plugins-ruby-patterns

Ruby Patterns Skill

Tier 1: Quick Reference - Common Idioms

Conditional Assignment

Set if nil

value ||= default_value

Set if falsy (nil or false)

value = value || default_value

Safe navigation

user&.profile&.avatar&.url

Array and Hash Shortcuts

Array creation

%w[apple banana orange] # ["apple", "banana", "orange"] %i[name email age] # [:name, :email, :age]

Hash creation

{ name: 'John', age: 30 } # Symbol keys { 'name' => 'John' } # String keys

Hash access with default

hash.fetch(:key, default) hash[:key] || default

Enumerable Shortcuts

Transformation

array.map(&:upcase) array.select(&:active?) array.reject(&:empty?)

Aggregation

array.sum array.max array.min numbers.reduce(:+)

Finding

array.find(&:valid?) array.any?(&:present?) array.all?(&:valid?)

String Operations

Interpolation

"Hello #{name}!"

Safe interpolation

"Result: %{value}" % { value: result }

Multiline

<<~TEXT Heredoc with indentation removed automatically TEXT

Block Syntax

Single line - use braces

array.map { |x| x * 2 }

Multi-line - use do/end

array.each do |item| process(item) log(item) end

Symbol to_proc

array.map(&:to_s) array.select(&:even?)

Guard Clauses

def process(user) return unless user return unless user.active?

Main logic here

end

Case Statements

Traditional

case status when 'active' activate when 'inactive' deactivate end

With ranges

case age when 0..17 'minor' when 18..64 'adult' else 'senior' end

Tier 2: Detailed Instructions - Design Patterns

Creational Patterns

Factory Pattern:

class UserFactory def self.create(type, attributes) case type when :admin AdminUser.new(attributes) when :member MemberUser.new(attributes) when :guest GuestUser.new(attributes) else raise ArgumentError, "Unknown user type: #{type}" end end end

Usage

user = UserFactory.create(:admin, name: 'John', email: 'john@example.com')

Builder Pattern:

class QueryBuilder def initialize @conditions = [] @order = nil @limit = nil end

def where(condition) @conditions << condition self end

def order(column) @order = column self end

def limit(count) @limit = count self end

def build query = "SELECT * FROM users" query += " WHERE #{@conditions.join(' AND ')}" if @conditions.any? query += " ORDER BY #{@order}" if @order query += " LIMIT #{@limit}" if @limit query end end

Usage

query = QueryBuilder.new .where("active = true") .where("age > 18") .order("created_at DESC") .limit(10) .build

Singleton Pattern:

require 'singleton'

class Configuration include Singleton

attr_accessor :api_key, :timeout

def initialize @api_key = ENV['API_KEY'] @timeout = 30 end end

Usage

config = Configuration.instance config.api_key = 'new_key'

Structural Patterns

Decorator Pattern:

Simple decorator

class User attr_accessor :name, :email

def initialize(name, email) @name = name @email = email end end

class AdminUser < SimpleDelegator def permissions [:read, :write, :delete, :admin] end

def admin? true end end

Usage

user = User.new('John', 'john@example.com') admin = AdminUser.new(user) admin.name # Delegates to user admin.admin? # From decorator

Using Ruby's Forwardable

require 'forwardable'

class UserDecorator extend Forwardable def_delegators :@user, :name, :email

def initialize(user) @user = user end

def display_name "#{@user.name} (#{@user.email})" end end

Adapter Pattern:

Adapting third-party API

class LegacyPaymentGateway def make_payment(amount, card) # Legacy implementation end end

class PaymentAdapter def initialize(gateway) @gateway = gateway end

def process(amount:, card_number:) card = { number: card_number } @gateway.make_payment(amount, card) end end

Usage

legacy = LegacyPaymentGateway.new adapter = PaymentAdapter.new(legacy) adapter.process(amount: 100, card_number: '1234')

Composite Pattern:

class File attr_reader :name, :size

def initialize(name, size) @name = name @size = size end

def total_size size end end

class Directory attr_reader :name

def initialize(name) @name = name @contents = [] end

def add(item) @contents << item end

def total_size @contents.sum(&:total_size) end end

Usage

root = Directory.new('root') root.add(File.new('file1.txt', 100)) subdir = Directory.new('subdir') subdir.add(File.new('file2.txt', 200)) root.add(subdir) root.total_size # 300

Behavioral Patterns

Strategy Pattern:

class PaymentProcessor def initialize(strategy) @strategy = strategy end

def process(amount) @strategy.process(amount) end end

class CreditCardStrategy def process(amount) puts "Processing #{amount} via credit card" end end

class PayPalStrategy def process(amount) puts "Processing #{amount} via PayPal" end end

Usage

processor = PaymentProcessor.new(CreditCardStrategy.new) processor.process(100)

processor = PaymentProcessor.new(PayPalStrategy.new) processor.process(100)

Observer Pattern:

require 'observer'

class Order include Observable

attr_reader :status

def initialize @status = :pending end

def complete! @status = :completed changed notify_observers(self) end end

class EmailNotifier def update(order) puts "Sending email: Order #{order.object_id} is #{order.status}" end end

class SMSNotifier def update(order) puts "Sending SMS: Order #{order.object_id} is #{order.status}" end end

Usage

order = Order.new order.add_observer(EmailNotifier.new) order.add_observer(SMSNotifier.new) order.complete! # Both notifiers triggered

Command Pattern:

class Command def execute raise NotImplementedError end

def undo raise NotImplementedError end end

class CreateUserCommand < Command def initialize(user_service, params) @user_service = user_service @params = params @user = nil end

def execute @user = @user_service.create(@params) end

def undo @user_service.delete(@user.id) if @user end end

class CommandInvoker def initialize @history = [] end

def execute(command) command.execute @history << command end

def undo command = @history.pop command&.undo end end

Usage

invoker = CommandInvoker.new command = CreateUserCommand.new(user_service, { name: 'John' }) invoker.execute(command) invoker.undo # Rolls back

Metaprogramming Techniques

Dynamic Method Definition:

class Model ATTRIBUTES = [:name, :email, :age]

ATTRIBUTES.each do |attr| define_method(attr) do instance_variable_get("@#{attr}") end

define_method("#{attr}=") do |value|
  instance_variable_set("@#{attr}", value)
end

end end

Usage

model = Model.new model.name = 'John' model.name # 'John'

Method Missing:

class DynamicFinder def initialize(data) @data = data end

def method_missing(method_name, *args) if method_name.to_s.start_with?('find_by_') attribute = method_name.to_s.sub('find_by_', '') @data.find { |item| item[attribute.to_sym] == args.first } else super end end

def respond_to_missing?(method_name, include_private = false) method_name.to_s.start_with?('find_by_') || super end end

Usage

data = [ { name: 'John', email: 'john@example.com' }, { name: 'Jane', email: 'jane@example.com' } ] finder = DynamicFinder.new(data) finder.find_by_name('John') # { name: 'John', ... } finder.find_by_email('jane@example.com') # { name: 'Jane', ... }

Class Macros (DSL):

class Validator def self.validates(attribute, rules) @validations ||= [] @validations << [attribute, rules]

define_method(:valid?) do
  self.class.instance_variable_get(:@validations).all? do |attr, rules|
    value = send(attr)
    validate_rules(value, rules)
  end
end

end

def validate_rules(value, rules) rules.all? do |rule, param| case rule when :presence !value.nil? && !value.empty? when :length value.length <= param when :format value.match?(param) else true end end end end

class User < Validator attr_accessor :name, :email

validates :name, presence: true, length: 50 validates :email, presence: true, format: /@/

def initialize(name, email) @name = name @email = email end end

Usage

user = User.new('John', 'john@example.com') user.valid? # true

Module Inclusion Hooks:

module Timestampable def self.included(base) base.class_eval do attr_accessor :created_at, :updated_at

  define_method(:touch) do
    self.updated_at = Time.now
  end
end

end end

Using ActiveSupport::Concern for cleaner syntax

module Trackable extend ActiveSupport::Concern

included do attr_accessor :tracked_at end

class_methods do def tracking_enabled? true end end

def track! self.tracked_at = Time.now end end

class Model include Timestampable include Trackable end

Usage

model = Model.new model.touch model.track!

Tier 3: Resources & Examples

Performance Patterns

Memoization:

Basic memoization

def expensive_calculation @expensive_calculation ||= begin # Expensive operation sleep 1 'result' end end

Memoization with parameters

def user_posts(user_id) @user_posts ||= {} @user_posts[user_id] ||= Post.where(user_id: user_id).to_a end

Thread-safe memoization

require 'concurrent'

class Service def initialize @cache = Concurrent::Map.new end

def get(key) @cache.compute_if_absent(key) do expensive_operation(key) end end end

Lazy Evaluation:

Lazy enumeration for large datasets

(1..Float::INFINITY) .lazy .select { |n| n % 3 == 0 } .first(10)

Lazy file processing

File.foreach('large_file.txt').lazy .select { |line| line.include?('ERROR') } .map(&:strip) .first(100)

Custom lazy enumerator

def lazy_range(start, finish) Enumerator.new do |yielder| current = start while current <= finish yielder << current current += 1 end end.lazy end

Struct for Value Objects:

Simple value object

User = Struct.new(:name, :email, :age) do def adult? age >= 18 end

def to_s "#{name} <#{email}>" end end

Keyword arguments (Ruby 2.5+)

User = Struct.new(:name, :email, :age, keyword_init: true) user = User.new(name: 'John', email: 'john@example.com', age: 30)

Data class (Ruby 3.2+)

User = Data.define(:name, :email, :age) do def adult? age >= 18 end end

Error Handling Patterns

Custom Exceptions:

class ApplicationError < StandardError; end class ValidationError < ApplicationError; end class NotFoundError < ApplicationError; end class AuthenticationError < ApplicationError; end

class UserService def create(params) raise ValidationError, 'Name is required' if params[:name].nil?

User.create(params)

rescue ActiveRecord::RecordNotFound => e raise NotFoundError, e.message end end

Usage with rescue

begin user_service.create(params) rescue ValidationError => e render json: { error: e.message }, status: 422 rescue NotFoundError => e render json: { error: e.message }, status: 404 rescue ApplicationError => e render json: { error: e.message }, status: 500 end

Result Object Pattern:

class Result attr_reader :value, :error

def initialize(success, value, error = nil) @success = success @value = value @error = error end

def success? @success end

def failure? !@success end

def self.success(value) new(true, value) end

def self.failure(error) new(false, nil, error) end

def on_success(&block) block.call(value) if success? self end

def on_failure(&block) block.call(error) if failure? self end end

Usage

def create_user(params) user = User.new(params) if user.valid? user.save Result.success(user) else Result.failure(user.errors) end end

result = create_user(params) result .on_success { |user| send_welcome_email(user) } .on_failure { |errors| log_errors(errors) }

Testing Patterns

Shared Examples:

RSpec.shared_examples 'a timestamped model' do it 'has created_at' do expect(subject).to respond_to(:created_at) end

it 'has updated_at' do expect(subject).to respond_to(:updated_at) end

it 'sets timestamps on create' do subject.save expect(subject.created_at).to be_present expect(subject.updated_at).to be_present end end

RSpec.describe User do it_behaves_like 'a timestamped model' end

Functional Programming Patterns

Composition:

Function composition

add_one = ->(x) { x + 1 } double = ->(x) { x * 2 } square = ->(x) { x ** 2 }

Manual composition

result = square.call(double.call(add_one.call(5))) # ((5+1)*2)^2 = 144

Compose helper

def compose(*fns) ->(x) { fns.reverse.reduce(x) { |acc, fn| fn.call(acc) } } end

composed = compose(square, double, add_one) composed.call(5) # 144

Immutability:

Frozen objects

class ImmutablePoint attr_reader :x, :y

def initialize(x, y) @x = x @y = y freeze end

def move(dx, dy) ImmutablePoint.new(@x + dx, @y + dy) end end

Frozen literals (Ruby 3+)

frozen_string_literal: true

NAME = 'John' # Frozen by default

Additional Resources

See assets/ directory for:

  • idioms-cheatsheet.md

  • Quick reference for Ruby idioms

  • design-patterns.rb

  • Complete implementations of all patterns

  • metaprogramming-examples.rb

  • Advanced metaprogramming techniques

See references/ directory for:

  • Style guides and best practices

  • Performance optimization examples

  • Testing pattern library

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

documentation-update

No summary provided by upstream source.

Repository SourceNeeds Review
General

git-troubleshooting

No summary provided by upstream source.

Repository SourceNeeds Review
General

git-advanced

No summary provided by upstream source.

Repository SourceNeeds Review
General

tokio-patterns

No summary provided by upstream source.

Repository SourceNeeds Review