Understanding Message ✉️ Passing in Ruby

Ruby is often celebrated for its expressive and dynamic nature, and one of its most fascinating yet under-appreciated feature is message passing. Unlike many other languages that focus purely on method calls, Ruby inherits the concept of sending messages from Smalltalk and embraces it throughout the language.

Let’s explore what message passing is, why Ruby uses it, and how you can leverage it effectively in your code.


What is Message Passing in Ruby?

In Ruby, every method call is actually a message being sent to an object. When you invoke a method on an object, Ruby sends a message to that object, asking it to execute a particular behavior.

Example:

str = "Hello, Ruby!"
puts str.reverse   # Standard method call

Behind the scenes, Ruby treats this as sending the :reverse message to the str object:

puts str.send(:reverse) # Equivalent to str.reverse

This concept extends beyond just methods—it applies to variable access and operators too! Even str.length is just Ruby sending the :length message to str.


Why Does Ruby Use Message Passing?

Message passing provides several advantages:

  1. Encapsulation and Flexibility
    • Instead of directly accessing methods, sending messages allows objects to decide how they respond to method calls.This abstraction makes code more modular and adaptable, allowing behavior to change at runtime without modifying existing method definitions.
class DynamicResponder
  def method_missing(name, *args)
    "I received a message: #{name}, but I don't have a method for it."
  end
end

obj = DynamicResponder.new
puts obj.some_method  # => "I received a message: some_method, but I don't have a method for it."

2. Dynamic Method Invocation

  • You can invoke methods dynamically at runtime using symbols instead of hardcoded method names.
method_name = :upcase
puts "ruby".send(method_name) # => "RUBY"

3. Metaprogramming Capabilities

  • Ruby allows defining methods dynamically using define_method, making it easier to create flexible APIs.
class Person
  [:first_name, :last_name].each do |method|
    define_method(method) do |value|
      instance_variable_set("@#{method}", value)
    end
  end
end

p = Person.new
p.first_name("John")
p.last_name("Doe")

Using send to Pass Messages Dynamically

The send method allows you to invoke methods dynamically using symbols or strings representing method names.

Example 1: Calling Methods Dynamically

str = "ruby messages"
puts str.send(:reverse)  # => "segassem ybur"
puts str.send(:[], 4..9) # => " messa"

Example 2: Checking Method Availability with respond_to?

puts str.respond_to?(:reverse) # => true
puts str.respond_to?(:last)    # => false

This ensures safe message passing by verifying that an object responds to a method before calling it.

Example 3: Avoiding Private Method Calls with public_send

class Secret
  private
  def hidden_message
    "This is private!"
  end
end

secret = Secret.new
puts secret.send(:hidden_message)      # Works! (bypasses visibility)
puts secret.public_send(:hidden_message) # Error! (Respects visibility)

Use public_send to ensure that only public methods are invoked dynamically.


Defining Methods Dynamically with define_method

Ruby also allows defining methods dynamically at runtime using define_method:

Example 1: Creating Methods on the Fly

class Person
  [:first_name, :last_name].each do |method|
    define_method(method) do |value|
      instance_variable_set("@#{method}", value)
    end
  end
end

p = Person.new
p.first_name("John")
p.last_name("Doe")

This creates first_name and last_name methods dynamically.

Example 2: Generating Getters and Setters

class Dynamic
  attr_accessor :data
end

obj = Dynamic.new
obj.data = "Dynamic Method"
puts obj.data # => "Dynamic Method"


Handling Undefined Methods with method_missing

If an object receives a message (method call) that it does not understand, Ruby provides a safety net: method_missing.

Example: Catching Undefined Method Calls

class Robot
  def method_missing(name, *args)
    "I don’t know how to #{name}!"
  end
end

r = Robot.new
puts r.dance # => "I don’t know how to dance!"
puts r.fly   # => "I don’t know how to fly!"

This feature is commonly used in DSLs (Domain Specific Languages) to handle flexible method invocations.


How Ruby Differs from Other Languages

Many languages support method calls, but few treat method calls as message passing the way Ruby does. For example:

  • Python: Uses getattr(obj, method_name) for dynamic calls, but lacks an equivalent to method_missing.
  • JavaScript: Uses obj[method_name]() for indirect invocation but doesn’t treat calls as messages.
  • Java: Requires reflection (Method.invoke), making it less fluid than Ruby.
  • Smalltalk: The original message-passing language, which inspired Ruby’s approach.

When to Use Message Passing Effectively

Use send for dynamic method invocation:

  • Useful for reducing boilerplate in frameworks like Rails.
  • Ideal when working with DSLs or metaprogramming scenarios.

Use respond_to? before send to avoid errors:

  • Ensures that the object actually has the method before attempting to call it.

