developing-ruby

jkappers's avatarfrom jkappers

Use when working with .rb files, Ruby gems, or implementing Ruby features. Covers Ruby idioms, naming conventions, enumerable patterns, service objects, error handling, and RSpec testing.

⭐0stars🔀0forks📁View on GitHub🕐Updated Dec 17, 2025

When & Why to Use This Skill

This Claude skill provides a comprehensive framework for professional Ruby development, ensuring high-quality code through idiomatic patterns, strict naming conventions, and robust RSpec testing practices. It streamlines the creation of Ruby gems and features by enforcing industry-standard service objects, advanced error handling, and modern Ruby 3+ syntax like pattern matching and one-line shorthand methods.

Use Cases

  • Refactoring legacy Ruby code to adopt modern idioms, such as symbol-to-proc mappings, safe navigation, and Enumerable patterns.
  • Architecting scalable business logic using the Service Object pattern and Result objects to handle success and failure states cleanly.
  • Implementing comprehensive test suites with RSpec, utilizing instance doubles, spies, and custom matchers for high-confidence QA.
  • Standardizing new Ruby projects or gems with proper directory structures, naming conventions, and mandatory frozen_string_literal pragmas.
  • Designing robust error handling systems with custom exception hierarchies and guard clauses to improve application resilience.
namedeveloping-ruby
descriptionUse when working with .rb files, Ruby gems, or implementing Ruby features. Covers Ruby idioms, naming conventions, enumerable patterns, service objects, error handling, and RSpec testing.

Ruby Development

Apply these patterns when working with Ruby code.

Naming Conventions

Element Convention Example
Classes, Modules PascalCase UserService
Methods, variables snake_case find_user_by_id
Constants SCREAMING_SNAKE MAX_RETRIES
Predicates snake_case? active?
Dangerous methods snake_case! save!
Files snake_case user_service.rb

Project Structure

lib/
├── my_app.rb
└── my_app/
    ├── version.rb
    └── services/
spec/
├── spec_helper.rb
└── services/
Gemfile

Idioms

Blocks and Lambdas

users.map { |u| u.name }
users.map(&:name)  # Symbol to proc

double = ->(n) { n * 2 }
double.call(5)

Enumerable

users.select(&:active?)
users.reject(&:banned?)
users.find { |u| u.id == 5 }
users.group_by(&:country)
orders.sum(&:total)

users.select(&:active?).sort_by(&:created_at).first(10).map(&:email)

Safe Navigation

user&.profile&.avatar_url
name = user.name || "Anonymous"

def expensive_calculation
  @expensive_calculation ||= perform_calculation
end

Pattern Matching (Ruby 3+)

case response
in { status: 200, body: }
  process(body)
in { status: 404 }
  handle_not_found
end

Classes

class User
  attr_reader :id, :email
  attr_accessor :name

  def initialize(id:, email:, name: nil)
    @id = id
    @email = email
    @name = name
  end

  def active?
    @status == :active
  end

  private

  def save!
    # Persist changes
  end
end

Modules for Composition

module Timestampable
  def touch
    @updated_at = Time.current
  end
end

class User
  include Timestampable
end

Service Objects

class CreateUser
  def initialize(user_repository:, email_service:)
    @user_repository = user_repository
    @email_service = email_service
  end

  def call(email:, name:)
    user = @user_repository.create(email: email, name: name)
    @email_service.send_welcome(user)
    user
  end
end

Error Handling

module MyApp
  class Error < StandardError; end
  class NotFoundError < Error; end
  class ValidationError < Error
    attr_reader :errors
    def initialize(errors)
      @errors = errors
      super("Validation failed: #{errors.join(', ')}")
    end
  end
end

raise MyApp::NotFoundError, "User not found"

Result Objects

class Result
  attr_reader :value, :error

  def self.success(value) = new(value: value)
  def self.failure(error) = new(error: error)

  def success? = @error.nil?
  def failure? = !success?

  def then
    return self if failure?
    yield(value)
  end
end

Testing with RSpec

RSpec.describe UserService do
  subject(:service) { described_class.new(repository: repository) }
  let(:repository) { instance_double(UserRepository) }
  let(:user) { build(:user, id: 1, name: "John") }

  describe "#find_by_id" do
    context "when user exists" do
      before { allow(repository).to receive(:find).with(1).and_return(user) }

      it "returns the user" do
        expect(service.find_by_id(1)).to eq(user)
      end
    end
  end
end

Matchers

expect(result).to eq(expected)
expect(user).to be_nil
expect(list).to include(item)
expect { user.activate! }.to change(user, :status).to(:active)
expect { risky_op }.to raise_error(MyError)

Doubles

repository = instance_double(UserRepository)
allow(repository).to receive(:find).with(1).and_return(user)

mailer = instance_spy(UserMailer)
service.call
expect(mailer).to have_received(:send_welcome).with(user)

Requirements

  1. Use composition over inheritance
  2. Use keyword arguments for methods with 2+ parameters
  3. Return early with guard clauses
  4. Add # frozen_string_literal: true to every file
  5. Use symbols for hash keys
  6. Use Enumerable methods over manual iteration
  7. Name predicate methods with ?
  8. Raise exceptions only for exceptional cases; use Result objects for expected failures