Master the safe navigation operator (&.) for nil-safe method calls, preventing NoMethodError exceptions and writing more robust Ruby code.
The safe navigation operator &., introduced in Ruby 2.3, allows you to call methods on objects
that might be nil without raising a NoMethodError. If the receiver is nil,
the entire expression returns nil instead of throwing an exception.
class SafeNavigationBasics
def self.demonstrate_basic_usage
puts "=== Basic Safe Navigation Examples ==="
# Traditional approach with nil checks
def self.traditional_approach(user)
if user && user.profile && user.profile.address
user.profile.address.street
else
nil
end
end
# Safe navigation approach
def self.safe_navigation_approach(user)
user&.profile&.address&.street
end
# Test with different user objects
class User
attr_accessor :profile
def initialize(profile = nil)
@profile = profile
end
end
class Profile
attr_accessor :address
def initialize(address = nil)
@address = address
end
end
class Address
attr_accessor :street, :city, :zip
def initialize(street, city, zip)
@street = street
@city = city
@zip = zip
end
end
# Test cases
full_user = User.new(Profile.new(Address.new("123 Main St", "Anytown", "12345")))
partial_user = User.new(Profile.new(nil))
nil_user = nil
test_cases = [
["Full user", full_user],
["Partial user", partial_user],
["Nil user", nil_user]
]
test_cases.each do |description, user|
puts "\n#{description}:"
puts " Traditional: #{traditional_approach(user).inspect}"
puts " Safe nav: #{safe_navigation_approach(user).inspect}"
end
end
def self.demonstrate_method_call_safety
puts "\n=== Method Call Safety ==="
# Various objects that might be nil
string_value = "Hello World"
nil_value = nil
array_value = [1, 2, 3, 4, 5]
empty_array = []
test_objects = [
["String", string_value],
["Nil", nil_value],
["Array", array_value],
["Empty Array", empty_array]
]
test_objects.each do |description, obj|
puts "\n#{description}:"
# Safe method calls
puts " length: #{obj&.length}"
puts " upcase: #{obj&.upcase}" if obj.respond_to?(:upcase)
puts " first: #{obj&.first}" if obj.respond_to?(:first)
puts " empty?: #{obj&.empty?}" if obj.respond_to?(:empty?)
# Chained calls
if obj.respond_to?(:first)
puts " first.to_s: #{obj&.first&.to_s}"
end
end
end
def self.demonstrate_comparison_with_traditional
puts "\n=== Comparison with Traditional Approaches ==="
data = {
user: {
name: "Alice",
preferences: {
theme: "dark",
notifications: true
}
}
}
# Method 1: Multiple conditions
def self.method1_multiple_conditions(data)
if data && data[:user] && data[:user][:preferences] && data[:user][:preferences][:theme]
data[:user][:preferences][:theme]
else
"default"
end
end
# Method 2: Rescue approach
def self.method2_rescue(data)
data[:user][:preferences][:theme]
rescue
"default"
end
# Method 3: Try chains
def self.method3_try_chains(data)
# Note: try is not built-in Ruby, but common in Rails
# We'll simulate it
begin
data[:user][:preferences][:theme]
rescue NoMethodError, TypeError
"default"
end
end
# Method 4: Safe navigation
def self.method4_safe_navigation(data)
data&.dig(:user, :preferences, :theme) || "default"
end
test_data = [
["Complete data", data],
["Partial data", { user: { name: "Bob" } }],
["Nil data", nil]
]
test_data.each do |description, test_case|
puts "\n#{description}:"
puts " Method 1: #{method1_multiple_conditions(test_case)}"
puts " Method 2: #{method2_rescue(test_case)}"
puts " Method 3: #{method3_try_chains(test_case)}"
puts " Method 4: #{method4_safe_navigation(test_case)}"
end
end
end
SafeNavigationBasics.demonstrate_basic_usage
SafeNavigationBasics.demonstrate_method_call_safety
SafeNavigationBasics.demonstrate_comparison_with_traditional
class ArrayHashSafeNavigation
def self.demonstrate_array_access
puts "=== Array Safe Navigation ==="
arrays = [
[1, 2, 3, 4, 5],
[],
nil
]
arrays.each_with_index do |arr, i|
puts "\nArray #{i + 1}: #{arr.inspect}"
# Safe array access
puts " first: #{arr&.first}"
puts " last: #{arr&.last}"
puts " [2]: #{arr&.[](2)}" # Note: [] method call
puts " length: #{arr&.length}"
# Chained operations
puts " first&.to_s: #{arr&.first&.to_s}"
puts " first&.even?: #{arr&.first&.even?}" if arr&.first.respond_to?(:even?)
# Safe iteration
result = arr&.map { |x| x * 2 }
puts " mapped: #{result&.inspect}"
end
end
def self.demonstrate_hash_access
puts "\n=== Hash Safe Navigation ==="
hashes = [
{ name: "Alice", age: 30, address: { street: "123 Main St", city: "Anytown" } },
{ name: "Bob" },
{},
nil
]
hashes.each_with_index do |hash, i|
puts "\nHash #{i + 1}: #{hash.inspect}"
# Safe hash access
puts " name: #{hash&.[](:name)}"
puts " age: #{hash&.[](:age)}"
# Using fetch safely
puts " name with default: #{hash&.fetch(:name, 'Unknown')}"
# Nested access
puts " address street: #{hash&.[](:address)&.[](:street)}"
# Using dig (safer for nested access)
puts " dig address/city: #{hash&.dig(:address, :city)}"
# Key operations
puts " keys: #{hash&.keys}"
puts " empty?: #{hash&.empty?}"
end
end
def self.demonstrate_complex_structures
puts "\n=== Complex Nested Structures ==="
data = {
users: [
{
id: 1,
name: "Alice",
posts: [
{ title: "Hello World", comments: [{ body: "Great post!" }] },
{ title: "Ruby Tips" }
]
},
{
id: 2,
name: "Bob",
posts: []
}
]
}
# Access deeply nested data safely
puts "First user's first post's first comment:"
comment = data&.dig(:users, 0, :posts, 0, :comments, 0, :body)
puts " #{comment.inspect}"
puts "\nSecond user's first post title:"
title = data&.dig(:users, 1, :posts, 0, :title)
puts " #{title.inspect}"
puts "\nNon-existent user's data:"
nonexistent = data&.dig(:users, 5, :name)
puts " #{nonexistent.inspect}"
# Safe navigation with methods
puts "\nFirst user's post count:"
post_count = data&.dig(:users, 0, :posts)&.length
puts " #{post_count.inspect}"
puts "\nAll user names:"
names = data&.dig(:users)&.map { |user| user&.dig(:name) }&.compact
puts " #{names.inspect}"
end
def self.demonstrate_conditional_assignment
puts "\n=== Conditional Assignment with Safe Navigation ==="
class UserProfile
attr_accessor :email, :preferences
def initialize(email = nil)
@email = email
@preferences = {}
end
end
user = UserProfile.new("alice@example.com")
nil_user = nil
# Safe assignment patterns
puts "Setting preferences for valid user:"
user&.preferences&.[]=(:theme, "dark")
puts " User preferences: #{user&.preferences}"
puts "\nAttempting to set preferences for nil user:"
nil_user&.preferences&.[]=(:theme, "light")
puts " Nil user preferences: #{nil_user&.preferences}"
# ||= with safe navigation
puts "\nConditional assignment:"
user&.preferences&.[]=(:language, user&.preferences&.[](:language) || "en")
puts " Language: #{user&.preferences&.[](:language)}"
# Multiple safe assignments
config = {}
config&.[]=((:database, {})
config&.dig(:database)&.[]=((:host, "localhost")
puts "\nConfig: #{config}"
end
end
ArrayHashSafeNavigation.demonstrate_array_access
ArrayHashSafeNavigation.demonstrate_hash_access
ArrayHashSafeNavigation.demonstrate_complex_structures
ArrayHashSafeNavigation.demonstrate_conditional_assignment
class AdvancedSafeNavigation
def self.demonstrate_method_chaining
puts "=== Advanced Method Chaining ==="
class Company
attr_accessor :name, :employees
def initialize(name, employees = [])
@name = name
@employees = employees
end
def ceo
employees.find { |emp| emp.role == "CEO" }
end
def department(name)
employees.select { |emp| emp.department == name }
end
end
class Employee
attr_accessor :name, :role, :department, :contact
def initialize(name, role, department = nil, contact = nil)
@name = name
@role = role
@department = department
@contact = contact
end
end
class Contact
attr_accessor :email, :phone
def initialize(email, phone = nil)
@email = email
@phone = phone
end
end
# Create test data
ceo = Employee.new("Alice Smith", "CEO", "Executive", Contact.new("alice@company.com", "555-0101"))
engineer = Employee.new("Bob Johnson", "Engineer", "Engineering", Contact.new("bob@company.com"))
company = Company.new("TechCorp", [ceo, engineer])
empty_company = Company.new("EmptyCorp", [])
nil_company = nil
companies = [
["Full company", company],
["Empty company", empty_company],
["Nil company", nil_company]
]
companies.each do |description, comp|
puts "\n#{description}:"
# Safe navigation chains
ceo_email = comp&.ceo&.contact&.email
puts " CEO email: #{ceo_email.inspect}"
ceo_phone = comp&.ceo&.contact&.phone
puts " CEO phone: #{ceo_phone.inspect}"
# Method with parameters
eng_dept = comp&.department("Engineering")
puts " Engineering dept size: #{eng_dept&.length}"
# Combining with other operators
eng_count = comp&.department("Engineering")&.count || 0
puts " Engineering count (with default): #{eng_count}"
# Complex chaining
first_eng_email = comp&.department("Engineering")&.first&.contact&.email
puts " First engineer email: #{first_eng_email.inspect}"
end
end
def self.demonstrate_block_operations
puts "\n=== Safe Navigation with Blocks ==="
data_sets = [
[1, 2, 3, 4, 5],
[],
nil
]
data_sets.each_with_index do |data, i|
puts "\nDataset #{i + 1}: #{data.inspect}"
# Safe block operations
mapped = data&.map { |x| x * 2 }
puts " mapped: #{mapped.inspect}"
selected = data&.select { |x| x.even? }
puts " evens: #{selected.inspect}"
found = data&.find { |x| x > 3 }
puts " first > 3: #{found.inspect}"
# Chaining with blocks
result = data&.map { |x| x * 2 }&.select { |x| x > 5 }&.first
puts " chained result: #{result.inspect}"
# Safe reduce
sum = data&.reduce(0) { |acc, x| acc + x }
puts " sum: #{sum.inspect}"
end
end
def self.demonstrate_assignment_operators
puts "\n=== Safe Navigation with Assignment Operators ==="
class Settings
attr_accessor :theme, :language, :notifications
def initialize
@theme = nil
@language = nil
@notifications = {}
end
def notification_setting(type)
@notifications[type]
end
def set_notification(type, value)
@notifications[type] = value
end
end
settings = Settings.new
nil_settings = nil
test_cases = [
["Valid settings", settings],
["Nil settings", nil_settings]
]
test_cases.each do |description, setting_obj|
puts "\n#{description}:"
# ||= with safe navigation (conditional assignment)
original_theme = setting_obj&.theme
setting_obj&.theme ||= "default"
new_theme = setting_obj&.theme
puts " theme before: #{original_theme.inspect}"
puts " theme after ||=: #{new_theme.inspect}"
# += with safe navigation
setting_obj&.language = setting_obj&.language.to_s + "_modified" if setting_obj&.language
puts " language: #{setting_obj&.language.inspect}"
# Method calls with assignment
setting_obj&.set_notification(:email, true)
email_notif = setting_obj&.notification_setting(:email)
puts " email notifications: #{email_notif.inspect}"
end
end
def self.demonstrate_error_handling_patterns
puts "\n=== Error Handling Patterns ==="
class APIClient
def initialize(base_url)
@base_url = base_url
end
def fetch_user(id)
# Simulate API call that might return nil
return nil if id == 999 # Simulate not found
{
id: id,
name: "User #{id}",
profile: id.even? ? { bio: "Bio for user #{id}" } : nil
}
end
def fetch_users
[
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]
end
end
api = APIClient.new("https://api.example.com")
nil_api = nil
user_ids = [1, 2, 999] # 999 will return nil
user_ids.each do |id|
puts "\nFetching user #{id}:"
# Safe API calls
user = api&.fetch_user(id)
name = user&.[](:name)
bio = user&.dig(:profile, :bio)
puts " name: #{name.inspect}"
puts " bio: #{bio.inspect}"
# Fallback patterns
display_name = user&.[](:name) || "Unknown User"
puts " display name: #{display_name}"
# Safe method chaining with fallbacks
user_info = user&.then { |u| "#{u[:name]} (ID: #{u[:id]})" } || "User not found"
puts " user info: #{user_info}"
end
# Safe API client access
puts "\nNil API client:"
users = nil_api&.fetch_users
puts " users: #{users.inspect}"
user_count = nil_api&.fetch_users&.length || 0
puts " user count: #{user_count}"
end
end
AdvancedSafeNavigation.demonstrate_method_chaining
AdvancedSafeNavigation.demonstrate_block_operations
AdvancedSafeNavigation.demonstrate_assignment_operators
AdvancedSafeNavigation.demonstrate_error_handling_patterns
class WebApplicationExamples
def self.demonstrate_request_handling
puts "=== Request Parameter Handling ==="
# Simulate web request parameters
requests = [
{
params: {
user: {
name: "Alice",
email: "alice@example.com",
preferences: {
theme: "dark",
notifications: { email: true, sms: false }
}
}
}
},
{
params: {
user: {
name: "Bob"
# Missing email and preferences
}
}
},
{
params: {} # Empty params
},
{ params: nil } # Nil params
]
requests.each_with_index do |request, i|
puts "\nRequest #{i + 1}:"
# Safe parameter extraction
user_name = request&.dig(:params, :user, :name)
user_email = request&.dig(:params, :user, :email)
theme = request&.dig(:params, :user, :preferences, :theme)
email_notifications = request&.dig(:params, :user, :preferences, :notifications, :email)
puts " name: #{user_name.inspect}"
puts " email: #{user_email.inspect}"
puts " theme: #{theme.inspect}"
puts " email notifications: #{email_notifications.inspect}"
# Safe validation
has_required_fields = user_name&.length&.> 0 && user_email&.include?("@")
puts " has required fields: #{has_required_fields.inspect}"
# Default values with safe navigation
display_theme = theme || "light"
notification_pref = email_notifications.nil? ? true : email_notifications
puts " display theme: #{display_theme}"
puts " email notifications (with default): #{notification_pref}"
end
end
def self.demonstrate_api_response_processing
puts "\n=== API Response Processing ==="
# Simulate various API responses
api_responses = [
{
status: 200,
data: {
user: {
id: 1,
name: "Alice",
avatar: { url: "https://example.com/avatar1.jpg", size: "large" }
}
}
},
{
status: 200,
data: {
user: {
id: 2,
name: "Bob"
# Missing avatar
}
}
},
{
status: 404,
error: { message: "User not found" }
},
nil # Network error
]
api_responses.each_with_index do |response, i|
puts "\nAPI Response #{i + 1}:"
# Safe response processing
status = response&.[](:status)
user_id = response&.dig(:data, :user, :id)
user_name = response&.dig(:data, :user, :name)
avatar_url = response&.dig(:data, :user, :avatar, :url)
error_message = response&.dig(:error, :message)
puts " status: #{status.inspect}"
puts " user ID: #{user_id.inspect}"
puts " user name: #{user_name.inspect}"
puts " avatar URL: #{avatar_url.inspect}"
puts " error: #{error_message.inspect}"
# Response handling logic
if status == 200 && user_name
display_name = user_name&.strip&.empty? ? "Anonymous" : user_name
avatar_display = avatar_url || "/images/default-avatar.png"
puts " → Success: #{display_name} (avatar: #{avatar_display})"
elsif error_message
puts " → Error: #{error_message}"
else
puts " → Unknown response format"
end
end
end
def self.demonstrate_session_handling
puts "\n=== Session and Cookie Handling ==="
# Simulate different session states
sessions = [
{
user_id: 123,
preferences: { language: "en", timezone: "UTC" },
cart: { items: [{ id: 1, name: "Product A" }], total: 29.99 }
},
{
user_id: 456
# Missing preferences and cart
},
{} # Empty session
]
sessions.each_with_index do |session, i|
puts "\nSession #{i + 1}:"
# Safe session access
user_id = session&.[](:user_id)
language = session&.dig(:preferences, :language)
timezone = session&.dig(:preferences, :timezone)
cart_items = session&.dig(:cart, :items)
cart_total = session&.dig(:cart, :total)
puts " user ID: #{user_id.inspect}"
puts " language: #{language.inspect}"
puts " timezone: #{timezone.inspect}"
# Cart processing
item_count = cart_items&.length || 0
total_amount = cart_total || 0.0
puts " cart items: #{item_count}"
puts " cart total: $#{total_amount}"
# User state determination
is_logged_in = !user_id.nil?
has_preferences = !language.nil?
has_cart_items = item_count > 0
puts " logged in: #{is_logged_in}"
puts " has preferences: #{has_preferences}"
puts " has cart items: #{has_cart_items}"
# Safe operations on cart items
first_item_name = cart_items&.first&.[](:name)
puts " first item: #{first_item_name.inspect}"
end
end
def self.demonstrate_database_record_processing
puts "\n=== Database Record Processing ==="
# Simulate database records (some fields might be nil)
records = [
{
id: 1,
name: "Alice Smith",
email: "alice@example.com",
profile: {
bio: "Software developer",
location: "New York",
social: {
twitter: "@alice",
github: "alice_dev"
}
},
created_at: "2023-01-15T10:30:00Z"
},
{
id: 2,
name: "Bob Johnson",
email: "bob@example.com",
profile: {
bio: "Designer"
# Missing location and social
},
created_at: "2023-02-20T15:45:00Z"
},
{
id: 3,
name: "Carol Williams",
email: nil, # Email not provided
profile: nil, # No profile created
created_at: "2023-03-10T09:15:00Z"
}
]
records.each do |record|
puts "\nUser #{record[:id]}:"
# Safe field access
name = record&.[](:name)
email = record&.[](:email)
bio = record&.dig(:profile, :bio)
location = record&.dig(:profile, :location)
twitter = record&.dig(:profile, :social, :twitter)
github = record&.dig(:profile, :social, :github)
puts " name: #{name.inspect}"
puts " email: #{email.inspect}"
puts " bio: #{bio.inspect}"
puts " location: #{location.inspect}"
# Display logic with safe navigation
display_name = name || "Anonymous User"
contact_info = email || "No email provided"
profile_summary = bio&.length&.> 10 ? "#{bio[0..50]}..." : bio
puts " display name: #{display_name}"
puts " contact: #{contact_info}"
puts " profile: #{profile_summary.inspect}"
# Social media links
social_links = []
social_links << "Twitter: #{twitter}" if twitter
social_links << "GitHub: #{github}" if github
puts " social links: #{social_links.empty? ? 'None' : social_links.join(', ')}"
# Safe date processing
created_date = record&.[](:created_at)&.to_s&.[](0..9) # Extract date part
puts " member since: #{created_date.inspect}"
end
end
end
WebApplicationExamples.demonstrate_request_handling
WebApplicationExamples.demonstrate_api_response_processing
WebApplicationExamples.demonstrate_session_handling
WebApplicationExamples.demonstrate_database_record_processing
class ConfigurationProcessing
def self.demonstrate_environment_variables
puts "=== Environment Variable Processing ==="
# Simulate different environment configurations
environments = [
{
"DATABASE_URL" => "postgresql://localhost:5432/myapp",
"REDIS_URL" => "redis://localhost:6379",
"LOG_LEVEL" => "info",
"FEATURES" => "feature1,feature2,feature3"
},
{
"DATABASE_URL" => "postgresql://localhost:5432/myapp"
# Missing other configurations
},
{} # Empty environment
]
environments.each_with_index do |env, i|
puts "\nEnvironment #{i + 1}:"
# Safe environment variable access
db_url = env&.[]("DATABASE_URL")
redis_url = env&.[]("REDIS_URL")
log_level = env&.[]("LOG_LEVEL")
features = env&.[]("FEATURES")
puts " database URL: #{db_url.inspect}"
puts " redis URL: #{redis_url.inspect}"
puts " log level: #{log_level.inspect}"
# Safe string processing
feature_list = features&.split(",")&.map(&:strip) || []
puts " features: #{feature_list}"
# URL parsing with safe navigation
if db_url
begin
uri = URI.parse(db_url)
db_host = uri&.host
db_port = uri&.port
db_name = uri&.path&.[](1..-1) # Remove leading slash
puts " DB host: #{db_host}"
puts " DB port: #{db_port}"
puts " DB name: #{db_name}"
rescue URI::InvalidURIError
puts " Invalid database URL"
end
end
# Configuration validation
has_database = !db_url&.strip&.empty?
has_cache = !redis_url&.strip&.empty?
valid_log_level = %w[debug info warn error].include?(log_level&.downcase)
puts " has database: #{has_database}"
puts " has cache: #{has_cache}"
puts " valid log level: #{valid_log_level}"
end
end
def self.demonstrate_configuration_files
puts "\n=== Configuration File Processing ==="
# Simulate loaded configuration files
config_files = [
{
application: {
name: "MyApp",
version: "1.0.0",
environment: "production"
},
database: {
host: "localhost",
port: 5432,
pool: { min: 5, max: 20 }
},
features: {
new_ui: true,
analytics: false,
beta_features: ["feature_a", "feature_b"]
}
},
{
application: {
name: "MyApp"
# Missing version and environment
},
database: {
host: "localhost"
# Missing port and pool
}
# Missing features section
},
nil # Failed to load config
]
config_files.each_with_index do |config, i|
puts "\nConfiguration #{i + 1}:"
# Safe configuration access
app_name = config&.dig(:application, :name)
app_version = config&.dig(:application, :version)
app_env = config&.dig(:application, :environment)
db_host = config&.dig(:database, :host)
db_port = config&.dig(:database, :port)
pool_min = config&.dig(:database, :pool, :min)
pool_max = config&.dig(:database, :pool, :max)
puts " app: #{app_name} v#{app_version} (#{app_env})"
puts " database: #{db_host}:#{db_port}"
puts " pool: #{pool_min}-#{pool_max}"
# Feature flags processing
new_ui_enabled = config&.dig(:features, :new_ui) || false
analytics_enabled = config&.dig(:features, :analytics) || false
beta_features = config&.dig(:features, :beta_features) || []
puts " new UI: #{new_ui_enabled}"
puts " analytics: #{analytics_enabled}"
puts " beta features: #{beta_features}"
# Configuration validation
has_required_config = app_name && db_host
has_production_config = app_env == "production" && pool_min && pool_max
beta_count = beta_features&.length || 0
puts " has required config: #{has_required_config}"
puts " production ready: #{has_production_config}"
puts " beta features count: #{beta_count}"
end
end
def self.demonstrate_settings_hierarchy
puts "\n=== Settings Hierarchy Processing ==="
# Simulate cascading settings (defaults < user < session)
settings_stack = [
{
defaults: {
theme: "light",
language: "en",
notifications: { email: true, push: false },
privacy: { profile_public: false, show_email: false }
},
user_preferences: {
theme: "dark",
notifications: { email: false }
# Partial override
},
session_overrides: {
language: "es"
# Minimal override
}
},
{
defaults: {
theme: "light",
language: "en"
},
user_preferences: nil, # No user preferences
session_overrides: {} # Empty session
},
{
defaults: nil, # No defaults
user_preferences: { theme: "dark" },
session_overrides: nil
}
]
settings_stack.each_with_index do |stack, i|
puts "\nSettings Stack #{i + 1}:"
defaults = stack&.[](:defaults)
user_prefs = stack&.[](:user_preferences)
session = stack&.[](:session_overrides)
# Cascade settings with safe navigation
theme = session&.[](:theme) || user_prefs&.[](:theme) || defaults&.[](:theme) || "system"
language = session&.[](:language) || user_prefs&.[](:language) || defaults&.[](:language) || "en"
# Nested setting cascading
email_notifications = session&.dig(:notifications, :email) ||
user_prefs&.dig(:notifications, :email) ||
defaults&.dig(:notifications, :email) ||
true
push_notifications = session&.dig(:notifications, :push) ||
user_prefs&.dig(:notifications, :push) ||
defaults&.dig(:notifications, :push) ||
false
profile_public = session&.dig(:privacy, :profile_public) ||
user_prefs&.dig(:privacy, :profile_public) ||
defaults&.dig(:privacy, :profile_public) ||
false
puts " final theme: #{theme}"
puts " final language: #{language}"
puts " email notifications: #{email_notifications}"
puts " push notifications: #{push_notifications}"
puts " profile public: #{profile_public}"
# Settings source tracking
theme_source = session&.key?(:theme) ? "session" :
user_prefs&.key?(:theme) ? "user" :
defaults&.key?(:theme) ? "default" : "fallback"
puts " theme source: #{theme_source}"
end
end
end
ConfigurationProcessing.demonstrate_environment_variables
ConfigurationProcessing.demonstrate_configuration_files
ConfigurationProcessing.demonstrate_settings_hierarchy
class PerformanceAndAlternatives
def self.performance_comparison
puts "=== Performance Comparison ==="
require 'benchmark'
# Test data
valid_object = { user: { profile: { name: "Alice" } } }
nil_object = nil
n = 100_000
puts "Comparing performance for #{n} iterations:"
Benchmark.bm(25) do |x|
x.report("Traditional nil check") do
n.times do
if valid_object && valid_object[:user] && valid_object[:user][:profile]
valid_object[:user][:profile][:name]
end
end
end
x.report("Safe navigation") do
n.times do
valid_object&.dig(:user, :profile, :name)
end
end
x.report("Rescue approach") do
n.times do
begin
valid_object[:user][:profile][:name]
rescue
nil
end
end
end
x.report("Try-like method") do
def try_method(obj, *methods)
methods.reduce(obj) { |o, m| o.respond_to?(m) ? o.send(m) : nil }
end
n.times do
try_method(valid_object, :[], :user, :[], :profile, :[], :name)
end
end
end
end
def self.demonstrate_alternatives
puts "\n=== Alternative Approaches ==="
test_data = [
{ user: { name: "Alice", email: "alice@example.com" } },
{ user: { name: "Bob" } }, # Missing email
{}, # Missing user
nil # Nil object
]
test_data.each_with_index do |data, i|
puts "\nData #{i + 1}: #{data.inspect}"
# Method 1: Safe navigation
email1 = data&.dig(:user, :email)
# Method 2: Fetch with default
email2 = data&.fetch(:user, {})&.fetch(:email, nil)
# Method 3: Custom safe accessor
def safe_get(obj, *keys)
keys.reduce(obj) do |current, key|
case current
when Hash
current[key]
when Array
current[key] if key.is_a?(Integer) && key >= 0
else
nil
end
end
end
email3 = safe_get(data, :user, :email)
# Method 4: Nil object pattern
class NilUser
def email; nil; end
def name; "Anonymous"; end
end
user_obj = data&.[](:user) || NilUser.new
email4 = user_obj.respond_to?(:email) ? user_obj.email : nil
puts " Safe navigation: #{email1.inspect}"
puts " Fetch method: #{email2.inspect}"
puts " Custom safe get: #{email3.inspect}"
puts " Nil object: #{email4.inspect}"
end
end
def self.demonstrate_best_practice_patterns
puts "\n=== Best Practice Patterns ==="
class UserService
def self.format_user_display(user_data)
# Pattern 1: Early return for nil
return "No user data" unless user_data
# Pattern 2: Safe navigation with meaningful defaults
name = user_data&.dig(:profile, :display_name) ||
user_data&.dig(:profile, :full_name) ||
user_data&.[](:username) ||
"Anonymous User"
email = user_data&.[](:email)
avatar = user_data&.dig(:profile, :avatar, :url) || "/images/default-avatar.png"
# Pattern 3: Conditional formatting
email_display = email ? " (#{email})" : ""
# Pattern 4: Safe string operations
bio = user_data&.dig(:profile, :bio)&.strip
bio_preview = bio&.length&.> 50 ? "#{bio[0..47]}..." : bio
{
display_name: name,
contact: "#{name}#{email_display}",
avatar_url: avatar,
bio_preview: bio_preview
}
end
def self.validate_user_data(user_data)
# Pattern 5: Validation with safe navigation
errors = []
# Required field validation
name = user_data&.dig(:profile, :display_name) || user_data&.[](:username)
errors << "Name is required" if name&.strip&.empty? || name.nil?
email = user_data&.[](:email)
errors << "Email is required" if email&.strip&.empty? || email.nil?
errors << "Email format invalid" if email && !email&.include?("@")
# Optional field validation
bio = user_data&.dig(:profile, :bio)
errors << "Bio too long" if bio&.length&.> 500
phone = user_data&.dig(:contact, :phone)
errors << "Invalid phone format" if phone && !phone&.match?(/^\+?[\d\s\-\(\)]+$/)
{
valid: errors.empty?,
errors: errors,
data: {
name: name,
email: email,
bio: bio,
phone: phone
}
}
end
end
# Test the patterns
test_users = [
{
username: "alice123",
email: "alice@example.com",
profile: {
display_name: "Alice Smith",
bio: "Software developer passionate about Ruby and web development.",
avatar: { url: "https://example.com/avatar1.jpg" }
},
contact: { phone: "+1-555-0123" }
},
{
email: "bob@example.com",
profile: { bio: "" }
# Missing name and other fields
},
{} # Empty user data
]
test_users.each_with_index do |user, i|
puts "\nUser #{i + 1}:"
display_info = UserService.format_user_display(user)
validation_result = UserService.validate_user_data(user)
puts " Display: #{display_info[:display_name]}"
puts " Contact: #{display_info[:contact]}"
puts " Bio: #{display_info[:bio_preview].inspect}"
puts " Valid: #{validation_result[:valid]}"
puts " Errors: #{validation_result[:errors]}" unless validation_result[:errors].empty?
end
end
end
# PerformanceAndAlternatives.performance_comparison # Uncomment to run benchmark
PerformanceAndAlternatives.demonstrate_alternatives
PerformanceAndAlternatives.demonstrate_best_practice_patterns
Watch and learn safe navigation operator