Claude Code Plugins

Community-maintained marketplace

Feedback

Modern Ruby idioms, design patterns, metaprogramming techniques, and best practices. Use when writing Ruby code or refactoring for clarity.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ruby-patterns
description Modern Ruby idioms, design patterns, metaprogramming techniques, and best practices. Use when writing Ruby code or refactoring for clarity.

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