Rails 🛤 DSLs Explained: How Ruby Makes Configuration Elegant

Ruby on Rails is known for its developer-friendly syntax and expressive code structure. One of the key reasons behind this elegance is its use of Domain-Specific Languages (DSLs). DSLs make Rails configurations, routes, and testing more intuitive by allowing developers to write code that reads like natural language.

In this blog post, we’ll explore what DSLs are, how Rails implements them, and why they make development in Rails both powerful and enjoyable.


What is a DSL?

A Domain-Specific Language (DSL) is a specialized language designed to solve problems in a specific domain. Unlike general-purpose languages (like Ruby or Java), a DSL provides a more concise and readable syntax for a particular task.

Two types of DSLs exist:

  • Internal DSLs: Written using an existing programming language’s syntax (e.g., Rails DSLs in Ruby).
  • External DSLs: Separate from the host language and require a custom parser (e.g., SQL, Regular Expressions).

Rails uses Internal DSLs to simplify web development. Let’s explore some core DSLs in Rails and how they work under the hood.


1. Routes in Rails: A Classic Example of DSL

In config/routes.rb, Rails provides a DSL to define application routes in a clear and structured way.

Example:

Rails.application.routes.draw do
  resources :users do
    resources :posts
  end

  get '/about', to: 'pages#about'
  root 'home#index'
end

How Does This Work?

  • resources :users automatically generates RESTful routes for UsersController.
  • get '/about', to: 'pages#about' maps a GET request to the about action in PagesController.
  • root 'home#index' sets the default landing page.

Why Use a DSL for Routes?

  • Concise & Readable: Avoids manually defining each route.
  • Expressive Syntax: Reads like a structured list of instructions.
  • Reduces Boilerplate Code: Automates RESTful route creation.

Under the hood, Rails uses metaprogramming to convert this DSL into actual Ruby methods that map HTTP requests to controllers.


2. Configuration DSL in Rails: config/environments/development.rb

Rails also provides a DSL for application configuration using Rails.application.configure.

Example:

Rails.application.configure do
  config.cache_classes = false
  config.eager_load = false
  config.consider_all_requests_local = true
end

What is config Here?

  • config is an instance of Rails::Application::Configuration, a special Ruby object that stores settings.
  • The configure block modifies application settings dynamically using method calls.

Why a DSL for Configuration?

  • Expressiveness: Instead of setting key-value pairs in a hash, we use method calls (config.cache_classes = false).
  • Customization: Each environment (development, test, production) has its own configuration file.
  • Readability: Makes it easy to understand and modify settings.

3. RSpec’s describe Method: A DSL for Testing

RSpec, the popular testing framework for Ruby, provides a DSL for writing tests.

Example:

describe User do
  it "has a valid factory" do
    user = FactoryBot.create(:user)
    expect(user).to be_valid
  end
end

How Does This Work?

  • describe User do ... end defines a test suite for the User model.
  • it "has a valid factory" do ... end describes an individual test case.
  • expect(user).to be_valid checks if the user instance is valid.

Under the hood, describe is a method that creates a structured test suite dynamically.

Why Use a DSL for Testing?

  • Improves Readability: Tests read like English sentences.
  • Encapsulates Test Logic: Eliminates boilerplate setup code.
  • Encourages Behavior-Driven Development (BDD).

4. Defining Methods Dynamically: ActiveSupport::Concern

Rails extends DSL capabilities with ActiveSupport::Concern, which allows modular mixins in models and controllers.

Example:

module Trackable
  extend ActiveSupport::Concern

  included do
    before_save :track_changes
  end

  private
  def track_changes
    puts "Tracking changes!"
  end
end

class User < ApplicationRecord
  include Trackable
end

How This Works:

  • included do ... end executes code when the module is included in a class.
  • before_save :track_changes hooks into the Rails lifecycle to run before saving a record.

Why a DSL for Mixins?

  • Encapsulation: Keeps related logic together.
  • Reusability: Can be included in multiple models.
  • Cleaner Code: Removes redundant callbacks in models.

Conclusion: Why Rails Embraces DSLs

DSLs in Rails make the framework expressive, flexible, and developer-friendly. They provide:

Concise syntax (reducing boilerplate code). ✅ Readability (code reads like natural language). ✅ Powerful abstractions (simplifying complex tasks). ✅ Customization (tailoring behavior dynamically).

By leveraging DSLs, Rails makes web development intuitive, allowing developers to focus on building great applications rather than writing repetitive code.

