Inside Rails: The Role of Rack 🗄 and Middleware 🔌

Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the bridge between web servers, web frameworks, and web application into a single method call.

Where is it used?

  • Rails (built on Rack)
  • Sinatra and Hanami
  • Middleware development

What is a Rack-Based Application?

A Rack-based application is any Ruby web application that implements the Rack interface. This means the app must follow Rack’s simple calling convention:

app = Proc.new do |env|
  ['200', { 'Content-Type' => 'text/html' }, ['Hello, Rack!']]
end

This returns an array of three elements:

  1. HTTP status code ('200')
  2. Headers ({ 'Content-Type' => 'text/html' })
  3. Response body (['Hello, Rack!'])
Example: Basic Rack Application
require 'rack'

app = Proc.new do |env|
  ['200', { 'Content-Type' => 'text/html' }, ['Hello, Rack!']]
end

Rack::Handler::WEBrick.run app, Port: 9292

Run it with:

ruby my_rack_app.rb

Open http://localhost:9292 in your browser.

Does Rails Use Rack?

Yes, Rails uses Rack. Rack serves as the interface between Rails and web servers like Puma or WEBrick.

How Rails Uses Rack

When a request comes in:

  1. The web server (Puma/WEBrick) receives it.
  2. The server passes the request to Rack.
  3. Rack processes the request and sends it through Rails middleware.
  4. After passing through the middleware stack, Rails’ router (ActionDispatch) decides which controller/action should handle the request.
  5. The response is generated, sent back through Rack, and returned to the web server.

Check /design_studio/config.ru file in our Rails 8 app is responsible for starting the server.

You can actually run a Rails app using just Rack!

  1. Create a config.ru file / use existing one:
require_relative 'config/environment'
run Rails.application
  1. Run it using Rack:
rackup -p 4343

open http://localhost:4343/products

This runs your Rails app without Puma or WEBrick, proving Rails works via Rack.

Is Rack a Server?

No, Rack is not a server. Instead, Rack is a middleware interface that sits between the web server (like Puma or WEBrick) and your Ruby application (like Rails or Sinatra).

How Does Rack Fit with Web Servers Like Puma and WEBrick?

Puma and WEBrick support Rack by implementing the Rack::Handler interface, allowing them to serve any Rack-based application, such as Rails and Sinatra.

  • Puma and WEBrick are not built “on top of” Rack—they are independent web servers.
  • However, they implement Rack::Handler, which means they support Rack applications.
  • This allows them to serve Rails, Sinatra, and other Rack-based applications.

The Relationship Between Rack, Web Servers, and Rails

  1. Rack provides a standard API for handling HTTP requests and responses.
  2. Web servers (Puma, WEBrick, etc.) implement Rack::Handler so they can run any Rack-based app.
  3. Rails supports Rack by implementing the Rack interface, allowing it to interact with web servers and middleware.

How Rails Supports Rack

  1. Rack Middleware: Rails includes middleware components that process requests before they reach controllers.
  2. Rack Interface: Rails applications can be run using config.ru, which follows the Rack convention.
  3. Web Server Communication: Rails works with Rack-compatible servers like Puma and WEBrick.

Illustration of How a Request Flows

  1. The browser sends a request to the server (Puma/WEBrick).
  2. The server passes the request to Rack.
  3. Rack processes the request (passing it through middleware).
  4. Rails handles the request and generates a response.
  5. The response goes back through Rack and is sent to the server, which then passes it to the browser.

So, while Rack is not a server, it allows web servers to communicate with Ruby web applications like Rails.

Adding Middleware in a Rails 8 App

Middleware is a way to process requests before they reach your Rails application.

How Does Middleware Fit In?

Middleware in Rails is just a Rack application that modifies requests/responses before they reach the main Rails app.

Example: Custom Middleware

Create a new file in app/middleware/my_middleware.rb:

class MyMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    body = ["Custom Middleware: "] + body
    [status, headers, body]
  end
end

Now, add it to Rails in config/application.rb:

config.middleware.use MyMiddleware

Restart your Rails server, and all responses will be prefixed with Custom Middleware:

Setup 🛠 Rails 8 App – Part 6: Attach images to Product model

To attach multiple images to a Product model in Rails 8, Active Storage provides the best way using has_many_attached. Below are the steps to set up multiple image attachments in a local development environment.


1️⃣ Install Active Storage (if not already installed)

We have already done this step if you are following this series. Else run the following command to generate the necessary database migrations:

rails active_storage:install
rails db:migrate

This will create two tables in your database:

  • active_storage_blobs → Stores metadata of uploaded files.
  • active_storage_attachments → Creates associations between models and uploaded files.

2️⃣ Update the Product Model

Configuring specific variants is done the same way as has_one_attached, by calling the variant method on the yielded attachable object:

add in app/models/product.rb:

class Product < ApplicationRecord
  has_many_attached :images do |attachable|
    attachable.variant :normal, resize_to_limit: [540, 720]
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

You just have to mention the above and rails will create everything for you!

Variants rely on ImageProcessing gem for the actual transformations of the file, so you must add gem "image_processing" to your Gemfile if you wish to use variants.

By default, images will be processed with ImageMagick using the MiniMagick gem, but you can also switch to the libvips processor operated by the ruby-vips gem.

Rails.application.config.active_storage.variant_processor
# => :mini_magick

Rails.application.config.active_storage.variant_processor = :vips
# => :vips

3️⃣ Configure Active Storage for Local Development

By default, Rails stores uploaded files in storage/ under your project directory.

Ensure your config/environments/development.rb has:

config.active_storage.service = :local

