Setup 🛠 Rails 8 App – Part 11: Convert 🔄 Rails App from SQLite to PostgreSQL

If you’ve already built a Rails 8 app using the default SQLite setup and now want to switch to PostgreSQL, here’s a clean step-by-step guide to make the transition smooth:

1.🔧 Setup PostgreSQL in macOS

🔷 Step 1: Install PostgreSQL via Homebrew

Run the following:

brew install postgresql

This created a default database cluster for me, check the output. So you can skip the Step 3.

==> Summary
🍺  /opt/homebrew/Cellar/postgresql@14/14.17_1: 3,330 files, 45.9MB

==> Running `brew cleanup postgresql@14`...
==> postgresql@14
This formula has created a default database cluster with:
  initdb --locale=C -E UTF-8 /opt/homebrew/var/postgresql@14

To start postgresql@14 now and restart at login:
  brew services start postgresql@14

Or, if you don't want/need a background service you can just run:
  /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14

After installation, check the version:

psql --version
> psql (PostgreSQL) 14.17 (Homebrew)

🔷 Step 2: Start PostgreSQL Service

To start PostgreSQL now and have it start automatically at login:

brew services start postgresql
==> Successfully started `postgresql@14` (label: homebrew.mxcl.postgresql@14)

If you just want to run it in the background without autostart:

# pg_ctl — initialize, start, stop, or control a PostgreSQL server
pg_ctl -D /opt/homebrew/var/postgresql@14 start

https://www.postgresql.org/docs/current/app-pg-ctl.html

You can find the installed version using:

brew list | grep postgres

🔷 Step 3: Initialize the Database (if needed)

Sometimes Homebrew does this automatically. If not:

initdb /opt/homebrew/var/postgresql@<version>

Or a more general version:

initdb /usr/local/var/postgres

Key functions of initdb: Creates a new database cluster, Initializes the database cluster’s default locale and character set encoding, Runs a vacuum command.

In essence, initdb prepares the environment for a PostgreSQL database to be used and provides a foundation for creating and managing databases within that cluster

🔷 Step 4: Create a User and Database

PostgreSQL uses a role-based access control. Create a user with superuser privileges:

# createuser creates a new Postgres user
createuser -s postgres

createuser is a shell script wrapper around the SQL command CREATE USER via the Postgres interactive terminal psql. Thus, there is nothing special about creating users via this or other methods

Then switch to psql:

psql postgres

You can also create a database:

createdb <db_name>

🔷 Step 5: Connect and Use psql

psql -d <db_name>

Inside the psql shell, try:

\l    -- list databases
\dt   -- list tables
\q    -- quit

🔷 Step 6: Use a GUI (Optional)

For a friendly UI, install one of the following:

pgAdmin

Postico

TablePlus

2. Update Gemfile

Replace SQLite gem with PostgreSQL:

# Remove or comment this:
# gem "sqlite3", "~> 1.4"

# Add this:
gem "pg", "~> 1.4"

Then run:

bundle install


3. Update config/database.yml

Replace the entire contents of config/database.yml with the following:

default: &default
  adapter: postgresql
  encoding: unicode
  username: postgres
  password:
  host: localhost
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: your_app_name_development

test:
  <<: *default
  database: your_app_name_test

production:
  primary: &primary_production
    <<: *default
    database: your_app_name_production
    username: your_production_username
    password: <%= ENV['YOUR_APP_DATABASE_PASSWORD'] %>
  cache:
    <<: *primary_production
    database: your_app_name_production_cache
    migrations_paths: db/cache_migrate
  queue:
    <<: *primary_production
    database: your_app_name_production_queue
    migrations_paths: db/queue_migrate
  cable:
    <<: *primary_production
    database: your_app_name_production_cable
    migrations_paths: db/cable_migrate

Replace your_app_name with your actual Rails app name.

4. Drop SQLite Database (Optional)

rm storage/development.sqlite3
rm storage/test.sqlite3

5. Create and Setup PostgreSQL Database

rails db:create
rails db:migrate

If you had seed data:

rails db:seed

6. Test It Works

Boot up your server:

bin/dev

Then go to http://localhost:3000 and confirm everything works.

7. Check psql manually (Optional)

psql -d your_app_name_development

Then run:

\dt     -- view tables
\q      -- quit

8. Update .gitignore