So next time you’re defining routes, configuring settings, or writing tests in Rails—remember, you’re using DSLs that make your life easier!

Enjoy Ruby 🚀

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 🚀

Exploring Rails 8: Powerful 💪 Features, Deployment & Real-Time Updates

Introduction

Rails 8.x has arrived, bringing exciting new features and enhancements to improve productivity, performance, and ease of development. From built-in authentication to real-time WebSocket updates, this latest version of Rails continues its commitment to being a powerful and developer-friendly framework.

Let’s dive into some of the most significant features and improvements introduced in Rails 8.


Rails 8 Features & Enhancements

1. Modern JavaScript with Importmaps & Hotwire

Rails 8 eliminates the need for Webpack and Node.js, allowing developers to manage JavaScript dependencies more efficiently. Importmaps simplify dependency management by fetching JavaScript packages directly and caching them locally, removing runtime dependencies.

Key Benefits:

  • Faster page loads and reduced complexity
  • No need for Node.js or Webpack
  • Dependencies are cached locally and loaded efficiently

Example: Pinning a Package

bin/importmap pin local-time

This command fetches the package from npm and stores it locally for future use.

Hotwire Integration

Hotwire enables dynamic page updates without requiring heavy JavaScript frameworks. Rails 8 fully integrates Turbo and Stimulus, making frontend interactivity more seamless.

Importing Dependencies in application.js:
import "trix";

With this setup, developers can create reactive UI elements with minimal JavaScript.


2. Real-Time WebSockets with Action Cable & Turbo Streams

Rails 8 enhances real-time functionality with Action Cable and Turbo Streams, allowing WebSocket-based updates across multiple pages without additional JavaScript libraries.

Setting Up Turbo Streams in Views:

<%= turbo_stream_from @object %>

This creates a WebSocket channel tied to the object.

Broadcasting Updates from Models:

broadcast_to :object, render(partial: "objects/object", locals: { object: self })

Any changes to the object will be instantly reflected across all connected clients.

Why This Matters:

  • No need for third-party WebSocket npm packages
  • Real-time updates are built into Rails
  • Simplifies building interactive applications

3. Rich Text with ActionText

Rails 8 continues to support ActionText, making it easy to handle rich text content within models and views.

Model Level Implementation:

has_rich_text :body

This enables rich text storage and formatting for the body attribute of a model.

View Implementation:

<%= form.rich_text_area :body %>

This adds a full-featured WYSIWYG text editor to the form, allowing users to create and edit rich text content seamlessly.

Displaying Updated Timestamps:

<%= time_tag post.updated_at %>

This helper formats timestamps cleanly, improving date and time representation in views.


4. Deployment with Kamal – Simpler & Faster

Rails 8 introduces Kamal, a modern deployment tool that simplifies remote deployment by leveraging Docker containers.

Deployment Steps:

  1. Setup Remote Serverkamal setup
    • Installs Docker (if missing) and configures the server.
  2. Deploy the Applicationkamal deploy
    • Builds and ships a Docker container using Rails’ default Dockerfile.

File Uploads with Active Storage

By default, Kamal stores uploaded files in Docker volumes, but this can be customized based on specific deployment needs.


5. Built-in Authentication – No Devise Needed

Rails 8 introduces native authentication, reducing reliance on third-party gems like Devise. This built-in system manages password encryption, user sessions, and password resets while keeping signup flows flexible.

Generating Authentication:

rails g authentication
rails db:migrate

Creating a User for Testing:

User.create(email: "user@example.com", password: "securepass")

Managing Authentication:

  • Uses bcrypt for password encryption
  • Provides a pre-built sessions_controller for handling authentication
  • Allows remote database changes via: kamal console

6. Turning a Rails App into a PWA

Rails 8 makes it incredibly simple to transform any app into a Progressive Web App (PWA), enabling offline support and installability.

Steps to Enable PWA:

  1. Modify application.html.erb: <%= tag.link pwa_manifest_path %>
  2. Ensure manifest and service-worker routes are enabled.
  3. Verify PWA files: pwa/manifest.json.erb and pwa/service-worker.js.
  4. Deploy and restart the application to see the Install button in the browser.

Final Thoughts

Rails 8 is packed with developer-friendly features that improve security, real-time updates, and deployment workflows. With Hotwire, Kamal, and native authentication, it’s clear that Rails is evolving to reduce dependencies while enhancing performance.