And check config/storage.yml to ensure you have:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

This will store the uploaded files in storage/.


4️⃣ Add File Uploads in Controller

Modify app/controllers/products_controller.rb to allow multiple image uploads:

class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)

    if @product.save
      redirect_to @product, notice: "Product was successfully created."
    else
      render :new
    end
  end

  private

  def product_params
    params.require(:product).permit(:name, :description, images: [])
  end
end

Notice images: [] → This allows multiple images to be uploaded.


5️⃣ Update Form for Multiple Image Uploads

Modify app/views/products/_form.html.erb:

<%= form_with model: @product, local: true do |form| %>
  <%= form.label :name %>
  <%= form.text_field :name %>

  <%= form.label :description %>
  <%= form.text_area :description %>

  <%= form.label :images %>
  <%= form.file_field :images, multiple: true %>

  <%= form.submit "Create Product" %>
<% end %>

🔹 multiple: true → Allows selecting multiple files.


6️⃣ Display Images in View

Modify app/views/products/_product.html.erb:

<h1><%= product.name %></h1>
<p><%= product.description %></p>

<h3>Product Images:</h3>
<% product.images.each do |image| %>
  <%= image_tag image.variant(:thumb), alt: "Product Image" %>
<% end %>
<% product.images.each do |image| %>
  <%= image_tag image, alt: "Product Image" %>
<% end %>

Replacing vs Adding Attachments

By default in Rails, attaching files to a has_many_attached association will replace any existing attachments.

To keep existing attachments, you can use hidden form fields with the signed_id of each attached file:

<% @message.images.each do |image| %>
  <%= form.hidden_field :images, multiple: true, value: image.signed_id %>
<% end %>

<%= form.file_field :images, multiple: true %>

This has the advantage of making it possible to remove existing attachments selectively, e.g. by using JavaScript to remove individual hidden fields.


7️⃣ Get Image URLs

In Rails Console (rails c):

product = Product.last
product.images.each do |image|
  puts Rails.application.routes.url_helpers.rails_blob_url(image, host: "http://localhost:3000")
end

This generates a direct URL for each attached image.


8️⃣ Delete an Attached Image

To remove an image from a product:

product = Product.last
product.images.first.purge  # Deletes a single image

To remove all images:

product.images.purge_later


Final Thoughts

  • has_many_attached :images is the best approach for multiple image uploads.
  • Local storage (storage/) is great for development, but for production, use S3 or another cloud storage.
  • Variants allow resizing images before displaying them.

Check: https://guides.rubyonrails.org/active_storage_overview.html https://github.com/<username>/<project>/tree/main/app/views/products

Enjoy Rails! 🚀

to be continued..

Setup 🛠 Rails 8 App – Part 5: Active Storage File Uploads 📤

Meanwhile we are setting up some UI for our app using Tailwind CSS, I have uploaded 2 images to our product in the rich text editor. Let’s discuss about this in this post.

Understanding Active Storage in Rails 8: A Deep Dive into Image Uploads

In our Rails 8 application, we recently tested uploading two images to a product using the rich text editor. This process internally triggers several actions within Active Storage. Let’s break down what happens behind the scenes.

How Active Storage Handles Image Uploads

When an image is uploaded, Rails 8 processes it through Active Storage, creating a new blob entry and storing it in the disk service. The following request is fired:

Processing by ActiveStorage::DirectUploadsController#create as JSON

Parameters: {"blob" => {"filename" => "floral-kurtha.jpg", "content_type" => "image/jpeg", "byte_size" => 107508, "checksum" => "GgNgNxxxxxxxjdPOLw=="}}

This request initiates a database entry in active_storage_blobs:

INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "service_name", "byte_size", "checksum", "created_at")
VALUES ('huk9dxxxxxxxx09e2cyiq', 'floral-kurtha.jpg', 'image/jpeg', 'local', 107312, 'Fxxxxxxd+bpRibo2EfvA==', '2025-03-31 08:10:07.232453')

Storing Files and Generating URLs

Once the blob entry is created, Rails stores the file on disk and generates a URL:

http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsiYSI6eyJxxxxxxx

This process triggers the ActiveStorage::DiskController, handling file storage via a PUT request:

Started PUT "/rails/active_storage/disk/eyJfcmFpbHMiOxxxxx"
Disk Storage (0.9ms) Uploaded file to key: hut9d0zxssxxxxxx
Completed 204 No Content in 96ms

Retrieving Images from Active Storage

After successfully storing the file, the application fetches the image via a GET request:

Started GET "/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOxxxxxxxxxxfQ==--f9c556012577xxxxxxxxxxxxfa21/floral-kurtha-2.jpg"

This request is handled by:

Processing by ActiveStorage::Blobs::RedirectController#show as JPEG

The file is then served via the ActiveStorage::DiskController#show:

Redirected to http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsiZGxxxxxxxxxd048aae4ab5c30/floral-kurtha-2.jpg

Updating Records with Active Storage Attachments

When updating a product, the system also updates its associated images. The following Active Storage updates occur:

UPDATE "action_text_rich_texts" SET "body" = .... WHERE "action_text_rich_texts"."id" = 1

UPDATE "active_storage_blobs" SET "metadata" = '{"identified":true}' WHERE "active_storage_blobs"."id" = 3

INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ('embeds', 'ActionText::RichText', 1, 3, '2025-03-31 11:46:13.464597')

Additionally, Rails updates the updated_at timestamp of the associated records:

UPDATE "products" SET "updated_at" = '2025-03-31 11:46:13.523640' WHERE "products"."id" = 1

