ruby-metaprogramming
$
npx mdskill add TheBushidoCollective/han/ruby-metaprogrammingEnables Ruby metaprogramming for dynamic code generation, method handling, and reflection tasks.
- Helps developers automate repetitive code patterns and handle undefined methods dynamically.
- Integrates with Bash, Read, Write, and Edit tools for file operations and code manipulation.
- Uses Ruby's built-in features like define_method and method_missing to generate or modify code based on context.
- Presents results through code examples and explanations in Ruby scripts or edited files.
SKILL.md
.github/skills/ruby-metaprogrammingView on GitHub ↗
---
name: ruby-metaprogramming
user-invocable: false
description: Use when working with Ruby metaprogramming features including dynamic method definition, method_missing, class_eval, define_method, and reflection.
allowed-tools:
- Bash
- Read
- Write
- Edit
---
# Ruby Metaprogramming
Master Ruby's powerful metaprogramming capabilities to write code that writes code. Ruby's dynamic nature makes it exceptionally good at metaprogramming.
## Dynamic Method Definition
### define_method
```ruby
class Person
[:name, :age, :email].each do |attribute|
define_method(attribute) do
instance_variable_get("@#{attribute}")
end
define_method("#{attribute}=") do |value|
instance_variable_set("@#{attribute}", value)
end
end
end
person = Person.new
person.name = "Alice"
puts person.name # "Alice"
```
### class_eval and instance_eval
```ruby
# class_eval - Evaluates code in context of a class
class MyClass
end
MyClass.class_eval do
def hello
"Hello from class_eval"
end
end
puts MyClass.new.hello
# instance_eval - Evaluates code in context of an instance
obj = Object.new
obj.instance_eval do
def greet
"Hello from instance_eval"
end
end
puts obj.greet
```
### module_eval
```ruby
module MyModule
end
MyModule.module_eval do
def self.info
"Module metaprogramming"
end
end
puts MyModule.info
```
## Method Missing
### Basic method_missing
```ruby
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
users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 }
]
finder = DynamicFinder.new(users)
puts finder.find_by_name("Alice") # {:name=>"Alice", :age=>30}
```
### Const Missing
```ruby
class DynamicConstants
def self.const_missing(const_name)
puts "Constant #{const_name} not found, creating it..."
const_set(const_name, "Dynamic value for #{const_name}")
end
end
puts DynamicConstants::SOMETHING # "Dynamic value for SOMETHING"
```
## send and public_send
```ruby
class Calculator
def add(x, y)
x + y
end
private
def secret_method
"This is private"
end
end
calc = Calculator.new
# send can call any method (including private)
puts calc.send(:add, 3, 4) # 7
puts calc.send(:secret_method) # "This is private"
# public_send only calls public methods
puts calc.public_send(:add, 3, 4) # 7
# calc.public_send(:secret_method) # NoMethodError
```
## Class Macros
```ruby
class ActiveModel
def self.attr_with_history(attribute)
define_method(attribute) do
instance_variable_get("@#{attribute}")
end
define_method("#{attribute}=") do |value|
history = instance_variable_get("@#{attribute}_history") || []
history << value
instance_variable_set("@#{attribute}_history", history)
instance_variable_set("@#{attribute}", value)
end
define_method("#{attribute}_history") do
instance_variable_get("@#{attribute}_history") || []
end
end
end
class Person < ActiveModel
attr_with_history :name
end
person = Person.new
person.name = "Alice"
person.name = "Alicia"
puts person.name_history.inspect # ["Alice", "Alicia"]
```
## Singleton Methods
```ruby
obj = "hello"
# Define method on single instance
def obj.shout
self.upcase + "!!!"
end
puts obj.shout # "HELLO!!!"
# Using define_singleton_method
obj.define_singleton_method(:whisper) do
self.downcase + "..."
end
puts obj.whisper # "hello..."
```
## Eigenclass (Singleton Class)
```ruby
class Person
def self.species
"Homo sapiens"
end
end
# Accessing eigenclass
eigenclass = class << Person
self
end
puts eigenclass # #<Class:Person>
# Adding class methods via eigenclass
class Person
class << self
def count
@@count ||= 0
end
def increment_count
@@count ||= 0
@@count += 1
end
end
end
Person.increment_count
puts Person.count # 1
```
## Reflection and Introspection
### Object Introspection
```ruby
class MyClass
def public_method; end
protected
def protected_method; end
private
def private_method; end
end
obj = MyClass.new
# List methods
puts obj.methods.include?(:public_method)
puts obj.private_methods.include?(:private_method)
puts obj.protected_methods.include?(:protected_method)
# Check method existence
puts obj.respond_to?(:public_method) # true
puts obj.respond_to?(:private_method) # false
puts obj.respond_to?(:private_method, true) # true (include private)
# Get method object
method = obj.method(:public_method)
puts method.class # Method
```
### Class Introspection
```ruby
class Parent
def parent_method; end
end
class Child < Parent
def child_method; end
end
# Inheritance chain
puts Child.ancestors # [Child, Parent, Object, Kernel, BasicObject]
# Instance methods
puts Child.instance_methods(false) # Only Child's methods
# Class variables and instance variables
class Person
@@count = 0
def initialize(name)
@name = name
end
end
puts Person.class_variables # [:@@count]
person = Person.new("Alice")
puts person.instance_variables # [:@name]
```
## Hook Methods
### Inheritance Hooks
```ruby
class BaseClass
def self.inherited(subclass)
puts "#{subclass} inherited from #{self}"
subclass.instance_variable_set(:@inherited_at, Time.now)
end
end
class ChildClass < BaseClass
end
# Output: ChildClass inherited from BaseClass
```
### Method Hooks
```ruby
module Monitored
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def method_added(method_name)
puts "Method #{method_name} was added to #{self}"
end
def method_removed(method_name)
puts "Method #{method_name} was removed from #{self}"
end
end
end
class MyClass
include Monitored
def my_method
end
# Output: Method my_method was added to MyClass
end
```
### included and extended
```ruby
module MyModule
def self.included(base)
puts "#{self} included in #{base}"
base.extend(ClassMethods)
end
def self.extended(base)
puts "#{self} extended by #{base}"
end
module ClassMethods
def class_method
"I'm a class method"
end
end
def instance_method
"I'm an instance method"
end
end
class MyClass
include MyModule # Adds instance_method as instance method
end
class AnotherClass
extend MyModule # Adds instance_method as class method
end
```
## DSL Creation
```ruby
class RouteBuilder
def initialize
@routes = {}
end
def get(path, &block)
@routes[path] = { method: :get, handler: block }
end
def post(path, &block)
@routes[path] = { method: :post, handler: block }
end
def routes
@routes
end
end
# DSL usage
builder = RouteBuilder.new
builder.instance_eval do
get "/users" do
"List of users"
end
post "/users" do
"Create user"
end
end
puts builder.routes
```
## Dynamic Class Creation
```ruby
# Create class dynamically
MyClass = Class.new do
define_method :greet do
"Hello from dynamic class"
end
end
puts MyClass.new.greet
# Create class with inheritance
Parent = Class.new do
def parent_method
"From parent"
end
end
Child = Class.new(Parent) do
def child_method
"From child"
end
end
child = Child.new
puts child.parent_method
puts child.child_method
```
## Object Extension
```ruby
module Greetable
def greet
"Hello!"
end
end
obj = Object.new
obj.extend(Greetable)
puts obj.greet # "Hello!"
# Only this instance has the method
another_obj = Object.new
# another_obj.greet # NoMethodError
```
## Binding and eval
```ruby
def get_binding(param)
local_var = "local value"
binding
end
b = get_binding("test")
# Evaluate code in the binding context
puts eval("param", b) # "test"
puts eval("local_var", b) # "local value"
# instance_eval with binding
class MyClass
def initialize
@value = 42
end
end
obj = MyClass.new
puts obj.instance_eval { @value } # 42
```
## TracePoint
```ruby
trace = TracePoint.new(:call, :return) do |tp|
puts "#{tp.event}: #{tp.method_id} in #{tp.defined_class}"
end
trace.enable
def my_method
"Hello"
end
my_method
trace.disable
```
## Best Practices
1. **Use metaprogramming sparingly** - it can make code hard to understand
2. **Always implement respond_to_missing?** when using method_missing
3. **Prefer define_method over class_eval** when possible
4. **Document metaprogramming heavily** - it's not obvious what's happening
5. **Use public_send over send** to respect visibility
6. **Cache metaprogrammed methods** to avoid repeated definition
7. **Test metaprogrammed code thoroughly** - bugs can be subtle
## Anti-Patterns
❌ **Don't overuse method_missing** - it's slow and hard to debug
❌ **Don't use eval with user input** - major security risk
❌ **Don't metaprogram when simple code works** - clarity over cleverness
❌ **Don't forget to call super** in method_missing
❌ **Don't create methods without documenting them** - IDE support breaks
## Common Use Cases
- **ORMs** (ActiveRecord) - Dynamic finders, associations
- **DSLs** - Route definitions, configurations
- **Decorators** - Method wrapping and enhancement
- **Mocking/Stubbing** - Test frameworks
- **Attribute definition** - Custom accessors with behavior
## Related Skills
- ruby-oop - Understanding classes and modules
- ruby-blocks-procs-lambdas - For callbacks and dynamic behavior
- ruby-gems - Many gems use metaprogramming extensively
More from TheBushidoCollective/han
- absinthe-resolversUse when implementing GraphQL resolvers with Absinthe. Covers resolver patterns, dataloader integration, batching, and error handling.
- absinthe-schemaUse when designing GraphQL schemas with Absinthe. Covers type definitions, interfaces, unions, enums, and schema organization patterns.
- absinthe-subscriptionsUse when implementing real-time GraphQL subscriptions with Absinthe. Covers Phoenix channels, PubSub, and subscription patterns.
- act-docker-setupUse when configuring Docker environments for act, selecting runner images, managing container resources, or troubleshooting Docker-related issues with local GitHub Actions testing.
- act-local-testingUse when testing GitHub Actions workflows locally with act. Covers act CLI usage, Docker configuration, debugging workflows, and troubleshooting common issues when running workflows on your local machine.
- act-workflow-syntaxUse when creating or modifying GitHub Actions workflow files. Provides guidance on workflow syntax, triggers, jobs, steps, and expressions for creating valid GitHub Actions workflows that can be tested locally with act.
- ameba-configurationUse when configuring Ameba rules and settings for Crystal projects including .ameba.yml setup, rule management, severity levels, and code quality enforcement.
- ameba-custom-rulesUse when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.
- ameba-integrationUse when integrating Ameba into development workflows including CI/CD pipelines, pre-commit hooks, GitHub Actions, and automated code review processes.
- analyze-performanceAnalyze performance metrics and identify slow transactions in Sentry