Are you excited about Rails 8? Let me know your thoughts and experiences in the comments below!

Why Ruby begin Block with ensure is Important

Ruby provides a powerful way to handle exceptions using the begin block. One of the key features of this block is ensure, which ensures that a certain section of code runs no matter what happens in the begin block. This is particularly useful when dealing with resource management, such as file handling, database connections, and network requests.

Understanding begin, rescue, and ensure

The begin block in Ruby is used to handle potential exceptions. It works alongside rescue, which catches exceptions, and ensure, which executes code regardless of whether an exception occurs.

Basic Syntax:

begin
  # Code that might raise an exception
rescue SomeError => e
  # Handle the exception
ensure
  # Code that will always execute
end

Why is ensure Important?

  1. Guaranteed Execution – Code inside ensure runs no matter what, ensuring cleanup actions always occur.
  2. Resource Cleanup – Ensures that resources like file handles, database connections, or network sockets are properly closed.
  3. Prevents Leaks – Helps avoid memory or resource leaks by making sure cleanup is performed.

Example 1: File Handling

One of the most common uses of ensure is closing a file after performing operations.

file = nil
begin
  file = File.open("example.txt", "r")
  puts file.read
rescue StandardError => e
  puts "An error occurred: #{e.message}"
ensure
  file.close if file
  puts "File closed."
end

Explanation:

  • The begin block opens a file and reads its contents.
  • If an error occurs (e.g., file not found), the rescue block catches it.
  • The ensure block ensures that the file is closed, preventing resource leaks.

Example 2: Database Connection Handling

Handling database connections properly is crucial to avoid locked or hanging connections.

require 'sqlite3'

db = nil
begin
  db = SQLite3::Database.open("test.db") # Open database connection
  db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
  db.execute("INSERT INTO users (name) VALUES ('Alice')")
  puts "User added successfully."
rescue SQLite3::Exception => e
  puts "Database error: #{e.message}"
ensure
  db.close if db # Ensure the database connection is closed
  puts "Database connection closed."
end

Explanation:

  • Opens a database connection and executes SQL statements.
  • If an error occurs, such as a syntax error in SQL, rescue catches it.
  • The ensure block ensures the database connection is closed, preventing connection leaks.

Example 3: Network Request Handling

When making HTTP requests, errors like timeouts or invalid URLs can occur. Using ensure, we can ensure proper handling.

require 'net/http'

url = URI("http://example.com")
response = nil

begin
  response = Net::HTTP.get(url)
  puts "Response received: #{response[0..50]}..." # Print a snippet of the response
rescue StandardError => e
  puts "Network error: #{e.message}"
ensure
  puts "Request complete. Cleanup actions (if any) can be performed here."
end

Explanation:

  • Makes an HTTP request to a given URL.
  • If an error occurs (e.g., network failure), rescue handles it.
  • The ensure block ensures any necessary final actions, such as logging, happen.

Key Takeaways

  • The ensure block always executes, making it essential for cleanup tasks.
  • It helps prevent resource leaks by ensuring proper closure of files, database connections, and network requests.
  • Using ensure makes your Ruby code robust and reliable, handling errors gracefully while ensuring necessary actions take place.

By incorporating ensure in your Ruby code, you can improve reliability, maintainability, and efficiency in handling critical resources.

Can We Do Type Checking in Ruby Method Parameters?

Ruby is a dynamically typed language that favors duck typing over strict type enforcement. However, there are cases where type checking can be useful to avoid unexpected behavior. In this post, we’ll explore various ways to perform type validation and type checking in Ruby.

Type Checking and Type Casting in Ruby

Yes, even though Ruby does not enforce types at the language level, there are several techniques to validate the types of method parameters. Below are some approaches:

1. Manual Type Checking with raise

One straightforward way to enforce type checks is by manually verifying the type of a parameter using is_a? and raising an error if it does not match the expected type.

def my_method(arg)
  raise TypeError, "Expected String, got #{arg.class}" unless arg.is_a?(String)
  
  puts "Valid input: #{arg}"
end

my_method("Hello")  # Works fine
my_method(123)      # Raises: TypeError: Expected String, got Integer

2. Using respond_to? for Duck Typing

Rather than enforcing a strict class type, we can check whether an object responds to a specific method.

def my_method(arg)
  unless arg.respond_to?(:to_str)
    raise TypeError, "Expected a string-like object, got #{arg.class}"
  end
  
  puts "Valid input: #{arg}"