Best Practices for Active Storage in Rails 8

  1. Use Direct Uploads: This improves performance by uploading files directly to cloud storage (e.g., AWS S3, Google Cloud Storage) instead of routing them through your Rails server.
  2. Attach Images Efficiently: Use has_one_attached or has_many_attached for file associations in models.
  3. Avoid Serving Files via Rails: Use a CDN or proxy service to serve images instead of relying on Rails controllers.
  4. Clean Up Unused Blobs: Regularly remove orphaned blob records using ActiveStorage::Blob.unattached.destroy_all.
  5. Optimize Image Processing: Use variants (image.variant(resize: "300x300").processed) to generate resized images efficiently.

In Rails 8, Active Storage uses two main tables for handling file uploads:

1. active_storage_blobs Table

This table stores metadata about the uploaded files but not the actual files. Each row represents a unique file (or “blob”) uploaded to Active Storage.

Columns in active_storage_blobs Table:

  • id – Unique identifier for the blob.
  • key – A unique key used to retrieve the file.
  • filename – The original name of the uploaded file.
  • content_type – The MIME type (e.g., image/jpeg, application/pdf).
  • metadata – JSON data storing additional information (e.g., width/height for images).
  • service_name – The storage service (e.g., local, amazon, google).
  • byte_size – File size in bytes.
  • checksum – A checksum to verify file integrity.
  • created_at – Timestamp when the file was uploaded.

Example Entry in active_storage_blobs:

INSERT INTO "active_storage_blobs" 
("key", "filename", "content_type", "service_name", "byte_size", "checksum", "created_at") 
VALUES ('avevnp6eg1xxxxxxsz8it6267eou7', 'floral-kurtha-2.jpg', 'image/jpeg', 'local', 204800, '0U0cXxxxxxxxxx/1u47Szg==', '2025-03-31 11:45:07.232453');

👉 Purpose: This table acts as a record of stored files and their metadata.


2. active_storage_attachments Table

This table links blobs (files) to Active Record models. Instead of storing files directly in the database, Rails stores a reference to the blob.

Columns in active_storage_attachments Table:

  • id – Unique identifier for the attachment.
  • name – Name of the attachment (:avatar, :images, etc.).
  • record_type – The model type associated with the file (User, Post, etc.).
  • record_id – The ID of the record in the model (users.id, posts.id).
  • blob_id – The corresponding ID from active_storage_blobs.
  • created_at – Timestamp when the association was created.

Example Entry in active_storage_attachments:

INSERT INTO "active_storage_attachments" 
("name", "record_type", "record_id", "blob_id", "created_at") 
VALUES ('avatar', 'User', 1, 42, '2025-03-31 08:15:20.123456');

INSERT INTO "active_storage_attachments" 
("name", "record_type", "record_id", "blob_id", "created_at") 
VALUES ('embeds', 'ActionText::RichText', 1, 4, '2025-03-31 11:46:20.123456');

👉 Purpose: This table allows a single file to be attached to multiple records without duplicating the file itself.


Why Does Rails Need Both Tables?

  1. Separation of Concerns:
    • active_storage_blobs tracks the files themselves.
    • active_storage_attachments links them to models.
  2. Efficient File Management:
    • The same file can be used in multiple places without storing it multiple times.
    • If a file is no longer attached to any record, Rails can remove it safely.
  3. Supports Different Attachments:
    • A model can have different types of attachments (avatar, cover_photo, documents).
    • A single model can have multiple files attached (has_many_attached).

Example Usage in Rails 8

class User < ApplicationRecord
  has_one_attached :avatar   # Single file
  has_many_attached :photos  # Multiple files
end

When a file is uploaded, an entry is added to active_storage_blobs, and an association is created in active_storage_attachments.

How Rails Queries These Tables

user.avatar # Fetches from `active_storage_blobs` via `active_storage_attachments`
user.photos.each { |photo| puts photo.filename } # Fetches multiple attached files

Conclusion

Rails 8 uses two tables to decouple file storage from model associations, enabling better efficiency, flexibility, and reusability. This structure allows models to reference files without duplicating them, making Active Storage a powerful solution for file management in Rails applications. 🚀


Where Are Files Stored in Rails 8 by Default?

By default, Rails 8 stores uploaded files using Active Storage’s disk service, meaning files are saved in the storage/ directory within your Rails project.

Default Storage Location:

  • Files are stored in:
    storage/
    ├── cache/ (temporary files)
    ├── store/ (permanent storage)
    └── variant/ (image transformations like resizing)
  • The exact file path inside storage/ is determined by the key column in the active_storage_blobs table. For example, if a blob entry has: key = 'xyz123abcd' then the file is stored at: storage/store/xyz123abcd

How to Change the Storage Location?

You can configure storage in config/storage.yml. For example:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: your_own_bucket-<%= Rails.env %>

Then, update config/environments/development.rb (or production.rb) to use:

config.active_storage.service = :local  # or :amazon for S3


How to Get the Stored File Path in Rails 8 Active Storage

Since Rails stores files in a structured directory inside storage/, the actual file path can be determined using the key stored in the active_storage_blobs table.

Get the File Path in Local Storage

If you’re using the Disk service (default for development and test), you can retrieve the stored file path manually:

blob = ActiveStorage::Blob.last
file_path = Rails.root.join("storage", "store", blob.key)
puts file_path

🔹 Example Output:

/your_project/storage/store/xyz123abcd

💡 This path is internal and cannot be accessed directly from a browser.


How to Get the File URL

Instead of accessing the internal path, Active Storage provides methods to generate URLs for public access.

1. Generate a URL for Direct Access