Note: If not already added /storage/*

Make sure SQLite DBs are not accidentally committed:

/storage/*.sqlite3
/storage/*.sqlite3-journal


After moving into PostgreSQL

I was getting an issue with postgres column, where I have the following data in the migration:

# migration
t.decimal :rating, precision: 1, scale: 1

# log
ActiveRecord::RangeError (PG::NumericValueOutOfRange: ERROR:  numeric field overflow
12:44:36 web.1  | DETAIL:  A field with precision 1, scale 1 must round to an absolute value less than 1.
12:44:36 web.1  | )

Value passed is: 4.3. I was not getting this issue in SqLite DB.

What does precision: 1, scale: 1 mean?

  • precision: Total number of digits (both left and right of the decimal).
  • scale: Number of digits after the decimal point

If you want to store ratings like 4.3, 4.5, etc., a good setup is:

t.decimal :rating, precision: 2, scale: 1
# revert and migrate for products table

✗ rails db:migrate:down VERSION=2025031XXXXX -t
✗ rails db:migrate:up VERSION=2025031XXXXXX -t

Then go to http://localhost:3000 and confirm everything works.

to be continued.. 🚀

Setup 🛠 Rails 8 App – Part 10: PostgreSQL Into The Action

For a Ruby on Rails 8 application, the choice of database depends on your specific needs, but here’s a breakdown of the best options and when to use each:

PostgreSQL (Highly Recommended)

Best overall choice for most Rails apps.

Why:

  • First-class support in Rails.
  • Advanced features like full-text search, JSONB support, CTEs, window functions.
  • Strong consistency and reliability.
  • Scales well vertically and horizontally (with tools like Citus).
  • Used by: GitHub, Discourse, Basecamp, Shopify.

Use if:

  • You’re building a standard Rails web app or API.
  • You need advanced query features or are handling complex data types (e.g., JSON).

SQLite (For development/testing only)

  • Lightweight, file-based.
  • Fast and easy to set up.
  • But not recommended for production.

Use if:

  • You’re building a quick prototype or local dev/testing app.
  • NOT for multi-user production environments.

MySQL / MariaDB

  • Also supported by Rails.
  • Can work fine for simpler applications.
  • Lacks some advanced features (like robust JSON support or full Postgres-style indexing).
  • Not the default in many modern Rails setups.

Use if:

  • Your team already has MySQL infrastructure or legacy systems.
  • You need horizontal scaling with Galera Cluster or similar setups.

Others (NoSQL like MongoDB, Redis, etc.)

  • Use Redis for caching and background job data (not as primary DB).
  • Use MongoDB or other NoSQL only if your data model really demands it (e.g., unstructured documents, event sourcing).

Recommendation Summary:

Use CaseRecommended DB
Production web/API appPostgreSQL
Dev/prototyping/local testingSQLite
Legacy systems/MySQL infrastructureMySQL/MariaDB
Background jobs/cachingRedis
Special needs (e.g., documents)MongoDB (with caution)

If you’re starting fresh or building something scalable and modern with Rails 8, go with PostgreSQL.

Let’s break that down:

💬 What does “robust JSON support” mean?

PostgreSQL supports a special column type: json and jsonb, which lets you store structured JSON data directly in your database — like hashes or objects.

Why it matters:

  • You can store dynamic data without needing to change your schema.
  • You can query inside the JSON using SQL (->, ->>, @>, etc.).
  • You can index parts of the JSON — for speed.

🔧 Example:

You have a products table with a specs column that holds tech specs in JSON:

specs = {
  "color": "black",
  "brand": "Libas",
  "dimensions": {"chest": "34", "waist": "30", "shoulder": "13.5"}
}

You can query like:

SELECT * FROM products WHERE specs->>'color' = 'black';

Or check if the JSON contains a value:

SELECT * FROM products WHERE specs @> '{"brand": "Libas"}';

You can even index specs->>'color' to make these queries fast.


💬 What does “full Postgres-style indexing” mean?

PostgreSQL supports a wide variety of powerful indexing options, which improve query performance and flexibility.

⚙️ Types of Indexes PostgreSQL supports:

Index TypeUse Case
B-TreeDefault; used for most equality and range searches
GIN (Generalized Inverted Index)Fast indexing for JSON, arrays, full-text search
Partial IndexesIndex only part of the data (e.g., WHERE active = true)
Expression IndexesIndex a function or expression (e.g., LOWER(email))
Covering Indexes (INCLUDE)Fetch data directly from the index, avoiding table reads
  • B-Tree Indexes: B-tree indexes are more suitable for single-value columns.
  • When to Use GIN Indexes: When you frequently search for specific elements within arrays, JSON documents, or other composite data types.
  • Example for GIN Indexes: Imagine you have a table with a JSONB column containing document metadata. A GIN index on this column would allow you to quickly find all documents that have a specific author or belong to a particular category. 

Why does this matter for our shopping app?

  • We can store and filter products with dynamic specs (e.g., kurtas, shorts, pants) without new columns.
  • Full-text search on product names/descriptions.
  • Fast filters: color = 'red' AND brand = 'Libas' even if those are stored in JSON.
  • Index custom expressions like LOWER(email) for case-insensitive login.

💬 What are Common Table Expressions (CTEs)?

CTEs are temporary result sets you can reference within a SQL query — like defining a mini subquery that makes complex SQL easier to read and write.

WITH recent_orders AS (
  SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days'
)
SELECT * FROM recent_orders WHERE total > 100;

  • Breaking complex queries into readable parts.
  • Re-using result sets without repeating subqueries.
In Rails (via with from gems like scenic or with_cte):
Order
  .with(recent_orders: Order.where('created_at > ?', 7.days.ago))
  .from('recent_orders')
  .where('total > ?', 100)

💬 What are Window Functions?

Window functions perform calculations across rows related to the current row — unlike aggregate functions, they don’t group results into one row.

🔧 Example: Rank users by their score within each team:
SELECT
  user_id,
  team_id,
  score,
  RANK() OVER (PARTITION BY team_id ORDER BY score DESC) AS rank
FROM users;
Use cases:
  • Ranking rows (like leaderboards).
  • Running totals or moving averages.
  • Calculating differences between rows (e.g. “How much did this order increase from the last?”).
🛤 In Rails:

Window functions are available through raw SQL or Arel. Here’s a basic example:

User
  .select("user_id, team_id, score, RANK() OVER (PARTITION BY team_id ORDER BY score DESC) AS rank")

CTEs and Window functions are fully supported in PostgreSQL, making it the go-to DB for any Rails 8 app that needs advanced querying.

JSONB Support

JSONB stands for “JSON Binary” and is a binary representation of JSON data that allows for efficient storage and retrieval of complex data structures.

This can be useful when you have data that doesn’t fit neatly into traditional relational database tables, such as nested or variable-length data structures.

Absolutely — storing JSON in a relational database (like PostgreSQL) can be super powerful when used wisely. It gives you schema flexibility without abandoning the structure and power of SQL. Here are real-world use cases for using JSON columns in relational databases:

Here are real-world use cases for using JSON columns in relational databases:

🔧 1. Flexible Metadata / Extra Attributes

Let users store arbitrary attributes that don’t require schema changes every time.

Use case: Product variants, custom fields

t.jsonb :metadata

{
  "color": "red",
  "size": "XL",
  "material": "cotton"
}

=> Good when:

  • You can’t predict all the attributes users will need.
  • You don’t want to create dozens of nullable columns.

🎛️ 2. Storing Settings or Preferences

User or app settings that vary a lot.

Use case: Notification preferences, UI layout, feature toggles

{
  "email": true,
  "sms": false,
  "theme": "dark"
}

=> Easy to store and retrieve as a blob without complex joins.

🌐 3. API Response Caching

Store external API responses for caching or auditing.

Use case: Storing Stripe, GitHub, or weather API responses.

t.jsonb :api_response

=> Avoids having to map every response field into a column.

📦 4. Storing Logs or Events

Use case: Audit trails, system logs, user events

{
  "action": "login",
  "timestamp": "2025-04-18T10:15:00Z",
  "ip": "123.45.67.89"
}

=> Great for capturing varied data over time without a rigid schema.

📊 6. Embedded Mini-Structures

Use case: A form builder app storing user-created forms and fields.

{
  "fields": [
    { "type": "text", "label": "Name", "required": true },
    { "type": "email", "label": "Email", "required": false }
  ]
}

=> When each row can have nested, structured data — almost like a mini-document.

🕹️ 7. Device or Browser Info (User Agents)

Use case: Analytics, device fingerprinting

{
  "browser": "Safari",
  "os": "macOS",
  "version": "17.3"
}

=> You don’t need to normalize or query this often — perfect for JSON.


JSON vs JSONB in PostgreSQL

Use jsonb over json unless you need to preserve order or whitespace.

  • jsonb is binary format → faster and indexable
  • You can do fancy stuff like:
SELECT * FROM users WHERE preferences ->> 'theme' = 'dark';

Or in Rails:

User.where("preferences ->> 'theme' = ?", 'dark')

store and store_accessor

They let you treat JSON or text-based hash columns like structured data, so you can access fields as if they were real database columns.

🔹 store

  • Used to declare a serialized store (usually a jsonb, json, or text column) on your model.
  • Works best with key/value stores.

👉 Example:

Let’s say your users table has a settings column of type jsonb:

# migration
add_column :users, :settings, :jsonb, default: {}

Now in your model:

class User < ApplicationRecord
  store :settings, accessors: [:theme, :notifications], coder: JSON
end

You can now do this:

user.theme = "dark"
user.notifications = true
user.save

user.settings
# => { "theme" => "dark", "notifications" => true }

🔹 store_accessor

A lightweight version that only declares attribute accessors for keys inside a JSON column. Doesn’t include serialization logic — so you usually use it with a json/jsonb/text column that already works as a Hash.

👉 Example:

class User < ApplicationRecord
  store_accessor :settings, :theme, :notifications
end

This gives you:

  • user.theme, user.theme=
  • user.notifications, user.notifications=
🤔 When to Use Each?
FeatureWhen to Use
storeWhen you need both serialization and accessors
store_accessorWhen your column is already serialized (jsonb, etc.)

If you’re using PostgreSQL with jsonb columns — it’s more common to just use store_accessor.

Querying JSON Fields
User.where("settings ->> 'theme' = ?", "dark")

Or if you’re using store_accessor:

User.where(theme: "dark")

💡 But remember: you’ll only be able to query these fields efficiently if you’re using jsonb + proper indexes.


🔥 Conclusion:

  • PostgreSQL can store, search, and index inside JSON fields natively.
  • This lets you keep your schema flexible and your queries fast.
  • Combined with its advanced indexing, it’s ideal for a modern e-commerce app with dynamic product attributes, filtering, and searching.

To install and set up PostgreSQL on macOS, you have a few options. The most common and cleanest method is using Homebrew. Here’s a step-by-step guide:

Setup 🛠 Rails 8 App – Part 9: Setup ⚙️ CI/CD with GitHub Actions | Run Test Cases via VS Code Co-pilot

Switching to a feature-branch workflow with pull requests is a great move for team collaboration, code review, and better CI/CD practices. Here’s how you can transition our Rails 8 app to a proper CI/CD pipeline using GitHub and GitHub Actions.

🔄 Workflow Change: Feature Branch + Pull Request

1. Create a new branch for each feature/task:
git checkout -b feature/feature-name
2. Push it to GitHub:
git push origin feature/feature-name
3. Open a Pull Request on GitHub from feature/feature-name to main.
4. Enable branch protection (optional but recommended):

Note: You can set up branch protection rules in GitHub for free only on public repositories.

About protected branches

You can protect important branches by setting branch protection rules, which define whether collaborators can delete or force push to the branch and set requirements for any pushes to the branch, such as passing status checks or a linear commit history.

You can create a branch protection rule in a repository for a specific branch, all branches, or any branch that matches a name pattern you specify with fnmatch syntax. For example, to protect any branches containing the word release, you can create a branch rule for *release*

  • Go to your repo → SettingsBranches → Protect main.
  • Require pull request reviews before merging.
  • Require status checks to pass before merging (CI tests).

Check the link in your github account: https://github.com/<user-name>/<repo-name>/settings/branch_protection_rules/new

For creating the branch protection rules, you need to take the github business account OR Move your work into an organization (https://github.com/account/organizations/new).

GitHub Actions

Basically github actions allow us to run some actions (ex: testing the code) if an event occurs during the code changes/commit/push (it mostly related to a branch).

Our Goal: When we push to a feature branch test the code before merging it to the main branch so that we can ensure nothing is broken before going the code into live.

You can try the VS Code plugin for helping the Github Actions workflow (best for auto-complete the data we needed and auto-populate the env variables etc from our github account):

Sign in using your github account and grant access to the public repositories.

If you try to push to main branch, you will find the following error:

remote: error: GH006: Protected branch update failed for refs/heads/main.
remote:
remote: - Changes must be made through a pull request.
remote:
remote: - Cannot change this locked branch
To github.com:<username>/<project>.git
 ! [remote rejected] main -> main (protected branch hook declined)

We will be finishing Database and all other setup for our Web Application before starting CI/CD setup.

For the next part of CI/CD configuration check the post: https://railsdrop.com/2025/05/06/rails-8-ci-cd-setup-with-github-actions/

Let’s Start to Use VS Code Co-pilot For Test Creation/Execution

Test cases are Important for CI/CD setup. Our main focus will be running Rails test cases when integrating CI.

  • Generate Test using Co-pilot From Controller
  • Co-pilot Creates Tests
  • Co-pilot run Tests

  • Use Co-pilot to Fix Test Failures
  • Test Results: Pending Migrations
  • Test Success: After Migration
  • VS Code: Check ruby Lint Symbol for details
  • VS Code try to run Tests: Rubocop Path Issue
  • Fixed Rubocop Issue: All Test passes

Cursor ai 🤖 Overview: Install, Usage, Advantages and Best Practices

Cursor AI is an innovative AI-powered code editor developed by Anysphere Inc., designed to enhance developer productivity by integrating advanced artificial intelligence features directly into the coding environment. It is a fork of Visual Studio Code with additional AI features like code generation, smart rewrites, and codebase queries. (Wikipedia)


What is Cursor AI?

Cursor AI is a smart code editor that assists developers in writing, debugging, and optimizing code. It offers AI-powered suggestions, real-time error detection, and the ability to interact with existing code through natural language prompts. This makes it a valuable tool for both experienced developers and newcomers to programming.(Reddit)


The Evolution of Cursor AI

Cursor AI was founded in early 2022 by four MIT graduates: Michael Truell, Sualeh Asif, Arvid Lunnemark, and Aman Sanger. Initially focusing on mechanical engineering tools, the team pivoted to programming after identifying a larger opportunity and aligning with their expertise. (Medium, lennysnewsletter.com)

Launched in 2023, Cursor AI quickly gained traction, reaching $100 million in annual recurring revenue within 12 months, making it one of the fastest-growing SaaS startups. By April 2025, the company achieved a $9 billion valuation following a $900 million funding round. (productmarketfit.tech, Financial Times)


Installing Cursor AI on macOS

To install Cursor AI on your MacBook:

  1. Download: Visit the Cursor Downloads page and select the appropriate version for your Mac (Universal, Arm64, or x64).(Cursor)
  2. Install: Run the downloaded installer and follow the on-screen instructions.
  3. Launch: After installation, open Cursor from the Applications folder.(apidog)
  4. Setup: On first launch, you’ll be prompted to configure settings to get started. (Cursor)

For a visual guide, you can refer to this tutorial:

How to Install Cursor AI on macOS ↗️

Useful shortcuts: cmd + L, cmd + K


The Importance of Cursor AI in Modern Coding

In today’s fast-paced development environment, tools that enhance productivity are invaluable. Cursor AI stands out by integrating AI directly into the coding process, allowing developers to:(Reddit)

  • Generate code snippets based on natural language prompts.
  • Refactor and debug code efficiently.(Rapid Dev)
  • Understand and navigate complex codebases with ease.

This integration reduces the cognitive load on developers, allowing them to focus on higher-level problem-solving. (fine.dev)


Software Development With and Without Cursor AI

Without Cursor AI:

  • Manual coding and debugging.(Builder.io)
  • Time-consuming code reviews.(YouTube)
  • Limited assistance in understanding unfamiliar codebases.(Reddit)

With Cursor AI:

  • Automated code generation and suggestions.
  • Faster identification and resolution of bugs.
  • Enhanced collaboration through AI-assisted code reviews.

The integration of AI into the development process streamlines workflows and accelerates project timelines.


Advantages and Disadvantages of Using Cursor AI

Advantages:

  • Increased productivity through AI-assisted coding.
  • Improved code quality with real-time suggestions.
  • Enhanced learning for new developers.

Disadvantages:

  • Steep learning curve for those unfamiliar with AI tools.(Medium)
  • Potential over-reliance on AI, leading to reduced manual coding skills.(Financial Times)
  • Challenges in handling large-scale, complex projects. (docs.kanaries.net)

Impact on Experienced vs. New Developers

New Developers:

  • Benefit from real-time feedback and suggestions.
  • Accelerated learning curve.(productmarketfit.tech)
  • Ability to build applications with minimal prior experience.

Experienced Developers:

  • Enhanced efficiency in coding and debugging.
  • Ability to focus on complex problem-solving tasks.
  • Potential to mentor juniors more effectively using AI tools.

Best Practices for Experienced Developers Using Cursor AI

Steps to Follow:

  1. Define Clear Objectives: Start with a clear understanding of the task at hand.
  2. Use AI for Repetitive Tasks: Leverage Cursor AI for boilerplate code and routine functions.
  3. Review AI Suggestions: Always review and understand AI-generated code before integration.(Rapid Dev)
  4. Integrate with Testing: Use test-driven development to ensure code reliability. (Builder.io)

What Not to Do:

  • Avoid blind reliance on AI suggestions without understanding the underlying code.
  • Do not neglect code reviews and testing.
  • Refrain from using AI for tasks that require deep domain expertise without proper oversight.

Happy AI Coding! 🚀

Profiling 📊 Ruby on Rails 8 Applications: Essential Tools and Techniques

Introduction

Performance optimization is critical for delivering fast, responsive Rails applications. This comprehensive guide covers the most important profiling tools you should implement in your Rails 8 application, complete with setup instructions and practical examples.

Why Profiling Matters

Before diving into tools, let’s understand why profiling is essential:

  1. Identify bottlenecks: Pinpoint exactly which parts of your application are slowing things down
  2. Optimize resource usage: Reduce memory consumption and CPU usage
  3. Improve user experience: Faster response times lead to happier users
  4. Reduce infrastructure costs: Efficient applications require fewer server resources

Essential Profiling Tools for Rails 8

1. Rack MiniProfiler

What it does: Provides real-time profiling of your application’s performance directly in your browser.

Why it’s important: It’s the quickest way to see performance metrics without leaving your development environment.

Installation:

# Gemfile
gem 'rack-mini-profiler', group: :development

Usage example:
After installation, it automatically appears in your browser’s corner showing:

  • SQL query times
  • Ruby execution time
  • Memory allocation
  • Flamegraphs (with additional setup)

Advantages:

  • No configuration needed for basic setup
  • Shows N+1 query warnings
  • Integrates with Rails out of the box

GitHubhttps://github.com/MiniProfiler/rack-mini-profiler
Documentationhttps://miniprofiler.com/

2. Bullet

What it does: Detects N+1 queries, unused eager loading, and missing counter caches.

Why it’s important: N+1 queries are among the most common performance issues in Rails applications.

Installation:

# Gemfile
gem 'bullet', group: :development

Configuration:

# config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end

Example output:

GET /posts
USE eager loading detected
  Post => [:comments]
  Add to your query: Post.includes([:comments])

Advantages:

  • Catches common ORM performance issues early
  • Provides specific recommendations for fixes
  • Works across all environments

GitHubhttps://github.com/flyerhzm/bullet
Documentationhttps://github.com/flyerhzm/bullet/blob/master/README.md

3. Ruby Prof (and StackProf)

What it does: Low-level Ruby code profiler that shows exactly where time is being spent.

Why it’s important: When you need deep insight into method-level performance characteristics.

Installation:

# Gemfile
gem 'ruby-prof', group: :development
gem 'stackprof', group: :development

Usage example:

# In your controller or service object
result = RubyProf.profile do
  # Code you want to profile
end

printer = RubyProf::GraphPrinter.new(result)
printer.print(STDOUT, {})

For StackProf:

StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump') do
  # Code to profile
end

Advantages:

  • Method-level granularity
  • Multiple output formats (call graphs, flamegraphs)
  • StackProf is sampling-based so has lower overhead

GitHubhttps://github.com/ruby-prof/ruby-prof
Documentationhttps://github.com/ruby-prof/ruby-prof/blob/master/README.md

StackProf Alternative:
GitHubhttps://github.com/tmm1/stackprof
Documentationhttps://github.com/tmm1/stackprof/blob/master/README.md

4. Memory Profiler

What it does: Tracks memory allocations and helps identify memory bloat.

Why it’s important: Memory issues can lead to slow performance and even crashes.

Installation:

# Gemfile
gem 'memory_profiler', group: :development

Usage example:

report = MemoryProfiler.report do
  # Code to profile
end

report.pretty_print(to_file: 'memory_report.txt')

Advantages:

  • Shows allocated objects by class and location
  • Tracks retained memory after GC
  • Helps find memory leaks

GitHubhttps://github.com/SamSaffron/memory_profiler
Documentationhttps://github.com/SamSaffron/memory_profiler/blob/master/README.md

5. Skylight

What it does: Production-grade application performance monitoring (APM).

Why it’s important: Understanding real-world performance characteristics is different from development profiling.

Installation:

# Gemfile
gem 'skylight'

Configuration:

# config/skylight.yml
production:
  authentication: [YOUR_AUTH_TOKEN]

Advantages:

  • Low-overhead production profiling
  • Endpoint-level performance breakdowns
  • Database query analysis
  • Exception tracking

Websitehttps://www.skylight.io
Documentationhttps://docs.skylight.io
GitHubhttps://github.com/skylightio/skylight-ruby

6. AppSignal

What it does: Full-stack performance monitoring and error tracking.

Why it’s important: Provides comprehensive insights across your entire application stack.

Installation:

# Gemfile
gem 'appsignal'

Then run:

bundle exec appsignal install YOUR_PUSH_API_KEY

Advantages:

  • Error tracking alongside performance
  • Host metrics integration
  • Background job monitoring
  • Magic Dashboard for quick insights

Websitehttps://appsignal.com
Documentationhttps://docs.appsignal.com/ruby
GitHubhttps://github.com/appsignal/appsignal-ruby

7. Derailed Benchmarks

What it does: Suite of benchmarks and performance tests for your application.

Why it’s important: Helps catch performance regressions before they hit production.

Installation:

# Gemfile
group :development, :test do
  gem 'derailed_benchmarks'
end

Usage examples:

# Memory usage at boot
bundle exec derailed bundle:mem

# Performance per route
bundle exec derailed exec perf:routes

Advantages:

  • CI-friendly performance testing
  • Memory usage analysis
  • Route-based performance testing

GitHubhttps://github.com/schneems/derailed_benchmarks
Documentationhttps://github.com/schneems/derailed_benchmarks/blob/master/README.md

8. Flamegraph Generation

What it does: Visual representation of where time is being spent in your application.

Why it’s important: Provides an intuitive way to understand call stacks and hot paths.

Installation:

# Gemfile
gem 'flamegraph'
gem 'stackprof' # if not already installed

Usage example:

Flamegraph.generate('flamegraph.html') do
  # Code to profile
end

Advantages:

  • Visual representation of performance
  • Easy to spot hot paths
  • Interactive exploration

GitHubhttps://github.com/SamSaffron/flamegraph
Documentationhttp://samsaffron.github.io/flamegraph/rails-startup.html

Additional Helpful Tools 🔧

9. Benchmark-ips

Benchmark-ips (iterations per second) is a superior benchmarking tool compared to Ruby’s standard Benchmark library. It provides:

  1. Iterations-per-second measurement – More intuitive than raw time measurements
  2. Statistical analysis – Shows standard deviation between runs
  3. Comparison mode – Easily compare different implementations
  4. Warmup phase – Accounts for JIT and cache warming effects

Benchmark-ips solves these problems and is particularly valuable for:

  • Comparing algorithm implementations
  • Testing performance optimizations
  • Benchmarking gem alternatives
  • Validating performance-critical code

GitHubhttps://github.com/evanphx/benchmark-ips
Documentationhttps://github.com/evanphx/benchmark-ips/blob/master/README.md

Installation
# Gemfile
gem 'benchmark-ips', group: :development
Basic Usage:
require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("addition") { 1 + 2 }
  x.report("addition with to_s") { (1 + 2).to_s }
  x.compare!
end
Advanced Features:
Benchmark.ips do |x|
  x.time = 5 # Run each benchmark for 5 seconds
  x.warmup = 2 # Warmup time of 2 seconds
  
  x.report("Array#each") { [1,2,3].each { |i| i * i } }
  x.report("Array#map") { [1,2,3].map { |i| i * i } }
  
  # Add custom statistics
  x.config(stats: :bootstrap, confidence: 95)
  
  x.compare!
end
# Memory measurement
require 'benchmark/memory'

Benchmark.memory do |x|
  x.report("method1") { ... }
  x.report("method2") { ... }
  x.compare!
end

# Disable GC for more consistent results
Benchmark.ips do |x|
  x.config(time: 5, warmup: 2, suite: GCSuite.new)
end
Sample Output:
Warming up --------------------------------------
            addition    281.899k i/100ms
  addition with to_s    261.831k i/100ms
Calculating -------------------------------------
            addition      8.614M (± 1.2%) i/s -     43.214M in   5.015800s
  addition with to_s      7.017M (± 1.8%) i/s -     35.347M in   5.038446s

Comparison:
            addition:  8613594.0 i/s
  addition with to_s:  7016953.3 i/s - 1.23x slower

Key Advantages

  1. Accurate comparisons with statistical significance
  2. Warmup phase eliminates JIT/caching distortions
  3. Memory measurements available through extensions
  4. Customizable reporting with various statistics options

10. Rails Performance (Dashboard)

What is Rails Performance?

Rails Performance is a self-hosted alternative to New Relic/Skylight that provides:

  1. Request performance tracking
  2. Background job monitoring
  3. Slowest endpoints identification
  4. Error tracking
  5. Custom event monitoring
Why It’s Important

For teams that:

  • Can’t use commercial SaaS solutions
  • Need to keep performance data in-house
  • Want historical performance tracking
  • Need simple setup without complex infrastructure

GitHubhttps://github.com/igorkasyanchuk/rails_performance
Documentationhttps://github.com/igorkasyanchuk/rails_performance/blob/master/README.md

Installation
# Gemfile
gem 'rails_performance', group: :development

Then run:

rails g rails_performance:install
rake db:migrate
Configuration
# config/initializers/rails_performance.rb
RailsPerformance.setup do |config|
  config.redis = Redis.new # optional, will use Rails.cache otherwise
  config.duration = 4.hours # store requests for 4 hours
  config.enabled = Rails.env.production?
  config.http_basic_authentication_enabled = true
  config.http_basic_authentication_user_name = 'admin'
  config.http_basic_authentication_password = 'password'
end
Accessing the Dashboard:

After installation, access the dashboard at:

http://localhost:3000/rails/performance

Custom Tracking:

# Track custom events
RailsPerformance.trace("custom_event", tags: { type: "import" }) do
  # Your code here
end

# Track background jobs
class MyJob < ApplicationJob
  around_perform do |job, block|
    RailsPerformance.trace(job.class.name, tags: job.arguments) do
      block.call
    end
  end
end
# Add custom fields to requests
RailsPerformance.attach_extra_payload do |payload|
  payload[:user_id] = current_user.id if current_user
end

# Track slow queries
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  if event.duration > 100 # ms
    RailsPerformance.trace("slow_query", payload: {
      sql: event.payload[:sql],
      duration: event.duration
    })
  end
end
Sample Dashboard Views:
  1. Requests Overview:
    • Average response time
    • Requests per minute
    • Slowest actions
  2. Detailed Request View:
    • SQL queries breakdown
    • View rendering time
    • Memory allocation
  3. Background Jobs:
    • Job execution time
    • Failures
    • Queue times
Key Advantages
  1. Self-hosted solution – No data leaves your infrastructure
  2. Simple setup – No complex dependencies
  3. Historical data – Track performance over time
  4. Custom events – Track any application events
  5. Background jobs – Full visibility into async processes

Implementing a Complete Profiling Strategy

For a comprehensive approach, combine these tools at different stages:

  1. Development:
  • Rack MiniProfiler (always on)
  • Bullet (catch N+1s early)
  • RubyProf/StackProf (for deep dives)
  1. CI Pipeline:
  • Derailed Benchmarks
  • Memory tests
  1. Production:
  • Skylight or AppSignal
  • Error tracking with performance context

Sample Rails 8 Configuration

Here’s how to set up a complete profiling environment in a new Rails 8 app:

# Gemfile

# Development profiling
group :development do
  # Basic profiling
  gem 'rack-mini-profiler'
  gem 'bullet'
  
  # Deep profiling
  gem 'ruby-prof'
  gem 'stackprof'
  gem 'memory_profiler'
  gem 'flamegraph'
  
  # Benchmarking
  gem 'derailed_benchmarks', require: false
  gem 'benchmark-ips'
  
  # Dashboard
  gem 'rails_performance'
end

# Production monitoring (choose one)
group :production do
  gem 'skylight'
  # or
  gem 'appsignal'
  # or
  gem 'newrelic_rpm' # Alternative option
end

Then create an initializer for development profiling:

# config/initializers/profiling.rb
if Rails.env.development?
  require 'rack-mini-profiler'
  Rack::MiniProfilerRails.initialize!(Rails.application)

  Rails.application.config.after_initialize do
    Bullet.enable = true
    Bullet.alert = true
    Bullet.bullet_logger = true
    Bullet.rails_logger = true
  end
end

Conclusion

Profiling your Rails 8 application shouldn’t be an afterthought. By implementing these tools throughout your development lifecycle, you’ll catch performance issues early, maintain a fast application, and provide better user experiences.

Remember:

  • Use development tools like MiniProfiler and Bullet daily
  • Run deeper profiles with RubyProf before optimization work
  • Monitor production with Skylight or AppSignal
  • Establish performance benchmarks with Derailed

With this toolkit, you’ll be well-equipped to build and maintain high-performance Rails 8 applications.

Enjoy Rails! 🚀

Setup 🛠 Rails 8 App – Part 8: Debugbar – Apply performance 📈 optimization

1. Integrate pagy for pagination

Why it’s the great choice:

  • Super fast and lightweight (~300x faster than Kaminari or WillPaginate).
  • No dependencies on Active Record or view helpers.
  • Very customizable and modular (can do Bootstrap/Tailwind/semantic UI integrations).
  • Supports metadata, responsive pagination, overflow handling, infinite scrolling, and JSON API pagination.
# Gemfile
# The Best Pagination Ruby Gem [https://ddnexus.github.io/pagy/]
gem "pagy", "~> 9.3" # omit patch digit

bundle install
Example Usage in Controller:
include Pagy::Backend

def index
  @pagy, @products = pagy(Product.all)
end

In Product Helper / Application Helper:
include Pagy::Frontend
In the View (ERB or HAML):
<%= pagy_nav(@pagy) %>
Add an initializer file

Download the file from: https://ddnexus.github.io/pagy/quick-start/

https://ddnexus.github.io/pagy/gem/config/pagy.rb

and save it into the config/initializers directory. Uncomment limit and size options.

Tailwind Support:
# In an initializer (e.g., config/initializers/pagy.rb)
Pagy::DEFAULT[:limit]       = 20                    # default
Pagy::DEFAULT[:size]        = 7                     # default
# Better user experience handled automatically
require "pagy/extras/overflow"
Pagy::DEFAULT[:overflow] = :last_page

I am getting a load error when I want tailwind css to apply to my views:

LoadError: cannot load such file -- pagy/extras/tailwind (LoadError)

Ahh it’s not supporting Tailwind CSS, and there is no tailwind file found in the Gem too!

Hmm..😟 Check below:

We can try to include the css manually, check: https://ddnexus.github.io/pagy/docs/api/stylesheets/#pagy-tailwind-css

Create a file pagy.tailwind.css and add the following:

.pagy {
    @apply flex space-x-1 font-semibold text-sm text-gray-500;
    a:not(.gap) {
      @apply block rounded-lg px-3 py-1 bg-gray-200;
      &:hover {
        @apply bg-gray-300;
      }
      &:not([href]) { /* disabled links */
        @apply text-gray-300 bg-gray-100 cursor-default;
      }
      &.current {
        @apply text-white bg-gray-400;
      }
    }
    label {
      @apply inline-block whitespace-nowrap bg-gray-200 rounded-lg px-3 py-0.5;
      input {
        @apply bg-gray-100 border-none rounded-md;
      }
    }
  }