end

my_method("Hello")  # Works fine
my_method(:symbol)  # Raises TypeError

3. Using Ruby 3’s Type Signatures (RBS)

Ruby 3 introduced RBS and TypeProf for static type checking. You can define types in an .rbs file:

def my_method: (String) -> void

Then, you can use tools like steep, a static type checker for Ruby, to enforce type checking at development time.

How to Use Steep for Type Checking

Steep does not use annotations or perform type inference on its own. Instead, it relies on .rbi files to define type signatures. Here’s how you can use Steep for type checking:

  1. Define a Ruby Class:
class Calculator
  def initialize(value)
    @value = value
  end
  
  def double
    @value * 2
  end
end

  1. Generate an .rbi File:
steep scaffold calculator.rb > sig/calculator.rbi

This generates an .rbi file, but initially, it will use any for all types. You need to manually edit it to specify proper types.

  1. Modify the .rbi File to Define Types:
class Calculator
  @value: Integer
  def initialize: (Integer) -> void
  def double: () -> Integer
end

  1. Run Steep to Check Types:
steep check

Steep also supports generics and union types, making it a powerful but less intrusive type-checking tool compared to Sorbet.

4. Using Sorbet for Stronger Type Checking

Sorbet is a third-party static type checker that allows you to enforce type constraints at runtime.

require 'sorbet-runtime'

extend T::Sig

sig { params(arg: String).void }
def my_method(arg)
  puts "Valid input: #{arg}"
end

my_method("Hello")  # Works fine
my_method(123)      # Raises error at runtime

References:

Another Approach: Using Rescue for Type Validation

A different way to handle type checking is by using exception handling (rescue) to catch unexpected types and enforce validation.

def process_order(order_items, customer_name, discount_code)
  # Main logic
  ...

rescue => e
  # Type and validation checks
  raise "Expecting an array of items: #{order_items.inspect}" unless order_items.is_a?(Array)
  raise "Order must contain at least one item: #{order_items.inspect}" if order_items.empty?
  raise "Expecting a string for customer name: #{customer_name.inspect}" unless customer_name.is_a?(String)
  raise "Customer name cannot be empty" if customer_name.strip.empty?
  
  raise "Unexpected error in `process_order`: #{e.message}"
end

Summary

  • Use is_a? or respond_to? for runtime type checking.
  • Use Ruby 3’s RBS for static type enforcement.
  • Use Sorbet for stricter type checking at runtime.
  • Use Steep for static type checking with RBS.
  • Exception handling can be used for validating types dynamically.

Additional Considerations

Ruby is a dynamically typed language, and unit tests can often be more effective than type checks in ensuring correctness. Writing tests ensures that method contracts are upheld for expected data.

For Ruby versions prior to 3.0, install the rbs gem separately to define types for classes.

If a method is defined, it will likely be called. If reasonable tests exist, every method will be executed and checked. Therefore, instead of adding excessive type checks, investing time in writing tests can be a better strategy.

Installing ⚙️ and Setting Up 🔧 Ruby 3.4, Rails 8.0 and IDE on macOS in 2025

Ruby on Rails is a powerful framework for building web applications. If you’re setting up your development environment on macOS in 2025, this guide will walk you through installing Ruby 3.4, Rails 8, and a best IDE for development.

1. Installing Ruby and Rails

“While macOS comes with Ruby pre-installed, it’s often outdated and can’t be upgraded easily. Using a version manager like Mise allows you to install the latest Ruby version, switch between versions, and upgrade as needed.” – Rails guides

Install Dependencies

Run the following command to install essential dependencies (takes time):

brew install openssl@3 libyaml gmp rust

…..
==> Installing rust dependency: libssh2, readline, sqlite, python@3.13, pkgconf
==> Installing rust

zsh completions have been installed to:
/opt/homebrew/share/zsh/site-functions
==> Summary
🍺 /opt/homebrew/Cellar/rust/1.84.1: 3,566 files, 321.3MB
==> Running brew cleanup rust
==> openssl@3
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
/opt/homebrew/etc/openssl@3/certs

and run
/opt/homebrew/opt/openssl@3/bin/c_rehash
==> rust
zsh completions have been installed to:
/opt/homebrew/share/zsh/site-functions

Install Mise Version Manager

curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc

Install Ruby and Rails

