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 ๐Ÿš€