define_method in Ruby
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