define_method in Ruby

· 6 min read · Updated March 26, 2026 · intermediate
ruby metaprogramming methods ruby-fundamentals

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.

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}!" }

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))

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.

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.

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
class Request
  define_method(:build) do |**params|
    params[:method] ||= :get
    params
  end
end

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

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\"}"

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

ref cannot do this — greet is fixed at parse time prefix = “Hello” def greet(name) ”#{prefix}, #{name}!” end

=> NameError (prefix is not in scope for def)

define_method captures prefix via closure

define_method(:greet) do |name| ”#{prefix}, #{name}!” end greet(“World”) # => “Hello, World!”


Here's a 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.

## 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”


### 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”


### 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)


## 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/lambda) 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.

## See Also

- [Ruby Metaprogramming Basics](/guides/ruby-metaprogramming-basics/) — take your metaprogramming skills further
- [Ruby Classes and Objects](/tutorials/ruby-classes-and-objects/) — where methods live in Ruby