Modify app/assets/tailwind/application.css :

@import "tailwindcss";
@import "./pagy.tailwind.css";

Restart your server and you got it!

Testing performance

You can see that in the query Tab in Debugbar, select * from products query has been replaced with limit query. But this is not the case where you go through the entire thousand hundreds of products, for example searching. We can think of view caching and SQL indexing for such a situation.

to be continued.. 🚀

Setup 🛠 Rails 8 App – Part 7: Mastering Debugbar 👾 for Rails Performance Optimization

As Rails developers, we’ve all been there – your application starts slowing down as data grows, pages take longer to load, and memory usage spikes. Before you blame Rails itself or consider rewriting your entire application, you should profile your app to understand what’s really happening behind the scenes.

Most of the time, the issue lies in how the app is written: unnecessary SQL queries, excessive object allocations, or inefficient code patterns. Before you think about rewriting your app or switching frameworks, profile it.

That’s where Rails Debugbar shines— It helps you identify bottlenecks like slow database queries, excessive object allocations, and memory leaks – all from a convenient toolbar at the bottom of your development environment.


🤔 What is Rails Debugbar?

Rails Debugbar is a browser-integrated dev tool that adds a neat, powerful panel at the bottom of your app in development. It helps you answer questions like:

  • How long is a request taking?
  • How many SQL queries are being executed?
  • How many Ruby objects are being allocated?
  • Which parts of my code are slow?

