Sitemap
Shamila

Shamila

Metaprogramming in Ruby on Rails

3 min readMay 26, 2026

--

Metaprogramming is the technique of writing code that can generate or modify other code at runtime.

Ruby has powerful built-in support for metaprogramming. This allows developers to:

  • Define methods dynamically
  • Modify existing classes
  • Change object behavior at runtime

Ruby on Rails heavily uses metaprogramming internally. Features like dynamic finders, associations, validations, and scopes are all powered by it.

Some commonly used metaprogramming techniques in Ruby are:

  • define_method
  • method_missing
  • send
  • class_eval
  • instance_eval

define_method

As the name suggests, define_method is used to define methods dynamically at runtime.

Without Metaprogramming:

class Order
def pending?
status == "pending"
end
def processing?
status == "processing"
end
def shipped?
status == "shipped"
end
end

This works, but becomes repetitive when there are many statuses.

With define_method:

class Order
STATUSES = %w[pending processing shipped delivered cancelled]
attr_accessor :status
STATUSES.each do |status|
define_method("#{status}?") do
self.status == status
end
end
end
order = Order.new
order.status = "pending"
order.pending? # => true
order.shipped? # => false

Here, methods are generated dynamically for each status, making the code cleaner and easier to maintain.

method_missing

method_missing is called automatically when Ruby cannot find a method. It allows us to handle undefined methods dynamically.

class DynamicFinder
def method_missing(method_name, *args)
if method_name.to_s.start_with?("find_by_")
attribute = method_name.to_s.delete_prefix("find_by_")
"Finding by #{attribute}: #{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
finder = DynamicFinder.new
finder.find_by_email("sha@test.com")
# => "Finding by email: sha@test.com"
finder.find_by_name("Shamila")
# => "Finding by name: Shamila"

Important Note:

Whenever you override method_missing, you should also override respond_to_missing?.

Get Shamila’s stories in your inbox

Join Medium for free to get updates from this writer.

Otherwise, methods like respond_to? will not work correctly

send

send calls a method dynamically using its name as a symbol or string. It is useful when the method name is determined at runtime.

class User
attr_accessor :email, :name
end

user = User.new

def update_attribute(user, name, value)
user.send("#{name}=", value)
end

update_attribute(user, :email, "new@test.com")

Important Note:

send can call private and protected methods as well.

Because of this, public_send is generally safer when you don't need access to private methods.

class_eval

class_eval Modifies a class at run time

Example:

class User
end

User.class_eval do
def admin?
true
end
end

user1 = User.new
user2 = User.new

user1.admin? # => true
user2.admin? # => true

Here, admin? gets added dynamically to the User class, so all instances of User can use it.

instance_eval

instance_eval evaluates code in the context of a specific object instead of a class.

It is often used to define singleton methods or modify a single object dynamically.

Example:

# Assume User is an ActiveRecord model
user = User.new
user.instance_eval do
def admin?
true
end
end
user.admin? # => true

Only this particular user object will have the admin? method.

Other instances of User will not.

Key Point:

  • class_eval modifies a class, affects all instances
  • instance_eval modifies a specific object, affects only that object

Conclusion

Metaprogramming is one of Ruby’s most powerful features and is heavily used inside Ruby on Rails.

It helps developers write flexible, reusable, and expressive code.

However, keep these trade-offs in mind:

  • method_missing is slower than defined methods because Ruby walks the full method lookup chain before reaching it
  • Excessive metaprogramming can make applications harder to debug and understand
  • Always override respond_to_missing? alongside method_missing
  • Prefer public_send over send when you don't need access to private methods

The key is to use metaprogramming wisely while keeping the code readable and maintainable.

--

--