If you want a publicly accessible URL, you can use:

Rails.application.routes.url_helpers.rails_blob_url(blob, host: "http://localhost:3000")

🔹 Example Output:

http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiO...--filename.jpg

This redirects to the actual file storage location.

2. Get a Temporary Signed URL

For direct storage services like S3 or Google Cloud Storage, you can generate a signed URL:

blob.service_url

🔹 Example Output (for S3 storage):

https://your-s3-bucket.s3.amazonaws.com/xyz123abcd?X-Amz-Signature=...

🔹 Example Output (for local storage, using Disk service):

http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiO...

This signed URL expires after a set time (default is a few minutes).

3. Get a Variant URL for an Image

If your file is an image and you want a resized version, use:

variant = blob.variant(resize: "300x300").processed
Rails.application.routes.url_helpers.rails_representation_url(variant, host: "http://localhost:3000")

🔹 Example Output:

http://localhost:3000/rails/active_storage/representations/abcdxyz.../resize_300x300.jpg

Summary

TaskCommand
Get internal file pathRails.root.join("storage", "store", blob.key)
Get public file URLRails.application.routes.url_helpers.rails_blob_url(blob, host: "http://localhost:3000")
Get signed (temporary) URL (If your model has has_one/many_attached)blob.service_url
Get resized image URLRails.application.routes.url_helpers.rails_representation_url(blob.variant(resize: "300x300").processed, host: "http://localhost:3000")
  • Files are stored in the storage/ directory by default.
  • Use rails_blob_url or service_url to get an accessible URL.
  • Use variant to generate resized versions.
  • For production, it’s best to use a cloud storage service like Amazon S3.

Understanding has_one_attached and has_many_attached in Rails 8

Rails 8 provides a built-in way to handle file attachments through Active Storage. The key methods for attaching files to models are:

  1. has_one_attached – For a single file attachment.
  2. has_many_attached – For multiple file attachments.

Let’s break down what they do and why they are useful.

1. has_one_attached

This is used when a model should have a single file attachment. For example, a User model may have only one profile picture.

Usage:

class User < ApplicationRecord
  has_one_attached :avatar
end

How It Works:

  • When you upload a file, Active Storage creates an entry in the active_storage_blobs table.
  • The active_storage_attachments table links this file to the record.
  • If a new file is attached, the old one is automatically replaced.

Example: Attaching and Displaying an Image

user = User.find(1)
user.avatar.attach(io: File.open("/path/to/avatar.jpg"), filename: "avatar.jpg", content_type: "image/jpeg")

# Checking if an avatar exists
user.avatar.attached? # => true

# Displaying the image in a view
<%= image_tag user.avatar.variant(resize: "100x100").processed if user.avatar.attached? %>

2. has_many_attached

Use this when a model can have multiple file attachments. For instance, a Product model may have multiple images.

Usage:

class Product < ApplicationRecord
  has_many_attached :images
end

How It Works:

  • Multiple files can be attached to a single record.
  • Active Storage tracks all file uploads in the active_storage_blobs and active_storage_attachments tables.
  • Deleting an attachment removes it from storage.

Example: Attaching and Displaying Multiple Images

product = Product.find(1)
product.images.attach([
  { io: File.open("/path/to/image1.jpg"), filename: "image1.jpg", content_type: "image/jpeg" },
  { io: File.open("/path/to/image2.jpg"), filename: "image2.jpg", content_type: "image/jpeg" }
])

# Checking if images exist
product.images.attached? # => true

# Displaying all images in a view
<% if product.images.attached? %>
  <% product.images.each do |image| %>
    <%= image_tag image.variant(resize: "200x200").processed %>
  <% end %>
<% end %>

Benefits of Using has_one_attached & has_many_attached

  1. Simplifies File Attachments – Directly associates files with Active Record models.
  2. No Need for Extra Tables – Unlike some gems (e.g., CarrierWave), Active Storage doesn’t require additional tables for storing file paths.
  3. Easy Cloud Storage Integration – Works seamlessly with Amazon S3, Google Cloud Storage, and Azure.
  4. Variant Processing – Generates resized versions of images using variant (e.g., thumbnails).
  5. Automatic Cleanup – Old attachments are automatically removed when replaced.

Final Thoughts

Active Storage in Rails 8 provides a seamless way to manage file uploads, integrating directly with models while handling storage efficiently. By understanding how it processes uploads internally, we can better optimize performance and ensure a smooth user experience.

In an upcoming blog, we’ll dive deeper into Turbo Streams and how they enhance real-time updates in Rails applications.

Setup 🛠 Rails 8 App – Part 4: Tailwind CSS 🎨 into the action

Does Tailwind Take Time?

  • If you haven’t used Tailwind before, expect a slight learning curve (1-2 days) to get comfortable.
  • But once you grasp utility classes, styling becomes faster than Bootstrap.

Fastest Way to Set Up Tailwind in Rails 8

1. Install Tailwind

   rails new myapp --css=tailwind

Or, if you already have a Rails app:

   ✗ bundle add tailwindcss-rails
     Fetching gem metadata from https://rubygems.org/.........
     Resolving dependencies...
     Fetching gem metadata from https://rubygems.org/.........
     Resolving dependencies...
     Fetching tailwindcss-ruby 4.0.17 (arm64-darwin)
     Installing tailwindcss-ruby 4.0.17 (arm64-darwin)
     Fetching tailwindcss-rails 4.2.1
     Installing tailwindcss-rails 4.2.1
   
  ✗ rails tailwindcss:install
      apply  /Users/abhilash/.local/share/mise/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/tailwindcss-rails-4.2.1/lib/install/install_tailwindcss.rb
  Add Tailwindcss container element in application layout
      insert    app/views/layouts/application.html.erb
      insert    app/views/layouts/application.html.erb
  Build into app/assets/builds
      create    app/assets/builds
      create    app/assets/builds/.keep
      append    .gitignore
  Add default /Users/xxxxx/rails/design_studio/app/assets/tailwind/application.css
      create    app/assets/tailwind/application.css
  Add default Procfile.dev
      create    Procfile.dev
  Ensure foreman is installed
         run    gem install foreman from "."
         Fetching foreman-0.88.1.gem
         Successfully installed foreman-0.88.1

