rubyguides

Ruby define_method: Create Dynamic Methods at Runtime

What is define_method?

In Ruby, most methods are defined with the def keyword at parse time. You write the method name, and Ruby creates it immediately when your program runs. That’s the static approach.

define_method takes a different route. It’s a method on the Module class, which means every class and module in Ruby has access to it. Instead of naming a method directly in your source code, you pass the name as an argument and supply the method body as a block.

class Greeter
  define_method(:greet) do |name|
    "Hello, #{name}!"
  end
end

Greeter.new.greet("World")  # => "Hello, World!"

The method name can be a symbol or a string. Ruby converts it internally via to_sym, but the convention is to use a symbol — it makes your intent clearer.

intro context

define_method is the tool you reach for when the method name or method body is not fully known until runtime. That can happen in DSLs, adapters, data-driven APIs, or any place where you want to generate several similar methods from the same pattern. The payoff is less repetition and more flexibility, but the cost is that the generated code can be harder to scan if you overuse it.

If you want the official background while you read, the Ruby docs for Module#define_method explain the core API. The examples below focus on the day-to-day cases where define_method is most useful.

tl;dr

  • Use define_method when the method name is dynamic or generated from data.
  • It captures surrounding local variables through closure, which def cannot do.
  • Use class_eval or instance_eval carefully depending on whether you want class methods or object-specific behavior.
  • Keep visibility explicit, because define_method creates public methods by default.

Basic Syntax

The most common form uses a symbol for the method name and a do/end block for the body:

define_method(:method_name) do |arg1, arg2|
  # method body
end

Both do/end and curly brace { } blocks work identically with define_method. Pick whichever reads better in context. Single-line bodies often look cleaner with braces, while longer bodies suit do/end.

# do/end style
define_method(:greet) do |name|
  "Hello, #{name}!"
end

# curly brace style
define_method(:greet) { |name| "Hello, #{name}!" }

Both do/end and curly brace {} blocks work identically with define_method. Single-line bodies often read better with braces, while multi-line bodies suit do/end. The choice is stylistic — Ruby treats both the same way.

You can pass a Proc, lambda, or Method object as the second argument instead of a block:

doubler = Proc.new { |n| n * 2 }
define_method(:double, doubler)

tripler = ->(n) { n * 3 }
define_method(:triple, tripler)

def helper(n); n * 4; end
define_method(:quadruple, method(:helper))

Passing a Proc, lambda, or Method object to define_method is less common than using a block, but it is useful when the method body already exists as a callable object. This pattern appears in delegation and composition scenarios where you want to reuse an existing method or closure as a new named method on a class.

Method Visibility

Methods created with define_method are public by default, just like methods defined with def. You control visibility the same way — with private, protected, or public:

class Vault
  define_method(:secret) { "hidden value" }
  private :secret
end

Vault.new.secret  # => NoMethodError (private method called)

One gotcha: wrapping define_method inside a private block does not make the resulting method private. Visibility must be set explicitly after the call.

class Widget
  private
  define_method(:internal) { "not automatically private" }
end

# internal is still public — you must do:
class Widget
  define_method(:internal) { "still public" }
  private :internal
end

To define a private class method via define_method, use the eigenclass:

class Widget
  class << self
    define_method(:factory) { new }
  end
end

Widget.factory  # => #<Widget:...>

Using define_method Inside initialize

A powerful pattern is calling define_method from within initialize. Because define_method lives on Module, it creates instance methods on the object being initialized. This gives each instance its own custom behaviour — a foundation for builder patterns and internal DSLs.

class Robot
  def initialize(&block)
    instance_eval(&block) if block_given?
  end

  def arm(type:)
    define_method(:arm) { type }
  end

  def leg(type:)
    define_method(:leg) { type }
  end
end

robot = Robot.new do
  arm type: :claw
  leg type: :walker
end

robot.arm  # => :claw
robot.leg  # => :walker

Each define_method call inside initialize creates a method on that specific instance. Another instance of Robot can have a completely different set of methods. This is what makes the pattern so useful for DSLs.

This per-instance behavior is exactly what you want when building internal DSLs — each object gets its own tailored interface built from the block it receives. The instance_eval inside initialize changes self to the new object so the block’s method calls resolve against it.

instance_eval vs class_eval

Both instance_eval and class_eval evaluate a block with a modified self, but they serve different purposes when combined with define_method.

class_eval (also known as module_eval) sets self to the class itself. This means define_method inside a class_eval block creates instance methods — exactly what you want.

MyClass.class_eval do
  define_method(:instance_method) { "defined on instances" }
end

MyClass.new.instance_method  # => "defined on instances"

instance_eval sets self to a specific instance. Code run in this context can access and set that instance’s instance variables directly:

obj = MyClass.new
obj.instance_eval do
  @secret = "hidden"
  define_method(:reveal) { @secret }
end

obj.reveal  # => "hidden"

For standard metaprogramming where you want to add methods to a class, class_eval is the right tool. Use instance_eval when you need to work with a particular object’s internal state.

A common point of confusion is that class_eval and instance_eval both change self, but they do so in different contexts. class_eval sets self to the class, so define_method creates instance methods. instance_eval sets self to a specific object, so code runs in that object’s scope and can access its private state.