It’s like a surgeon’s X-ray for your app—giving you visibility into internals without needing to dig into logs or guess. Get a better understanding of your application performance and behavior (SQL queries, jobs, cache, routes, logs, etc)


⚙️ Installation & Setup (Rails 8)

Prerequisites

  • Ruby on Rails 5.2+ (works perfectly with Rails 8)
  • A Ruby version supported by your Rails version

1. Add it to your Gemfile:

group :development do
  gem 'debugbar'
end

Then run:

bundle install

2. Add the Debugbar layout helpers in your application layout:

In app/views/layouts/application.html.erb, just before the closing </head> and </body> tags:

<%= debugbar_head if defined?(Debugbar) %>
...
<%= debugbar_body if defined?(Debugbar) %>

That’s it! When you restart your server, you’ll see a sleek Debugbar docked at the bottom of the screen.

You can see ActionCable interacting with debugbar_channel in logs:

[ActionCable] Broadcasting to debugbar_channel: [{id: "xxxx-xxxx-xxxx-xxxx", meta: {controller: "ProductsController", action: "show", params: {"controller" => "products", "action" => "show", "id" => "3"}, format: :html, method: "GET", path: "/products/3", status: 200, view_runtime: 10.606000004219823, db_runtime: 0.44599999819...

23:47:17 web.1  | Debugbar::DebugbarChannel transmitting [{"id" => "xxxx-xxxx-xxxx-xxxx", "meta" => {"controller" => "ProductsController", "action" => "show", "params" => {"controller" => "products", "action" => "show", "id" => "3"}, "format" => "html", "method" => "GET", "path" => "/products/3", "status" => 200, "view_runtime" => 10.6... (via streamed from debugbar_channel)

23:47:17 web.1  | Debugbar::DebugbarChannel#receive({"ids" => ["xxxx-xxxx-xxxx-xxxx"]})
23:47:17 web.1  | [ActionCable] Broadcasting to debugbar_channel: []

23:47:17 web.1  | Debugbar::DebugbarChannel transmitting [] (via streamed from debugbar_channel)

📚 Official links for reference:


🔍 Exploring the Debugbar Tabs

Rails Debugbar includes several tabs. Let’s go through the most useful ones—with real-world examples of how to interpret and improve performance using the data.

1. Queries Tab

This tab shows all SQL queries executed during the request, including their duration in milliseconds.

Example:

You see this in the Queries tab:

SELECT * FROM users WHERE email = 'test@example.com'  (15ms)
SELECT * FROM products WHERE user_id = 1                 (20ms)
SELECT * FROM comments WHERE product_id IN (...)         (150ms)

You realize:

  • The third query is taking 10x more time.
  • You’re not using eager loading, and it’s triggering N+1 queries.

How to Fix:

Update your controller:

@products = Product.includes(:comments).where(user_id: 1)

This loads the comments in a single query, reducing load time and object allocation.


2. Timeline Tab

Gives you a timeline breakdown of how long each part of the request takes—view rendering, database, middleware, etc.

Example:

You notice that rendering a partial takes 120ms, way more than expected.

<%= render 'shared/sidebar' %>

How to Fix:

Check the partial for:

  • Heavy loops or database calls
  • Uncached helper methods

Move the partial to use a fragment cache:

<% cache('sidebar') do %>
  <%= render 'shared/sidebar' %>
<% end %>

Another Example Problem:
If you notice view rendering takes 800ms for a simple page.

Solution:
Investigate partials being rendered. You might be:

  • Rendering unnecessary partials
  • Using complex helpers in views
  • Need to implement caching
# Before
<%= render @products %> # Renders _product.html.erb for each

# After (with caching)
<% @products.each do |product| %>
  <% cache product do %>
    <%= render product %>
  <% end %>
<% end %>

3. Memory Tab

Tracks memory usage and object allocations per request.

Example:

You load a dashboard page and see 25,000+ objects allocated. Yikes.

Dig into the view and see:

<% User.all.each do |user| %>
  ...
<% end %>

That’s loading all users into memory.

How to Fix:

Use pagination or lazy loading:

@users = User.page(params[:page]).per(20)

Now the object count drops dramatically.


4. Environment & Request Info

See request parameters, environment variables, session data, and headers.

Example:

You’re debugging an API endpoint and want to confirm the incoming headers or params—Debugbar shows them neatly in this tab.

It can help identify:

  • Wrong content-type headers
  • CSRF issues
  • Auth headers or missing cookies

💡 Debugbar Best Practices

  • Use it early: Don’t wait until your app is slow—profile as you build.
  • Watch out for hidden N+1 in associations, partials, or background jobs.
  • Keep an eye on object counts to reduce memory pressure in production.
  • Use fragment and Russian doll caching where needed, based on render timelines.
  • Regularly review slow pages with Debugbar open—it’s a development-time lifesaver.

💭 Final Thoughts

Rails Debugbar offers an easy, visual way to profile and optimize your Rails 8 app. Whether you’re debugging a slow page, inspecting a query storm, or chasing down memory leaks, this tool gives you insight without friction.

So before you overhaul your architecture or blame Rails, fire up Debugbar—and fix the real issues.

to be modified..  🚀

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:

Git best practices 🔀: Git HEAD, useful commands, commit message

What is HEAD in Git?

In Git, HEAD is a pointer to the latest commit in the current branch. It tells Git which commit you’re currently working on.

Types of HEAD States:

  • Normal (Attached HEAD)
    When HEAD points to the latest commit in a branch, it’s called an attached HEAD.
# show commits in single line with message
git log --oneline --graph

* 07cf493 (HEAD -> main, origin/main) feat: Implement tailwind css to product pages
* c3ee7d4 feat: Add images to products
* e342472 feat: Install tailwind css
* 40fc222 first commit
  • Detached HEAD
    If you check out a specific commit (not a branch), HEAD becomes detached.
✗ git checkout c242462
# undo detached HEAD
✗ git switch -

# or go back to main
✗ git checkout main

Common HEAD Uses in Commands

  1. Reset Last Commit (Undo Latest Commit, Keep Changes)
git reset HEAD~1

HEAD~1 means “one commit before HEAD” (previous commit). This unstages the latest commit but keeps changes.

2. Unstage a Staged File

git reset HEAD filename

Removes filename from staged state but keeps changes.

3. Move HEAD to a Different Commit (Soft Reset)

git reset --soft HEAD~2

Moves HEAD back two commits but keeps all changes staged.

4. Hard Reset (Undo Everything, No Recovery)

git reset --hard HEAD~1

Moves HEAD one commit back and deletes all changes.

5. View HEAD Commit Hash

git rev-parse HEAD

Shows the exact commit hash HEAD is pointing to.

HEAD is simply Git’s way of tracking where you are in the commit history. It allows you to navigate, reset, and control commits efficiently.

Git useful commands ✍

# compare 2 commits to see what is changed from one to another
git diff e144462 c4ed9d4

# To ignore all changes in the working directory and reset it to match the latest commit
git restore .

# If You Also Want to Remove Untracked Files
git clean -fd

# completely wipe all changes in the working directory
git restore . && git clean -fd

# move uncommitted file into a special area
git stash

# get back the unstaged files into working tree
git stash pop

# other commands
git stash save "Add tailwind to product show"
git stash list
git stash pop stash@{2}
git stash show
git stash apply

# If you decide you no longer need a particular stash, you can delete it with git stash drop
git stash drop stash@{1}

# or you can delete all of your stashes with:
git stash clear

To get the new changes from the remote repo to your local repo do the following command:

git fetch

but remember this does not update your working directory. git fetch allows you to check the incoming commits using git log and you can merge those changes to your current branch using git merge.

git fetch + git merge = git pull

If all goes well without any code conflict with git pull your code is updated to Local Repo and Working directory. (Your branch is Fast-Forwarded)

 When Do Stash and Pull Interact?

The connection arises in real-world workflows when you need to pull remote changes but you have local uncommitted work:

Scenario:

  1. You’re working on a branch (main) with uncommitted changes.
  2. You need to pull updates from the remote (git pull), but Git blocks this if your working directory is dirty (has uncommitted changes).
  3. To resolve this, you:
    • Stash your changes (git stash) → clears the working directory.
    • Pull the updates (git pull).
    • Reapply your stash (git stash pop) to merge your changes with the newly pulled updates.

Git commit message:Best practices 🚀

1. Follow the Conventional Format

A well-structured commit message consists of:

  • A short summary (50 characters max)
  • A blank line
  • A detailed description (if necessary, up to 72 characters per line)

Example:

git commit -m "feat: Add user authentication with Devise" -m "Implemented Devise for user authentication, including:
- User sign up, login, and logout
- Email confirmation and password recovery
- Integration with Turbo Streams

Closes #42"

feat: Add user authentication with Devise

Implemented Devise for user authentication, including:
- User sign up, login, and logout
- Email confirmation and password recovery
- Integration with Turbo Streams

Closes #42

Explanation:

  • The first -m argument contains the commit title (short summary, 50 characters max).
  • The second -m argument contains the detailed description, with each bullet point on a new line.
  • The Closes #42 automatically links and closes GitHub/GitLab issue #42 when pushed.

Alternative Using a Text Editor (Recommended for Long Messages)

If your commit message is long, use:

git commit

This opens the default text editor (like Vim or Nano), where you can structure the message properly:

feat: Add user authentication with Devise

Implemented Devise for user authentication, including:
- User sign up, login, and logout
- Email confirmation and password recovery
- Integration with Turbo Streams

Closes #42

This keeps the message clean and readable. 🚀

2. Use a Clear and Concise Subject Line

  • Limit the first line to 50 characters.
  • Start with an imperative verb (e.g., “Add”, “Fix”, “Refactor”, “Improve”).
  • Avoid generic messages like “Update” or “Fix bug”.

✅ Good:

fix: Resolve N+1 query issue in orders controller

❌ Bad:

Fixed bug

3. Use Conventional Commit Types

Use prefixes to categorize the change:

  • feat: → New feature
  • fix: → Bug fix
  • docs: → Documentation update
  • style: → Code formatting (no logic change)
  • refactor: → Code refactoring (no feature change)
  • test: → Adding/modifying tests
  • chore: → Maintenance tasks (e.g., dependencies, build scripts)
  • perf: → Performance improvement
  • ci: → CI/CD-related changes

Example:

perf: Optimize database queries for dashboard stats

4. Include Context and Motivation

Explain why a change was made if it’s not obvious.

✅ Good:

refactor: Extract user authentication logic to service object

Moved authentication logic from controllers to a dedicated 
service object to improve testability and maintainability.

5. Reference Issues and PRs

  • Use Closes #123 to automatically close the issue.
  • Use Refs #456 if it’s related but not closing the issue.

Example:

feat: Implement image upload in profile settings

Users can now upload profile pictures. The uploaded images 
are stored using Active Storage.

Closes #89

6. Keep Commits Small and Focused

Each commit should:

  • Represent a single logical change.
  • Avoid mixing refactoring with new features.

✅ Good:

  • Commit 1: refactor: Extract helper method for API requests
  • Commit 2: feat: Add API endpoint for fetching user statistics

❌ Bad:

  • Commit 1: feat: Add API endpoint and refactor helper methods

7. Use Present Tense

Write commit messages in present tense, not past tense.

✅ Good:

fix: Handle nil values in user profile settings

❌ Bad:

Fixed nil values issue in user profile settings


Following these best practices ensures readable, maintainable, and searchable commit history. 🚀

Understanding Confusing 🧐 Ruby Concepts: Procfile, Rake, Rack, and More

Ruby has several terms that sound similar but serve different purposes. If you’ve ever been confused by things like Procfile, Rakefile, Rack, and Rake, this guide will clarify them all. Plus, we’ll cover additional tricky concepts you might have overlooked!

1. Procfile

What is it?

A Procfile is a text file used in deployment environments (like Heroku and Kamal) to specify how your application should be started.

Where is it used?

Platforms like Heroku, Kamal, and Foreman use Procfile to define process types (like web servers and workers).

Example:

web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq

  • web: Starts the Puma web server.
  • worker: Runs background jobs using Sidekiq.

Check the post for details (Foreman): https://railsdrop.com/2025/03/26/setup-rails-8-app-part-4-tailwind-css-into-the-action/

2. Rake and Rakefile

What is Rake?

Rake is a task management tool for automating scripts in Ruby applications. It’s like Makefile but written in Ruby.

What is a Rakefile?

A Rakefile is a Ruby file where Rake tasks are defined.

Check Rails::Railtie.rake_tasks for more info.

Where is it used?

  • Rails applications (for tasks like database migrations and data seeding)
  • Standalone Ruby applications (for automating scripts)

Common Rake Commands in Rails:

rake db:migrate    # Run database migrations
rake db:seed       # Seed the database
rake routes        # Show all available routes

Example Custom Rake Task:

Create a file at lib/tasks/custom.rake:

namespace :custom do
  desc "Prints a greeting"
  task hello: :environment do
    puts "Hello from custom Rake task!"
  end
end

Run it with:

rake custom:hello

3. RackWhat is it?

Rack is a lightweight interface between Ruby web applications and web servers. It provides a simple way to handle HTTP requests and responses.

https://github.com/rack/rack

Checkout Rack in more detail: https://railsdrop.com/2025/04/07/inside-rails-the-role-of-rack-and-middleware/

4. Adding Middleware in a Rails 8 App

Checkout the post: https://railsdrop.com/2025/04/07/inside-rails-the-role-of-rack-and-middleware/

5. Other Confusing Ruby Concepts You Should Know

Gemfile vs. Gemspec

  • Gemfile: Defines dependencies for a Rails project (uses Bundler).
  • Gemspec: Defines dependencies and metadata for a Ruby gem.

Lambda vs. Proc

Both are used for defining anonymous functions, but behave differently:

lambda_example = -> { return "Lambda returns from itself" }
proc_example = Proc.new { return "Proc returns from the enclosing method" }

Safe Navigation Operator (&.)

user&.profile&.name  # Avoids NoMethodError if user or profile is nil

Symbol vs. String

:my_symbol  # Immutable, faster lookup
"my_string" # Mutable, slower lookup

&: Shortcut for Blocks

Ruby allows a shorthand syntax for passing methods as blocks using &:.

["hello", "world"].map(&:upcase)  # => ["HELLO", "WORLD"]

Equivalent to:

["hello", "world"].map { |word| word.upcase }

Single Splat (*) Operator

The * operator is used for handling variable-length arguments in methods.

def sum(*numbers)
  numbers.reduce(:+)
end

puts sum(1, 2, 3, 4)  # Output: 10

It can also be used for array expansion (spreads out Arrays):

arr = [1, 2, 3, 4]
> puts *arr
1
2
3
4
=> nil

odds = [3, 5, 7, 9]
puts *odds
>
3
5
7
9
=> nil

first_odd, *rest = odds
> puts rest
5
7
9
=> nil

We can also insert array elements into another Array. In the example below, odds elements are added to the numbers Array, starting from the position where *odds is called.

odds = [3, 5, 7, 9]
numbers = [1, 2, *odds, 10]
puts "numbers: #{numbers}"

# =>
# numbers: [1, 2, 3, 5, 7, 9, 10]

Double Splat (**) in Method Arguments

The ** operator is used to capture keyword arguments.

def greet(name:, **options)
  puts "Hello, #{name}!"
  puts "Options: #{options}"
end

greet(name: "Alice", age: 25, city: "New York")
# Output:
# Hello, Alice!
# Options: {:age=>25, :city=>"New York"}

What Are Keyword Arguments (kwargs) in Ruby?

(name:) in greet is an example of a keyword argument (kwargs).

Keyword arguments allow you to pass arguments to a method using explicit parameter names, making the code more readable and flexible.

Example: Using a Required Keyword Argument
def greet(name:)
  puts "Hello, #{name}!"
end

greet(name: "Alice")  # Output: Hello, Alice!
  • The name: argument must be provided, otherwise, Ruby will raise an error.
Example: Using Optional Keyword Arguments

You can provide default values for keyword arguments:

def greet(name: "Guest")
  puts "Hello, #{name}!"
end

greet        # Output: Hello, Guest!
greet(name: "Bob")  # Output: Hello, Bob!
Example: Combining Required and Optional Keyword Arguments
def greet(name:, age: nil)
  puts "Hello, #{name}!"
  puts "You are #{age} years old." if age
end

greet(name: "Alice", age: 25)
# Output:
# Hello, Alice!
# You are 25 years old.
Example: Capturing Extra Keyword Arguments with **options

The ** operator captures any additional keyword arguments passed to the method into a hash.

def greet(name:, **options)
  puts "Hello, #{name}!"
  puts "Additional Info: #{options}"
end

greet(name: "Alice", age: 25, city: "New York")
# Output:
# Hello, Alice!
# Additional Info: {:age=>25, :city=>"New York"}

**options collects { age: 25, city: "New York" } as a hash.

Check: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/

Also check the latest Ruby that is released last week:

Final Thoughts

Ruby has many terms that seem similar but have distinct uses. By understanding Procfile, Rake, Rack, and middleware in Rails 8, you’ll have a much clearer picture of how Ruby applications work under the hood. If you’re working on a Rails 8 app, take some time to explore these concepts further—they’ll definitely make your life easier!

Happy coding! 🚀