mise use -g ruby@3
mise ruby@3.4.1 ✓ installed
mise ~/.config/mise/config.toml tools: ruby@3.4.1

ruby --version   # output Ruby 3.4.1

gem install rails

# reload terminal and check
rails --version  # output Rails 8.0.1

For additional guidance, refer to these resources:


2. Installing an IDE for Ruby on Rails Development

Choosing the right Integrated Development Environment (IDE) is crucial for productivity. Here are some popular options:

RubyMine

  • Feature-rich and specifically designed for Ruby on Rails.
  • Includes debugging tools, database integration, and smart code assistance.
  • Paid software that can be resource-intensive.

Sublime Text

  • Lightweight and highly customizable.
  • Requires plugins for additional functionality.

Visual Studio Code (VS Code) (Recommended)

  • Free and open-source.
  • Excellent plugin support.

Install VS Code

Follow the official installation guide.

Enable GitHub Copilot for AI-assisted coding:

  1. Open VS Code.
  2. Sign in with your GitHub account.
  3. Enable Copilot from the extensions panel.

To use VS Code from the terminal, ensure code is added to your $PATH:

  1. Open Command Palette (Cmd+Shift+P).
  2. Search for Shell Command: Install 'code' command in PATH.
  3. Restart your terminal and try: code .

3. Your 15 Essential VS Code Extensions for Ruby on Rails

To enhance your development workflow, install the following VS Code extensions:

  1. GitHub Copilot – AI-assisted coding (already installed).
  2. vscode-icons – Better file and folder icons.
  3. Tabnine AI – AI autocompletion for JavaScript and other languages.
  4. Ruby & Ruby LSP – Language support and linting.
  5. ERB Formatter/Beautify – Formats .erb files (requires htmlbeautifier gem): gem install htmlbeautifier
  6. ERB Helper Tags – Autocomplete for ERB tags.
  7. GitLens – Advanced Git integration.
  8. Ruby Solargraph – Provides code completion and inline documentation (requires solargraph gem): gem install solargraph
  9. Rails DB Schema – Auto-completion for Rails database schema.
  10. ruby-rubocop – Ruby linting and auto-formatting (requires rubocop gem): gem install rubocop
  11. endwise – Auto-adds end keyword in Ruby.
  12. Output Colorizer – Enhances syntax highlighting in log files.
  13. Auto Rename Tag – Automatically renames paired HTML/Ruby tags.
  14. Highlight Matching Tag – Highlights matching tags for better visibility.
  15. Bracket Pair Colorizer 2 – Improved bracket highlighting.

Conclusion

By following this guide, you’ve successfully set up a robust Ruby on Rails development environment on macOS. With Mise for version management, Rails installed, and VS Code configured with essential extensions, you’re ready to start building Ruby on Rails applications.

Part 2: https://railsdrop.com/2025/03/22/setup-rails-8-app-rubocop-actiontext-image-processing-part-2

Happy Rails setup! 🚀

Setting Up Terminal 🖥️ for Development on MacOS (Updated 2025)

If you’re setting up your MacBook for development, having a well-configured terminal is essential. This guide will walk you through installing and configuring a powerful terminal setup using Homebrew, iTerm2, and Oh My Zsh, along with useful plugins.

1. Install Homebrew

Homebrew is a package manager that simplifies installing software on macOS.

Open the Terminal and run:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

After installation, add Homebrew to your PATH by running the following commands:

echo >> ~/.zprofile
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

Verify installation:

brew --version

Check here.

2. Install iTerm2

The default macOS Terminal is functional but lacks advanced features. iTerm2 is a powerful alternative.

Install it using Homebrew:

brew install --cask iterm2

Open iTerm2 from your Applications folder after installation.

Check and Install Git

Ensure Git is installed:

git --version

If not installed, install it using Homebrew:

brew install git

3. Install Oh My Zsh

Oh My Zsh enhances the Zsh shell with themes and plugins. Install it with:

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Check here.

Configure .zshrc

Edit your .zshrc file:

vim ~/.zshrc

Add useful plugins:

plugins=(git rails ruby)

The default theme is robbyrussell. You can explore other themes here.

Customize iTerm2 Color Scheme

Find and import themes from iTerm2 Color Schemes.

4. Add Zsh Plugins

Enhance your terminal experience with useful plugins.

a. Install zsh-autosuggestions

This plugin provides command suggestions as you type.

Install via Oh My Zsh:

git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

Or install via Homebrew:

brew install zsh-autosuggestions