Splat and keyword arguments

define_method handles *args and **kwargs the same way def does:

class Logger
  define_method(:log) do |*messages|
    messages.each { |m| puts "[LOG] #{m}" }
  end
end

logger = Logger.new
logger.log("Server started", "Port 3000", "PID 1234")
# => [LOG] Server started
# => [LOG] Port 3000
# => [LOG] PID 1234

The splat operator * collects positional arguments into an array, which is useful when the number of arguments varies at call time. You can combine it with define_method to create flexible interfaces where the method signature adapts to whatever arguments the caller provides.

class Request
  define_method(:build) do |**params|
    params[:method] ||= :get
    params
  end
end

Request.new.build(endpoint: "/users")
# => {:endpoint=>"/users", :method=>:get}

Double-splat ** gathers keyword arguments into a hash, letting you pass named parameters without declaring each one in the method signature. This pattern is especially common in DSLs and configuration builders where the exact set of options is not known until runtime.

You can combine both:

class Flexible
  define_method(:call) do |*args, **kwargs|
    "args: #{args}, kwargs: #{kwargs}"
  end
end

Flexible.new.call(1, 2, a: "alpha", b: "beta")
# => "args: [1, 2], kwargs: {:a=>\"alpha\", :b=>\"beta\"}"

Combining *args and **kwargs gives you a method that accepts any combination of positional and keyword arguments. Use this when the method is meant to be a generic dispatcher, but be mindful that it can hide the expected interface from callers and from tools that rely on introspecting method signatures.

When to use define_method vs def

def is the right choice for most methods. It’s slightly faster, has no closure overhead, and the syntax is clean and familiar.

define_method earns its place when you need something def cannot do:

  • Dynamic method names - generate methods from data at runtime
  • Closure capture - reference local variables from the surrounding scope
  • Code generation - build many similar methods from a template

def cannot capture a local variable from the surrounding scope. define_method can, because the block keeps access to the variables that were in scope when the method was created.

prefix = "Hello"

klass = Class.new do
  define_method(:greet) do |name|
    "#{prefix}, #{name}!"
  end
end

klass.new.greet("World")
# => "Hello, World!"

That difference matters when you want a method body to remember values that were computed earlier. You can build tiny factories, DSL helpers, or per-class behaviors without having to thread every value through a long parameter list.

Here’s another practical example where define_method shines, generating predicate methods from a list of statuses:

class Task
  STATUSES = [:pending, :active, :completed, :cancelled].freeze

  STATUSES.each do |status|
    define_method(:"#{status}?") { true }
  end
end

task = Task.new
task.pending?    # => true
task.active?     # => true
task.completed?  # => true

Defining these four methods with def would require writing each one out manually. With define_method, a single loop generates them all, which keeps the relationship between the source data and the generated API much easier to see.

Real-world patterns

Attribute accessors from data

You can build attribute accessors dynamically from a hash, similar to what ActiveRecord does with its attribute method:

class Model
  def initialize(attrs = {})
    attrs.each do |key, value|
      define_method(key) { value }
      define_method("#{key}=") { |v| instance_variable_set("@#{key}", v) }
    end
  end
end

user = Model.new(name: "Alice", email: "alice@example.com")
user.name         # => "Alice"
user.email        # => "alice@example.com"
user.name = "Bob"
user.name         # => "Bob"

This style is handy when a class is really just a wrapper around a set of attributes. You can keep the code short and still expose a clean API for reading and writing values.

Delegation

define_method simplifies delegation patterns by letting you generate forwarding methods in a loop:

class Printer
  def initialize(target)
    [:print, :puts, :printf].each do |m|
      define_method(m) { |*args| target.send(m, *args) }
    end
  end
end

printer = Printer.new(Kernel)
printer.puts "Delegated to Kernel"

The generated methods are easy to reason about because they all follow the same forwarding rule. That makes the pattern less magical than method_missing, and usually easier to debug when something goes wrong.

Lazy initialization

You can defer expensive computation until a method is first called, then cache the result:

class Config
  def initialize
    @settings = {}
  end

  def setting(name, &block)
    define_method(name) do
      @settings[name] ||= block.call
    end
  end
end

config = Config.new
config.setting(:db_url) { "postgres://localhost:5432" }
config.db_url  # => "postgres://localhost:5432"
config.db_url  # => "postgres://localhost:5432" (cached)

This pattern is useful when the value is expensive to compute but the method name is still predictable. You get lazy loading without having to build a separate lookup table or special cache object.

Summary

define_method gives you the power to create methods dynamically at runtime. It lives on Module, so every class and module has access to it. The method name can be a symbol, string, or even a computed expression. The block or Proc becomes the method body, and it captures the surrounding scope via closure, something def cannot do.

Use def for everyday methods. Reach for define_method when building DSLs, generating methods from data, or needing per-instance customisation. Visibility defaults to public and must be set explicitly. Inside initialize, it enables powerful builder patterns where each object gets its own tailored interface.

If you want to keep going, compare define_method with method_missing, class_eval, and instance_eval. Those tools solve different metaprogramming problems, and the best choice depends on whether the method names are known ahead of time or discovered at runtime.

See Also