Here we will look into the detailed explanation of some Ruby concepts with practical examples, and real-world scenarios:
1. Handling Many Constants in a Ruby Class
Problem:
A class with numerous constants becomes cluttered and harder to maintain.
Solutions & Examples:
- Nested Module for Grouping:
class HTTPClient
module StatusCodes
OK = 200
NOT_FOUND = 404
SERVER_ERROR = 500
end
def handle_response(code)
case code
when StatusCodes::OK then "Success"
when StatusCodes::NOT_FOUND then "Page missing"
end
end
end
Why: Encapsulating constants in a module improves readability and avoids namespace collisions.
- Dynamic Constants with
const_set:
class DaysOfWeek
%w[MON TUE WED THU FRI SAT SUN].each_with_index do |day, index|
const_set(day, index + 1)
end
end
puts DaysOfWeek::MON # => 1
Use Case: Generate constants programmatically (e.g., days, months).
- External Configuration (YAML):
# config/constants.yml
error_codes:
NOT_FOUND: 404
SERVER_ERROR: 500
class App
CONSTANTS = YAML.load_file('config/constants.yml')
def self.error_message(code)
CONSTANTS['error_codes'].key(code)
end
end
Why: Centralize configuration for easy updates.
2. Meta-Programming: Dynamic Methods & Classes
Examples:
define_method for Repetitive Methods:
class User
ATTRIBUTES = %w[name email age]
ATTRIBUTES.each do |attr|
define_method(attr) { instance_variable_get("@#{attr}") }
define_method("#{attr}=") { |value| instance_variable_set("@#{attr}", value) }
end
end
user = User.new
user.name = "Alice"
puts user.name # => "Alice"
Use Case: Auto-generate getters/setters for multiple attributes.
- Dynamic Classes with
Class.new:
Animal = Class.new do
def speak
puts "Animal noise!"
end
end
dog = Animal.new
dog.speak # => "Animal noise!"
Use Case: Generate classes at runtime (e.g., for plugins).
class_eval for Modifying Existing Classes:
String.class_eval do
def shout
upcase + "!"
end
end
puts "hello".shout # => "HELLO!"
Why: Add/redefine methods in existing classes dynamically.
3. Why Classes Are Objects in Ruby
Explanation:
- Every class is an instance of
Class.
- Classes inherit from
Module and ultimately Object, allowing them to have methods and variables:
class Dog
@count = 0 # Class instance variable
def self.increment_count
@count += 1
end
end
- Real-World Impact: You can pass classes as arguments, modify them at runtime, and use them like any other object.
4. super Keyword: Detailed Usage
Examples:
- Implicit Argument Passing:
class Vehicle
def start_engine
"Engine on"
end
end
class Car < Vehicle
def start_engine
super + " (Vroom!)"
end
end
puts Car.new.start_engine # => "Engine on (Vroom!)"
- Explicit
super() for No Arguments:
class Parent
def greet
"Hello"
end
end
class Child < Parent
def greet
super() + " World!" # Explicitly call Parent#greet with no args
end
end
Pitfall: Forgetting () when overriding a method with parameters.
5. Blocks in Ruby Methods: Scenarios
A simple ruby method that accepts a block and executing via yield:
irb* def abhi_block
irb* yield
irb* yield
irb> end
=> :abhi_block
irb* abhi_block do. # multi-line block
irb* puts "*"*7
irb> end
*******
*******
irb> abhi_block { puts "*"*7 }. # single-line block
*******
*******
=> nil
irb* def abhi_block
irb* yield 3
irb* yield 7
irb* yield 9
irb> end
=> :abhi_block
irb> abhi_block { |x| puts x }. # pass argument to block
3
7
9
=> nil
Note: We can call yield any number times that we want.
Proc
Procs are similar to blocks, however, they differ in that they may be saved to a variable to be used again and again. In Ruby, aย procย can be called directly using theย .callย method.
To create Proc, we callย newย on theย Procย class and follow it with the block of code
my_proc = Proc.new { |x| x*x*9 }
=> #<Proc:0x000000011f64ed38 (irb):34>
my_proc.call(6)
=> 324
> my_proc.call # try to call without an argument
(irb):34:in 'block in <top (required)>': undefined method '*' for nil (NoMethodError)
lambda
> my_lambda = lambda { |x| x/3/5 }
=> #<Proc:0x000000011fe6fd28 (irb):44 (lambda)>
> my_lambda.call(233)
=> 15
> my_lambda = lambda.new { |x| x/3/5 } # wrong
in 'Kernel#lambda': tried to create Proc object without a block (ArgumentError)
> my_lambda = lambda # wrong
(irb):45:in 'Kernel#lambda': tried to create Proc object without a block (ArgumentError)
> my_lambda.call # try to call without an argument
(irb):46:in 'block in <top (required)>': wrong number of arguments (given 0, expected 1) (ArgumentError)
Difference 1: lambda gets an ArgumentError if we call without an argument and Proc doesn’t.
Difference 2: lambda returns to its calling method rather than returning itself like Proc from its parent method.
irb* def proc_method
irb* my_proc = Proc.new { return "Proc returns" }
irb* my_proc.call
irb* "Retun by proc_method" # neaver reaches here
irb> end
=> :proc_method
irb> p proc_method
"Proc returns"
=> "Proc returns"
irb* def lambda_method
irb* my_lambda = lambda { return 'Lambda returns' }
irb* my_lambda.call
irb* "Method returns"
irb> end
=> :lambda_method
irb(main):079> p lambda_method
"Method returns"
=> "Method returns"
Use Cases & Examples:
- Resource Management (File Handling):
def open_file(path)
file = File.open(path, 'w')
yield(file) if block_given?
ensure
file.close
end
open_file('log.txt') { |f| f.write("Data") }
Why: Ensures the file is closed even if an error occurs.
- Custom Iterators:
class MyArray
def initialize(items)
@items = items
end
def custom_each
@items.each { |item| yield(item) }
end
end
MyArray.new([1,2,3]).custom_each { |n| puts n * 2 }
- Timing Execution:
def benchmark
start = Time.now
yield
puts "Time taken: #{Time.now - start}s"
end
benchmark { sleep(2) } # => "Time taken: 2.0s"
Procs And Lambdas in Ruby
proc = Proc.new { puts "I am the proc block" }
lambda = lambda { puts "I am the lambda block"}
proc_test.call # => I am the proc block
lambda_test.call # => I am the lambda block
6. Enums in Ruby
Approaches:
- Symbols/Constants:
class TrafficLight
STATES = %i[red yellow green].freeze
def initialize
@state = STATES.first
end
def next_state
@state = STATES[(STATES.index(@state) + 1) % STATES.size]
end
end
- Rails ActiveRecord Enum:
class User < ActiveRecord::Base
enum role: { admin: 0, user: 1, guest: 2 }
end
user = User.new(role: :admin)
user.admin? # => true
Why: Generates helper methods like admin? and user.admin!.
7. Including Enumerable
Why Needed:
Enumerable methods (map, select, etc.) rely on each being defined.
- Example Without Enumerable:
class MyCollection
def initialize(items)
@items = items
end
def each(&block)
@items.each(&block)
end
end
# Without Enumerable:
collection = MyCollection.new([1,2,3])
collection.map { |n| n * 2 } # Error: Undefined method `map`
class MyCollection
include Enumerable
# ... same as above
end
collection.map { |n| n * 2 } # => [2,4,6]
8. Class Variables (@@)
Example & Risks:
class Parent
@@count = 0
def self.count
@@count
end
def increment
@@count += 1
end
end
class Child < Parent; end
Parent.new.increment
Child.new.increment
puts Parent.count # => 2 (Shared across Parent and Child)
Why Avoid: Subclasses unintentionally modify the same variable.
Alternative (Class Instance Variables):
class Parent
@count = 0
def self.count
@count
end
def self.increment
@count += 1
end
end
9. Global Variables ($)
Example & Issues:
$logger = Logger.new($stdout)
def log_error(message)
$logger.error(message) # Accessible everywhere
end
# Problem: Tight coupling; changing $logger affects all code.
When to Use: Rarely, for truly global resources like $stdout or $LOAD_PATH.
Alternative: Dependency injection or singleton classes.
class AppConfig
attr_reader :logger
def initialize(logger:)
@logger = logger
end
def info(msg)
@logger.info(msg)
end
end
config = AppConfig.new(Logger.new($stdout))
info = config.info("Safe")
Summary:
- Constants: Organize with modules or external files.
- Meta-Programming: Use
define_method/Class.new for dynamic code.
- Classes as Objects: Enable OOP flexibility.
super: Call parent methods with/without arguments.
- Blocks: Abstract setup/teardown or custom logic.
- Enums: Simulate via symbols or Rails helpers.
- Enumerable: Include it and define
each.
- Class/Global Variables: Rarely used due to side effects.
Enjoy Ruby! ๐