Refer: https://tailwindcss.com/docs/installation/framework-guides/ruby-on-rails

Note: You can see this create a Procfile.dev file and installs foreman gem.

The foreman gem in Rails is used to manage and run multiple processes in development using a Procfile. It is particularly useful when your Rails application depends on several background services that need to run simultaneously.

Why is foreman used?

  • It allows you to define and manage multiple services (like Rails server, Tailwind compiler, Sidekiq, etc.) in a single command.
  • Ensures that all necessary processes start together, making development easier.
  • Helps simulate production environments where multiple services need to run concurrently.

Who is using foreman? Rails or Tailwind CSS?

  • The foreman gem itself is not specific to Rails or Tailwind CSS—it is a general-purpose process manager.
  • In our case, both Rails and Tailwind CSS are using foreman.
    • Rails: You can use foreman to start Rails server, background jobs, and Webpack.
    • Tailwind CSS: Since Tailwind needs a process to watch and compile CSS files (using npx tailwindcss -i input.css -o output.css --watch), foreman helps keep this process running.

What is Procfile.dev in Tailwind CSS?

  • When you install Tailwind in Rails, a Procfile.dev is created to define the processes required for development.
  • Example Procfile.dev for Tailwind and Rails:
    web: bin/rails server -p 3000
    js: yarn build --watch
    css: bin/rails tailwindcss:watch
    • web: Starts the Rails server.
    • js: Watches and compiles JavaScript files (if using esbuild or webpack).
    • css: Watches and compiles Tailwind CSS files.

How to use foreman?

  • Run the following command to start all processes defined in Procfile.dev: bin/dev
    • This starts the Rails server, the Tailwind CSS watcher, and other necessary processes.

The foreman gem is used as a process manager to run multiple services in development. In our case, both Rails and Tailwind CSS are using it. It ensures that Tailwind’s CSS compilation process runs alongside the Rails server.

2. Use Tailwind Classes in Views

Example:

   <div class="container mx-auto p-4">
     <h1 class="text-blue-500 text-3xl font-bold">Welcome to My App</h1>
     <button class="bg-green-500 text-white px-4 py-2 rounded">Click Me</button>
   </div>

This keeps your CSS minimal, avoids custom stylesheets, and helps you learn Tailwind naturally while building your app.

Here’s a Tailwind CSS Cheat Sheet to help you get started quickly with your Rails 8 app.

📌 1. Layout & Spacing

<div class="container mx-auto p-4">
  <div class="m-4 p-4 bg-gray-200">Margin & Padding</div>
</div>
  • mx-auto → Center content horizontally
  • p-4 → Padding (4 * 4px = 16px)
  • m-4 → Margin (4 * 4px = 16px)

🎨 2. Colors

<div class="bg-blue-500 text-white p-4">Blue Background</div>
<div class="text-red-600">Red Text</div>
  • Colors: gray, red, blue, green, etc.
  • Shades: 100 - 900 (lighter → darker)

Refer: https://tailwindcss.com/docs/background-color


🔤 3. Typography

<p class="text-lg font-bold">Large Bold Text</p>
<p class="text-sm italic">Small Italic Text</p>
  • text-lg → Large text
  • text-sm → Small text
  • font-bold → Bold text
  • italic → Italic text

📏 4. Width & Height

<div class="w-1/2 h-32 bg-green-300">50% Width, 128px Height</div>
  • w-1/2 → 50% width
  • w-full → Full width
  • h-32 → 128px height

🔳 5. Flexbox & Grid

<div class="flex justify-center items-center h-screen">
  <div class="bg-blue-500 p-4 text-white">Centered Box</div>
</div>
  • flex → Enables flexbox
  • justify-center → Centers horizontally
  • items-center → Centers vertically

🔘 6. Buttons

<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  Click Me
</button>
  • hover:bg-blue-700 → Darker blue on hover
  • rounded → Rounded corners

🔗 7. Borders & Shadows

<div class="border border-gray-400 shadow-lg p-4">Border & Shadow</div>
  • border → Default border
  • border-gray-400 → Gray border
  • shadow-lg → Large shadow

📲 8. Responsive Design

<div class="p-4 text-sm md:text-lg lg:text-xl">
  Text size changes on different screens!
</div>
  • sm: → Small screens (≥ 640px)
  • md: → Medium screens (≥ 768px)
  • lg: → Large screens (≥ 1024px)

✨ 9. Animations & Transitions

<button class="bg-green-500 transition duration-300 hover:bg-green-700">
  Smooth Transition
</button>
  • transition → Enables animations
  • duration-300 → 300ms transition speed
  • hover:bg-green-700 → Change color on hover

🚀 Quick Setup for Your Rails 8 App

  1. Install Tailwind
   rails new myapp --css=tailwind
  1. Use Tailwind Classes in Views
   <div class="text-center p-6 text-2xl text-blue-500">Hello Tailwind!</div>

🔥 Bonus: Try Tailwind Play for live previews!

Tailwind in design studio