Add to ~/.zshrc:

plugins=(git rails ruby zsh-autosuggestions)

If installed via Homebrew, add:

source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh

to the bottom of ~/.zshrc.

Restart iTerm2:

exec zsh

b. Install zsh-syntax-highlighting

This plugin highlights commands to distinguish valid syntax from errors.

Install via Oh My Zsh:

git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

Add to .zshrc:

plugins=(git rails ruby zsh-autosuggestions zsh-syntax-highlighting)

Restart iTerm2:

exec zsh

Wrapping Up

Your terminal is now set up for an optimized development experience! With Homebrew, iTerm2, Oh My Zsh, and useful plugins, your workflow will be faster and more efficient.

to be continued …

The Evolution of Asset 📑 Management in Web and Ruby on Rails

Understanding Middleware in Rails

When a client request comes into a Rails application, it doesn’t always go directly to the MVC (Model-View-Controller) layer. Instead, it might first pass through middleware, which handles tasks such as authentication, logging, and static asset management.

Rails uses middleware like ActionDispatch::Static to efficiently serve static assets before they even reach the main application.

ActionDispatch::Static Documentation

“This middleware serves static files from disk, if available. If no file is found, it hands off to the main app.”

Where Are Static Files Stored?

Rails stores static assets in the public/ directory, and ActionDispatch::Static ensures these are served efficiently without hitting the Rails stack.

Core Components of Ruby on Rails – A reminder

To understand asset management evolution, let’s quickly revisit Rails’ core components:

  • ActiveRecord: Object-relational mapping (ORM) system for database interactions.
  • Action Pack: Handles the controller and view layers.
  • Active Support: A collection of utility classes and standard library extensions.
  • Action Mailer: A framework for designing email services.

The Role of Browsers in Asset Management

Web browsers cache static assets to improve performance. The caching strategy varies based on asset types:

  • Images: Rarely change, so they are aggressively cached.
  • JavaScript and CSS files: Frequently updated, requiring cache-busting mechanisms.

The Era of Sprockets

Historically, Rails used Sprockets as its default asset pipeline. Sprockets provided:

  • Conversion of CoffeeScript to JavaScript and SCSS to CSS.
  • Minification and bundling of assets into fewer files.
  • Digest-based caching to ensure updated assets were fetched when changed.

The Rise of JavaScript & The Shift Towards Webpack

The release of ES6 (2015-2016) was a turning point for JavaScript, fueling the rise of Single Page Applications (SPAs). This marked a shift from traditional asset management:

  • Sprockets was effective but became complex and difficult to configure for modern JS frameworks.
  • Projects started including package.json at the root, indicating JavaScript dependency management.
  • Webpack emerged as the go-to tool for handling JavaScript, offering features like tree-shaking, hot module replacement, and modern JavaScript syntax support.

The Landscape in 2024: A More Simplified Approach

Recent advancements in web technology have drastically simplified asset management:

  1. ES6 Native Support in All Major Browsers
    • No need for transpilation of modern JavaScript.
  2. CSS Advancements
    • Features like variables and nesting eliminate the need for preprocessors like SASS.
  3. HTTP/2 and Multiplexing
    • Enables parallel loading of multiple assets over a single connection, reducing dependency on bundling strategies.

Enter Propshaft: The Modern Asset Pipeline

Propshaft is the new asset management solution introduced in Rails, replacing Sprockets for simpler and faster asset handling. Key benefits include:

  • Digest-based file stamping for effective cache busting.
  • Direct and predictable mapping of assets without complex processing.
  • Better integration with HTTP/2 for efficient asset delivery.

Rails 8 Precompile Uses Propshaft

What is Precompile? A Reminder

Precompilation hashes all file names and places them in the public/ folder, making them accessible to the public.

Propshaft improves upon this by creating a manifest file that maps the original filename as a key and the hashed filename as a value. This significantly enhances the developer experience in Rails.

Propshaft ultimately moves asset management in Rails to the next level, making it more efficient and streamlined.

The Future of Asset Management in Rails

With advancements like native ES6 support and CSS improvements, Rails continues evolving to embrace simpler, more efficient asset management strategies. Propshaft, combined with modern browser capabilities, makes asset handling seamless and more performance-oriented.

As the web progresses, we can expect further simplifications in asset pipelines, making Rails applications faster and easier to maintain.

Stay tuned for more innovations in the Rails ecosystem!

Happy Rails Coding! 🚀