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:
- Conditional expressions return values:
value = if 10 > 5
"Greater"
else
"Smaller"
end
puts value # => "Greater"
- 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
- Assignment is an expression:
x = y = 10 + 5
puts x # => 15
puts y # => 15
- 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:
putscalls.to_son its argument and prints it, followed by a newline, but always returnsnil.pprints the argument using.inspect, preserving its original representation and also returns the argument.printoutputs 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 ๐