Check our Rails 8 app for tailwind implementation: https://github.com/abhilashak/design_studio/blob/main/bin/dev

I have added Tailwind CSS to our design_studio app. You can check the commit here: https://github.com/abhilashak/design_studio/commit/07db459

Track our Github Issue for tailwind task: https://github.com/abhilashak/design_studio/issues/1

The above issue will be automatically closed if you follow our Git commit message principle, that is mentioned in the below post:

Finally add the Tailwind Plugin Tailwind CSS IntelliSense for VS Code to make development easy.

to be continued.. 🚀

Setup 🛠 Rails 8 App – Part 2: Command line, VS Code, RuboCop, Server, Action text, Image processing

In the first part of this guide, we covered setting up a Rails 8 app with essential configurations. In this follow-up, we’ll go over optimizing command-line usage, setting up VS Code for development, running migrations, styling the app, and enabling Action Text.


1. Optimizing Command-Line Usage with Aliases

One of the best ways to speed up development is to create shortcuts for frequently used commands. You can do this by adding aliases to your shell configuration.

Steps to Add an Alias:

  1. Open your shell configuration file:
    vim ~/.zshrc
  2. Search for the alias section:
    <esc> / alias <enter>
  3. Add your alias:
    alias gs="git status"
  4. Save and exit:
    <esc> :wq
  5. Reload your configuration:
    source ~/.zshrc
  6. Use your new alias:
    gs

This method saves time by allowing you to run frequently used commands more quickly.


2. Using Terminal Efficiently in VS Code