Use define_method for dynamic method creation:

  • Helps in cases where multiple similar methods are needed.

Use method_missing cautiously:

  • Can be powerful, but should be handled carefully to avoid debugging nightmares.

Final Thoughts

Ruby’s message passing mechanism is one of the reasons it excels in metaprogramming and dynamic method invocation. Understanding this feature allows developers to write more flexible and expressive code while keeping it clean and readable. The best example using this features you can see here: https://github.com/rails/rails . None other than Rails Framework itself!!

By embracing message passing, you can unlock new levels of code abstraction, modularity, and intelligent method handling in your Ruby projects.

Enjoy Ruby 🚀

Understanding 💭 include, extend, and prepend in Ruby

Ruby is well known for its flexibility and expressive syntax. One of its powerful features is the ability to share functionality using modules. While many languages offer mixins, Ruby stands out with three key methods: include, extend, and prepend. Understanding these methods can significantly improve code reuse and clarity. Let’s explore each with examples!

include → Adds Module Methods as Instance Methods

When you use include in a class, it injects the module’s methods as instance methods of the class.

Example: Using include for instance methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  include Greet
end

user = User.new
puts user.hello # => "Hello from Greet module!"

Scope: The methods from Greet become instance methods in User.


extend → Adds Module Methods as Class Methods

When you use extend in a class, it injects the module’s methods as class methods.

Example: Using extend for class methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  extend Greet
end

puts User.hello # => "Hello from Greet module!"

Scope: The methods from Greet become class methods in User, instead of instance methods.


prepend → Adds Module Methods Before Instance Methods

prepend works like include, but the methods from the module take precedence over the class’s own methods.

Example: Using prepend to override instance methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  prepend Greet
  
  def hello
    "Hello from User class!"
  end
end

user = User.new
puts user.hello # => "Hello from Greet module!"

Scope: The methods from Greet override User‘s methods because prepend places Greet before the class in the method lookup chain.


Method Lookup Order

Understanding how Ruby resolves method calls is essential when using these techniques.

The method lookup order is as follows:

  1. Prepend modules (highest priority)
  2. Instance methods in the class itself
  3. Include modules
  4. Superclass methods
  5. Object, Kernel, BasicObject

To visualize this, use the ancestors method:

class User
  prepend Greet
end

puts User.ancestors
# => [Greet, User, Object, Kernel, BasicObject]


How Ruby Differs from Other Languages

  • Ruby is unique in dynamically modifying method resolution order (MRO) using prepend, include, and extend.
  • In Python, multiple inheritance is used with super(), but prepend-like behavior does not exist.
  • JavaScript relies on prototypes instead of module mixins, making Ruby’s approach cleaner and more structured.

Quick Summary

KeywordInjects Methods AsWorks OnTakes Precedence?
includeInstance methodsInstancesNo
extendClass methodsClassesN/A
prependInstance methodsInstancesYes (overrides class methods)

By understanding and using include, extend, and prepend, you can make your Ruby code more modular, reusable, and expressive. Master these techniques to level up your Ruby skills!

Enjoy Ruby 🚀

Exploring the Interesting Features of Ruby 💎 Programming Language

Ruby is an elegant and expressive language that stands out due to its simplicity and readability. While its syntax is designed to be natural and easy to use, Ruby also has several powerful and unique features that make it a joy to work with. Let’s explore some of the fascinating aspects of Ruby that set it apart from other programming languages.

Everything is an Expression

Unlike many other languages where statements exist separately from expressions, in Ruby, everything is an expression. Every line of code evaluates to something, even function definitions. For example:

result = def greet
  "Hello, World!"
end

puts result # => :greet

Here, defining the greet method itself returns a symbol representing its name, :greet. This feature makes metaprogramming and introspection very powerful in Ruby.

Other examples:

  1. Conditional expressions return values:
value = if 10 > 5
  "Greater"
else
  "Smaller"
end

puts value # => "Greater"

  1. Loops also evaluate to a return value:
result = while false
  "This won't run"
end

puts result # => nil

result = until true
  "This won't run either"
end

puts result # => nil

  1. Assignment is an expression:
x = y = 10 + 5
puts x # => 15
puts y # => 15

  1. Case expressions return values:
day = "Sunday"
message = case day
when "Monday"
  "Start of the week"
when "Friday"
  "Almost weekend!"
when "Sunday"
  "Time to relax!"
else
  "Just another day"
end

puts message # => "Time to relax!"

This characteristic of Ruby allows concise and elegant code that minimizes the need for temporary variables and makes code more readable.

The send Method for Dynamic Method Invocation

While Ruby does not allow storing functions in variables (like JavaScript or Python), you can hold their identifiers as symbols and invoke them dynamically using send:

def hello
  "Hello!"
end

method_name = :hello
puts send(method_name) # => "Hello!"

Why does Ruby use .send instead of .call? The call method is used for Proc and lambda objects, whereas send is a general method that allows invoking methods dynamically on an object. This also enables calling private methods.

So the question is can we restrict the programmer calling private methods with send? Let’s look at some examples how we can do that.

1. Use public_send Instead of send

The public_send method only allows calling public methods, preventing invocation of private methods.

class Demo
  private

  def secret_method
    "This is private!"
  end
end

d = Demo.new
puts d.public_send(:secret_method) # Raises NoMethodError

2. Override send in Your Class

You can override send in your class to restrict private method access.

class SecureDemo
  def send(method, *args)
    if private_methods.include?(method)
      raise NoMethodError, "Attempt to call private method `#{method}`"
    else
      super
    end
  end

  private

  def secret_method
    "This is private!"
  end
end

d = SecureDemo.new
puts d.send(:secret_method) # Raises NoMethodError
Use private_method_defined? to Manually Check for Access Level

Before calling a method via send, verify if it is public.

class SecureDemo
  def send(method, *args)
    if self.class.private_method_defined?(method)
      raise NoMethodError, "private method `#{method}` called"
    else
      super
    end
  end

  private

  def secret_method
    "This is private!"
  end
end

d = SecureDemo.new
puts d.send(:secret_method) # Raises NoMethodError

Why private_method_defined? Instead of method_defined??
  • method_defined? checks for public and protected methods but ignores private ones.
  • private_method_defined? explicitly checks for private methods, which is what we need here.

This ensures only public methods are called dynamically.

puts, p, and print – What’s the Difference?

Ruby provides multiple ways to output text:

  • puts calls .to_s on its argument and prints it, followed by a newline, but always returns nil.
  • p prints the argument using .inspect, preserving its original representation and also returns the argument.
  • print outputs text without adding a newline.

Example:

puts "Hello" # Output: Hello (returns nil)
p "Hello"   # Output: "Hello" (returns "Hello")
print "Hello" # Output: Hello (returns nil)

Variable Assignment and Storage Behavior

Ruby variables work with references, not direct values. Consider the following:

name = "Alice"
a = name
name = ["A", "l", "i", "c", "e"]

puts name # => ["A", "l", "i", "c", "e"]
puts a    # => "Alice"

Here, a still points to the original string, whereas name is reassigned to an array.

The next Keyword

Ruby’s next keyword is used to skip to the next iteration in a loop:

(1..5).each do |i|
  next if i.even?
  puts i
end

This prints:

1
3
5

Method Naming Conventions

Ruby allows method names with punctuation like ?, !, and =:

def valid?
  true
end

def modify!
  @value = 42
end

def name=(new_name)
  @name = new_name
end

  • ? indicates a method that returns a boolean.
  • ! signals a method that modifies the object in place.
  • = denotes an assignment-like method.

The Power of Symbols

Symbols in Ruby are immutable, memory-efficient string-like objects commonly used as keys in hashes:

user = { name: "John", age: 30 }
puts user[:name] # => "John"

Symbols don’t get duplicated in memory, making them faster than strings for certain use cases.

Converting Strings to Symbols with to_sym

string_key = "username"
hash = { string_key.to_sym => "johndoe" }
puts hash[:username] # => "johndoe"

Delegation in Ruby with Forwardable

Ruby provides the Forwardable module to simplify delegation by forwarding method calls to another object:

require 'forwardable'

class User
  extend Forwardable
  attr_reader :profile

  def_delegators :@profile, :email, :age

  def initialize(email, age)
    @profile = Profile.new(email, age)
  end
end

class Profile
  attr_reader :email, :age

  def initialize(email, age)
    @email = email
    @age = age
  end
end

user = User.new("john@example.com", 30)
puts user.email # => "john@example.com"
puts user.age   # => 30

This approach avoids unnecessary method redefinitions and keeps code clean.

You can read more about Ruby’s Forwardable module here:
https://ruby-doc.org/stdlib-2.5.1/libdoc/forwardable/rdoc/Forwardable.html

Ruby’s include, extend and prepend

This post is getting bigger. I have added it in a separate post.

Check the article: https://railsdrop.com/2025/03/05/understanding-include-extend-and-prepend-in-ruby/

Writing Clean Ruby Code

Using a linter and following a style guide ensures consistency and readability. Tools like RuboCop help enforce best practices.


These features showcase Ruby’s power and expressiveness. With its readable syntax, metaprogramming capabilities, and intuitive design, Ruby remains a top choice for developers who value simplicity and elegance in their code.

Enjoy Ruby 🚀