By default, VS Code uses `Ctrl + “ to toggle the terminal, which may not be intuitive. You can change this shortcut:

  1. Open VS Code.
  2. Go to Settings → Keyboard Shortcuts.
  3. Search for Toggle Terminal.
  4. Click Edit and change it to Ctrl + Opt + T for easier access.

3. Setting Up RuboCop in VS Code

RuboCop ensures your Ruby code follows best practices. Here’s how to set it up:

Checking RuboCop from the Terminal:

rubocop .

VS Code Setup:

  1. Open Command Palette (Cmd + Shift + P) and search for “Lint by RuboCop”.
  2. Go to Extensions Tab and install “VS Code RuboCop”.
  3. In VS Code Settings, search for “Rubocop” and check Ruby -> Rubocop -> Execute Path.
  4. Find the RuboCop installation path: whereis rubocop Example output: ~/.local/share/mise/installs/ruby/3.4.1/bin/rubocop/
  5. Update the Execute Path in VS Code to: ~/.local/share/mise/installs/ruby/3.4.1/bin/
  6. If RuboCop still returns an empty output, check .rubocop.yml in your project: ~/rails/design_studio/.rubocop.yml
  7. If the issue persists, ensure the gem is installed: gem install rubocop
  8. Restart VS Code from the Rails project root: code .

For more details, check the official documentation: RuboCop Usage


4. Running Migrations and Starting the Server

Running Migrations:

rails db:migrate -t

You can check the file: db/schema.rb You can see the active storage tables for attachments and other info.

Starting the Rails Server:

bin/dev

Access your application at: http://localhost:3000/

Check the product routes:


5. Adding Basic Styles

To quickly improve the appearance of your site, add this to application.html.erb:

<%= stylesheet_link_tag "https://cdn.simplecss.org/simple.css" %>

Alternatively, use Tailwind CSS for a more modern approach.

An example of convention over configurations in view file in Rails:

<div id="products">
  <%= render brand.products %>
</div>

Rails will look into the views/products/ folder and fetch right partial file that match.


6. Debugging with Rails Console

If an error occurs, you can inspect variables and data in the Rails console:

rails console


7. Installing Action Text for Rich Text Editing

Action Text is not installed by default but can be added easily:

rails action_text:install

This command:

  • Installs JavaScript dependencies
  • Creates necessary stylesheets
  • Generates database migrations for rich text

Running Migrations:

rails db:migrate -t

If you encounter an error about missing gems:

Could not find gem 'image_processing (~> 1.2)'

Run:

bundle install
rails db:migrate -t

Updating the Product Model:

Add this to app/models/product.rb:

has_rich_text :description

Updating the Form View:

In app/views/products/_form.html.erb, replace the description input with:

<%= form.rich_text_area :description %>

Now, visiting the new product page should display a rich text editor.


8. Solving Image Processing Errors

If you see an error like:

LoadError: Could not open library 'vips.42'

Install libvips to resolve it:

brew install vips


Conclusion

In this second part, we covered:

  • Optimizing terminal usage with aliases.
  • Configuring VS Code for efficient development.
  • Running migrations and starting the Rails server.
  • Enhancing the UI with styles.
  • Debugging using Rails console.
  • Installing and configuring Action Text for rich text support.

Stay tuned for more Rails 8 improvements!

Part 3: https://railsdrop.com/2025/03/25/setup-rails-8-app-git-setup-gitignore-part-3/

To be continued… 🚀

Setup 🛠 Rails 8 App – Part 1: Setup All Necessary Configurations | Ruby | Rails setup | Kamal | Rails Generations

Ruby on Rails 8 introduces several improvements that make development easier, more secure, and more maintainable. In this guide, we’ll walk through setting up a new Rails 8 application while noting the significant configurations and features that come out of the box.

1. Check Your Ruby and Rails Versions

If not installed Ruby 3.4 and Rails 8.0 please check: https://railsdrop.com/2025/02/11/installing-and-setup-ruby-3-rails-8-vscode-ide-on-macos-in-2025/

Before starting, ensure that you have the correct versions of Ruby and Rails installed:

$ ruby -v
ruby 3.4.1

$ rails -v
Rails 8.0.1

If you don’t have these versions installed, update them using your package manager or version manager (like rbenv or RVM).

2. Create a New Rails 8 Application

Run the following command to create a new Rails app:

$ rails new design_studio

Noteworthy Files and Directories Created

Here are some interesting files and directories that are generated with a new Rails 8 app:

 create  .ruby-version
 create  bin/brakeman
 create  bin/rubocop
 create  bin/docker-entrypoint
 create  .rubocop.yml
 create  .github/workflows
 create  .github/workflows/ci.yml
 create  config/cable.yml
 create  config/storage.yml
 create  config/initializers/content_security_policy.rb
 create  config/initializers/filter_parameter_logging.rb
 create  config/initializers/new_framework_defaults_8_0.rb

Key Takeaways:

  • Security & Code Quality Tools: Brakeman (security scanner) and RuboCop (code style linter) are included by default.
  • Docker Support: The presence of bin/docker-entrypoint suggests better built-in support for containerized deployment.
  • GitHub Actions Workflow: The .github/workflows/ci.yml file provides default CI configurations.
  • Enhanced Security: The content_security_policy.rb initializer helps enforce a strict security policy.
  • New Rails Defaults: The new_framework_defaults_8_0.rb initializer helps manage breaking changes in Rails 8.

Rails automatically creates the following during the creation of the rails new app.

a. Configuring Import Maps and Installing Turbo & Stimulus

Rails 8 still defaults to Import Maps for JavaScript package management, avoiding the need for Node.js and Webpack:

$ rails turbo:install stimulus:install

This creates the following files:

create    config/importmap.rb
create    app/javascript/controllers
create    app/javascript/controllers/index.js
create    app/javascript/controllers/hello_controller.js
append    config/importmap.rb

Key Takeaways:

  • Import Maps: Defined in config/importmap.rb, allowing dependency management without npm.
  • Hotwired Support: Turbo and Stimulus are automatically configured for modern front-end development.
  • Generated Controllers: Stimulus controllers are pre-configured inside app/javascript/controllers/.

b. Deploying with Kamal

Kamal simplifies deployment with Docker and Kubernetes. Rails 8 includes built-in support:

$ bundle binstubs kamal
$ bundle exec kamal init

This results in:

Created .kamal/secrets file
Created sample hooks in .kamal/hooks

Key Takeaways:

  • Automated Deployment Setup: Kamal provides easy-to-use deployment scripts.
  • Secret Management: The .kamal/secrets file ensures secure handling of credentials.
  • Deployment Hooks: Custom hooks allow pre- and post-deployment scripts for automation.

c. Setting Up Caching and Queues with Solid Cache, Queue, and Cable

NOTE: Rails automatically creates this for you while creating the rails app.

Rails 8 includes Solid Cache, Solid Queue, and Solid Cable for enhanced performance and scalability:

$ rails solid_cache:install solid_queue:install solid_cable:install

This creates:

create  config/cache.yml
create  db/cache_schema.rb
create  config/queue.yml

Key Takeaways:

  • Caching Support: config/cache.yml manages application-wide caching.
  • Database-Powered Queue System: Solid Queue simplifies background job management without requiring external dependencies like Sidekiq.
  • Real-Time WebSockets: Solid Cable offers Action Cable improvements for real-time features.

3. Rails 8 Migration Enhancements

Rails 8 provides new shortcuts and syntax improvements for database migrations:

NOT NULL Constraints with ! Shortcut

You can impose NOT NULL constraints directly from the command line using !:

# Example for not null constraints: 
➜ rails generate model User name:string!

Type Modifiers in Migrations

Rails 8 allows passing commonly used type modifiers directly via the command line. These modifiers are enclosed in curly braces {} after the field type.

# Example for model generation: 
➜ rails generate model Product name:string description:text
# Example for passing modifiers: 
➜ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}

Generating a Scaffold for the Product Model

Let’s generate a complete scaffold for our Product model:

✗ rails generate scaffold product title:string! description:text category:string color:string 'size:string{10}' 'mrp:decimal{7,2}' 'discount:decimal{7,2}' 'rating:decimal{1,1}'
➜  design_studio git:(main) ✗ rails -v
Rails 8.0.1
➜  design_studio git:(main) ✗ ruby -v
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [arm64-darwin24]
➜  design_studio git:(main) ✗ rails generate scaffold product title:string! description:text category:string color:string 'size:string{10}' 'mrp:decimal{7,2}' 'discount:decimal{7,2}' 'rating:decimal{1,1}'

Using the Rails Resource Generator

The rails g resource command is a powerful way to generate models, controllers, migrations, and routes all in one go. This is particularly useful when setting up RESTful resources in a Rails application.

Basic Syntax

➜ rails g resource product

This command creates the necessary files for a new resource, including:

  • A model (app/models/product.rb)
  • A migration file (db/migrate/)
  • A controller (app/controllers/product_controller.rb)
  • Routes in config/routes.rb
  • A test file (test/controllers/product_controller_test.rb or spec/)

Example Usage

To generate a Post resource with attributes:

➜ rails g resource Product title:string! description:text brand:references

This will:

  1. Create a Product model with title and description attributes.
  2. Add a brand_id foreign key as a reference.
  3. Apply a NOT NULL constraint on title (! shortcut).
  4. Generate a corresponding migration file.
  5. Set up routes automatically (resources :products).

Running the Migration

After generating a resource, apply the migration to update the database:

➜ rails db:migrate


Difference Between resource and scaffold

Rails provides both rails g resource and rails g scaffold, but they serve different purposes:

Featurerails g resourcerails g scaffold
Generates a Model
Generates a Migration
Generates a Controller✅ (empty actions)✅ (full CRUD actions)
Generates Views (HTML/ERB)✅ (index, show, new, edit, etc.)
Generates Routes
Generates Helper Files
Generates Tests
  • rails g resource is minimal—it generates only essential files without view templates. It’s useful when you want more control over how your views and controller actions are built.
  • rails g scaffold is more opinionated and generates full CRUD functionality with prebuilt forms and views, making it ideal for rapid prototyping.

If you need full CRUD functionality quickly, use scaffold. If you prefer a leaner setup with manual control over implementation, use resource.

Conclusion

Rails 8 significantly enhances the development experience with built-in security tools, CI/CD workflows, streamlined deployment via Kamal, and modern front-end support with Turbo & Stimulus. It also improves caching, background jobs, and real-time features with Solid tools.

These improvements make Rails 8 a robust framework for modern web applications, reducing the need for additional dependencies while keeping development efficient and secure.

Enjoy Rails! 🚀

Mastering 𖡎 Ruby: Exploring Syntax Sugar, Operators, and Useful Methods

Ruby is known for its elegant and expressive syntax, making it one of the most enjoyable programming languages to work with. In this blog post, we will explore some interesting aspects of Ruby, including syntax sugar, operator methods, useful array operations, and handy built-in methods.


Ruby Syntax Sugar

Ruby provides several shorthand notations that enhance code readability and reduce verbosity. Let’s explore some useful syntax sugar techniques.

Shorthand for Array and Symbol Arrays

Instead of manually writing arrays of strings or symbols, Ruby provides %w and %i shortcuts.

words = %w[one two three]   # => ["one", "two", "three"]
symbols = %i[one two three] # => [:one, :two, :three]

These notations also support interpolation:

prefix = 'item'
words = %W[#{prefix}_one #{prefix}_two #{prefix}_three]  # => ["item_one", "item_two", "item_three"]
symbols = %I[#{prefix}_one #{prefix}_two #{prefix}_three] # => [:item_one, :item_two, :item_three]

Shorthand for Mapping and Selecting Elements

Instead of using map with blocks, we can use &: shorthand.

strings = %w[one two three]
upcased = strings.map(&:upcase) # => ["ONE", "TWO", "THREE"]

This is equivalent to:

strings.map { |str| str.upcase }


Operator Method Calls

Many mathematical and array operations in Ruby are actually method calls, thanks to Ruby’s message-passing model.

puts 1.+(2)  # => 3 (Same as 1 + 2)

Array Indexing and Append Operations

words = %w[zero one two three four]
puts words.[](2)   # => "two" (Equivalent to words[2])
words.<<('five')   # => ["zero", "one", "two", "three", "four", "five"] (Equivalent to words << "five")

Avoiding Dot Notation with Operators

# Bad practice
num = 10
puts num.+(5) # Avoid this syntax

# Good practice
puts num + 5  # Preferred

Using a linter like rubocop can help enforce best practices in Ruby code.


none? Method in Ruby Arrays

The none? method checks if none of the elements in an array satisfy a given condition.

numbers = [1, 2, 3, 4, 5]
puts numbers.none? { |n| n > 10 }  # => true (None are greater than 10)
puts numbers.none?(&:odd?)          # => false (Some numbers are odd)

This method is useful for quickly asserting that an array does not contain specific values.


Exponent Operator (**) and XOR Operator (^)

Exponentiation

Ruby uses ** for exponentiation instead of ^ (which performs a bitwise XOR operation).

puts 2 ** 3  # => 8 (2 raised to the power of 3)
puts 10 ** 0.5 # => 3.162277660168379 (Square root of 10)

XOR Operator

In Ruby, ^ is used for bitwise XOR operations, which differ from exponentiation.

puts 5 ^ 3  # => 6 (Binary: 101 ^ 011 = 110)
puts 10 ^ 7 # => 13 (Binary: 1010 ^ 0111 = 1101)

This is useful in scenarios involving bitwise manipulations, such as cryptography or performance optimizations.


Safe Navigation Operator (&.)

Ruby provides the safe navigation operator (&.) to avoid NilClass errors when calling methods on potentially nil objects.

user = nil
puts user&.name  # => nil (Avoids NoMethodError)

This is helpful when dealing with uncertain data, such as API responses or optional attributes.


Useful String Methods: chop and find_all

chop: Removing the Last Character

The chop method removes the last character from a string, unlike chomp, which removes only the newline character.

str = "Hello!"
puts str.chop  # => "Hello"

find_all: Filtering Collections

The find_all method (alias of select) filters elements that match a condition.

numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.find_all(&:even?) # => [2, 4]

This is particularly useful for data processing tasks.


=~ Operator in Ruby

The =~ operator is used to match a string against a regular expression and returns the index of the first match or nil if no match is found.

puts "hello" =~ /e/  # => 1 (Index of 'e' in "hello")
puts "hello" =~ /z/  # => nil (No match found)

This operator is useful for quick pattern matching in strings.


.squish Method in Ruby

The .squish method is part of ActiveSupport (Rails) and removes leading, trailing, and extra spaces within a string.

require 'active_support/all'

text = "   Hello   World!  "
puts text.squish  # => "Hello World!"

This is particularly useful for cleaning up user input or text from external sources.


Conclusion

Ruby’s syntax sugar, operator methods, and built-in methods make code more readable, expressive, and powerful. By leveraging these features effectively, you can write clean, efficient, and maintainable Ruby code.

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!

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.