Git Workflow 🔀: Best Practices for Your Development Process

Scenario:

  • You have a main branch (main).
  • You create feature branches (e.g., feature-x) for development.
  • You want to keep your history clean and structured.

1️⃣ git merge → Use When You Want to Keep Full History (Default Approach)

📌 What It Does?

  • Combines feature branch commits into main, keeping all commit history.
  • Creates a new merge commit (Fast-forward if no diverging commits).
  • Easy to understand but can clutter history with many small commits.

📌 When to Use?

✅ If you want a full commit history, including every small commit.
✅ When working in teams where visibility of each commit matters.

📌 Example (Merging feature-x into main):

git checkout main
git pull origin main   # Ensure main is up to date
git merge feature-x
git push origin main

2️⃣ git rebase → Use When You Want a Clean, Linear History

📌 What It Does?

  • Moves (reapplies) all commits from the feature branch on top of the latest main.
  • No merge commit → keeps history linear and clean.
  • Can cause conflicts if multiple people are rebasing.

📌 When to Use?

✅ When working solo and want a clean, linear history.
✅ Before merging a feature branch to avoid unnecessary merge commits.
✅ If you regularly update your feature branch with main.

📌 Example (Rebasing feature-x onto main Before Merging):

git checkout feature-x
git pull origin main   # Ensure main is up to date
git rebase main        # Moves feature branch commits on top of the latest main

If there are conflicts, resolve them, then:

git rebase --continue

Then merge into main:

git checkout main
git merge feature-x   # Fast-forward merge (clean)
git push origin main

3️⃣ git squash → Use When You Want a Single Commit for a Feature

📌 What It Does?

  • Combines multiple commits in the feature branch into one commit before merging.
  • Keeps history very clean but loses commit granularity.

📌 When to Use?

✅ If a feature branch has many small commits (e.g., fix typo, refactor, debugging).
✅ When you want one clean commit per feature.
✅ If your team follows a “one commit per feature” policy.

📌 Example (Squashing Commits Before Merging into main):

Step 1: Squash commits interactively

git checkout feature-x
git rebase -i main

Step 2: Mark commits to squash

You’ll see:

pick abc123 First commit
pick def456 Second commit
pick ghi789 Third commit

Change all but the first pick to squash (s), like this:

pick abc123 First commit
squash def456 Second commit
squash ghi789 Third commit

Save and exit.

Step 3: Push the squashed commit

git push origin feature-x --force  # Required after rewriting history

Step 4: Merge into main

git checkout main
git merge feature-x
git push origin main

Another git squash example. Suppose you have created a branch for adding a feature and you do 2 more commits one for refactoring and other for commenting on the feature. Now you have 3 commits in the feature-x branch.

Now you think better I would commit these 3 commit together and push it as one feature commit. Now what you do? follow the bel0w steps:

# this tells git you want to do some action upon the last 3 commits
git rebase -i HEAD~3

Now you can see 3 commits in your default editor prefixed: pick. Change the last 2 commit pick to squash and save the file. Now Another file popup in the editor to change the commit message for this one squashed commit. Ignore the messages start with # . Keep/modify the commit message which is not starting with # and save the file. This rewrites the history and save as one commit.

Now check the commit history:

git log --oneline

Checkout git HEAD in our post: https://railsdrop.com/2025/04/06/git-best-practices-git-head-useful-commands-commit-message/

Which One to Use in Your Development Process?

ScenarioUse merge?Use rebase?Use squash?
Merging a feature branch✅ Yes (keeps history)✅ Yes (cleaner history)✅ Yes (one commit per feature)
Keeping history linear❌ No✅ Yes✅ Yes
Keeping every commit✅ Yes✅ Yes❌ No (loses small commits)
Team collaboration✅ Yes (safer)⚠️ Be careful (can rewrite history)✅ Yes (if team agrees)
Avoiding merge commits❌ No✅ Yes✅ Yes

Recommendation for Your Workflow 🔀

For daily work, keep your feature branch updated with main:

git checkout feature-x 
git pull origin main 
git rebase main

Before merging into main, If you want full history:

git merge feature-x

If you want a clean history:

git rebase main

If you want a single commit per feature:

git squash

Merge into main and push:

git checkout main 
git merge feature-x 
git push origin main

🤔 What’s the Problem with Merge Commits?

A merge commit happens when you run:

git merge feature-x

and Git creates an extra commit to record the merge.

Example merge commit:

commit abc123 (HEAD -> main)
Merge: def456 ghi789
Author: You <you@example.com>
Date:   Sat Mar 29 12:00:00 2025 +0000

    Merge branch 'feature-x' into 'main'

Why Do Some Developers Avoid Merge Commits?

1️⃣ Cluttered History

  • Merge commits pollute history when feature branches have multiple commits.
  • Running git log shows lots of merge commits, making it harder to track actual code changes.
  • Example of messy history with merge commits:
    * Merge branch 'feature-x' (Merge Commit)
    * Fix typo in error message
    * Add validation for user input
    * Implement user authentication
    * Merge branch 'feature-y' (Merge Commit)
    * Fix UI issue

2️⃣ Harder to Track Changes

  • git log gets filled with merge commits instead of meaningful commits.
  • git blame might point to a merge commit, making debugging harder.

3️⃣ Confusing Graph with Many Branches

  • Running git log --graph --oneline --all shows a complex branching history:
    * abc123 Merge branch ‘feature-x’
    |\
    | * ghi789 Fix authentication bug
    | * def456 Add new login method
    | 123456 Merge branch ‘feature-y’
    |/
  • If you rebase instead, the history stays linear.

🛠️ When Do Devs Avoid Merge Commits?

1️⃣ When They Want a Clean, Linear History

✅ Use git rebase instead of git merge to avoid merge commits.
Example:

git checkout feature-x
git rebase main
git checkout main
git merge feature-x  # No merge commit (fast-forward merge)

🔹 This way, the commits from feature-x appear directly on top of main.

2️⃣ When They Squash Multiple Commits into One

✅ Use git rebase -i main to combine multiple commits into one before merging.
Example:

git checkout feature-x
git rebase -i main  # Squash commits
git checkout main
git merge feature-x  # No unnecessary commits

🔹 This results in one clean commit per feature.

3️⃣ When Working on a Team with Many Developers

  • If multiple people merge their branches without rebasing, Git history gets full of merge commits.
  • Teams using trunk-based development or feature branch workflows often rebase instead of merging.

✅ Solution → Always rebase before merging:

git checkout feature-x
git pull --rebase origin main
git push origin feature-x

When Are Merge Commits Actually Useful?

Merge commits are not always bad! Sometimes, they are necessary:

  1. Merging Long-Lived Feature Branches
    • If a branch exists for weeks or months, it has many commits.
    • A merge commit documents when the feature was merged.
  2. Merging Changes from a Release Branch
    • Example: Merging release-v1.0 into main should have a merge commit.
  3. Handling Complex Conflicts
    • When resolving big merge conflicts, a merge commit shows exactly when conflicts were fixed.

🔑 Final Answer: When to Avoid Merge Commits?

🚫 Avoid merge commits when you want a clean history
✅ Use rebasing (git rebase) before merging to keep history linear.
✅ Use squashing (git rebase -i) to avoid unnecessary commits in a feature branch.

🔹 If you’re working solo or in a small team, rebase before merging to keep history simple.
🔹 If you’re working in a large team with long-lived branches, merge commits may be useful for tracking.


🔥 Finally Best Practices for Your Development Process

Before merging, update your branch without a merge commit:

git checkout feature-x
git pull --rebase origin main

Squash unnecessary commits (optional):

git rebase -i main

Merge the branch (fast-forward, no merge commit):

git checkout main
git merge feature-x  # Fast-forward merge, no merge commit

This keeps your history clean and easy to read!

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 3: Git setup, modify gitignore, git config

So now let’s push the code to github repository. Before that there is some final checks need to be done from our end.

Check .gitignore file and update

✅ Files/Folders to Include in .gitignore

Here’s a breakdown of which files/folders should be added to .gitignore in your Rails project:

These files are user-specific or environment-specific and should not be committed to Git.

1️⃣ .dockerignore → ❌ Ignore from .gitignore

  • Keep this file if you’re using Docker.
  • It’s like .gitignore but for Docker, helping to reduce Docker image size.
  • Do not add it to .gitignore if you need it.

2️⃣ .github/ → ✅ Add to .gitignore (If personal CI/CD configs)

  • If this contains GitHub Actions or issue templates, keep it in the repo.
  • If it’s just for personal workflows, add it to .gitignore.

3️⃣ .kamal/ → ✅ Add to .gitignore

  • This contains deployment secrets and configuration files for Kamal (deployment tool).
  • It’s usually auto-generated and should not be committed.

4️⃣ .vscode/ → ✅ Add to .gitignore

  • User-specific VSCode settings, should not be committed.
  • Different developers use different editors.

Keep These Files in Git (Don’t Add to .gitignore)

These files are important for project configuration.

1️⃣ .gitattributes → ❌ Keep in Git

  • Defines how Git handles line endings and binary files.
  • Helps avoid conflicts on Windows/Linux/Mac.

2️⃣ .gitignore → ❌ Keep in Git

  • Defines ignored files, obviously should not be ignored itself.

3️⃣ .rubocop.yml → ❌ Keep in Git

  • This is for Rubocop linting rules, which helps maintain coding style.
  • All developers should follow the same rules.

4️⃣ .ruby-version → ❌ Keep in Git

  • Defines the Ruby version for the project.
  • Ensures all team members use the same Ruby version.

Final .gitignore Entries Based on Your Files

# Ignore log files, temp files, and dependencies
/log/
/tmp/
.bundle/
/node_modules/

# Manually added
# Ignore editor & environment-specific configs
.vscode/

# Ignore deployment configs
.kamal/

# Ignore personal GitHub configs (if applicable)
.github/

Final Summary

FolderInclude in Git?Why?
log/❌ IgnoreDynamically generated logs
public/✅ Keep (except public/assets/)Static files like favicon, error pages
script/✅ KeepOld Rails script files (if used)
storage/❌ IgnoreActiveStorage uploads (except seed/)
test/✅ KeepContains important test cases
tmp/❌ IgnoreTemporary runtime files
vendor/❌ Ignore (except custom libraries)Third-party libraries

First time git setup

# You can view all of your settings and where they are coming from using

git config --list --show-origin

# Your Identity: The first thing you should do is to set your user name and email address.

git config --global user.name "Abhilash"
git config --global user.email abhilash@example.com

# configure the default text editor that will be used when Git needs you to type in a message

git config --global core.editor emacs
git config --global core.editor "code --wait"     # vs code
git config --global -e      # verify editor

# command to list all the settings Git can find at that point

git config --list
git config user.name

Add your ssh key to your github. Check the post: https://railsdrop.com/2025/03/30/setting-up-ssh-in-your-system/

Initial commit: Execute the Git commands

Run the following commands in your rails app folder:

git add .
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:abhilashak/design_studio.git
git remote -v     # check the remote endpoints
git push -u origin main

git log      # check commit details

What It Does:

git branch -M main
  1. Renames the current branch to main.
  2. The -M flag forcefully renames the branch (overwrites if needed).

Common Use Case:

  • If your branch is named master and you want to rename it to main (which is now the default in many repositories).
  • If you created a branch with a different name and want to standardise it as main.

Example Usage:

git branch -M main
git push -u origin main

This renames the current branch to main and then pushes it to the remote repository.

Use github ssh option: it checks the ssh key that you setup in your github account for that machine.
http option asks your github credentials to login.

The -u option in the command:

git push -u origin main

What Does -u Do?

The -u flag stands for --set-upstream. It sets the upstream branch for main, which means:

  • It links your local branch (main) to the remote branch (main on origin).
  • After running this command once, you can simply use:
  git push

instead of git push origin main, because Git now knows where to push.

Example Use Case:

If you just created a new branch (main in this case) and are pushing it for the first time:

git push -u origin main

This ensures that main always pushes to origin/main without needing to specify it every time.

After Running This Command:

✅ Next time, you can simply use:

git push
git pull

without needing to specify origin main again.

For better git work flow

Check the post: https://railsdrop.com/2025/03/29/git-workflow-best-practices-for-your-development-process/

Want to See Your Upstream Branch?

Run:

git branch -vv

This shows which remote branch your local branches are tracking.

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! 🚀

Writing Perfect Active Record 🗒️ Queries in Ruby on Rails 8

Active Record (AR) is the heart of Ruby on Rails when it comes to database interactions. Writing efficient and readable queries is crucial for application performance and maintainability. This guide will help you master Active Record queries with real-world examples and best practices.


Setting Up a Sample Database

To demonstrate complex Active Record queries, let’s create a Rails app with a sample database structure containing multiple tables.

Generate Models & Migrations

rails new MyApp --database=postgresql
cd MyApp
rails g model User name:string email:string
rails g model Post title:string body:text user:references
rails g model Comment body:text user:references post:references
rails g model Category name:string
rails g model PostCategory post:references category:references
rails g model Like user:references comment:references
rails db:migrate

Database Schema Overview

  • users: Stores user information.
  • posts: Stores blog posts written by users.
  • comments: Stores comments on posts, linked to users and posts.
  • categories: Stores post categories.
  • post_categories: Join table for posts and categories.
  • likes: Stores likes on comments by users.

Basic Active Record Queries

1. Fetching All Records

User.all  # Returns all users (Avoid using it directly on large datasets as it loads everything into memory)

⚠️ User.all can lead to performance issues if the table contains a large number of records. Instead, prefer pagination (User.limit(100).offset(0)) or batch processing (User.find_each).

2. Finding a Specific Record

User.find(1)  # Finds a user by ID
User.find_by(email: 'john@example.com')  # Finds by attribute

3. Filtering with where vs having

Post.where(user_id: 2)  # Fetch all posts by user with ID 2

Difference between where and having:

  • where is used for filtering records before grouping.
  • having is used for filtering after group operations.

Example:

Post.group(:user_id).having('COUNT(id) > ?', 5)  # Users with more than 5 posts

4. Ordering Results

User.order(:name)  # Order users alphabetically
Post.order(created_at: :desc)  # Order posts by newest first

5. Limiting Results

Post.limit(5)  # Get the first 5 posts

6. Selecting Specific Columns

User.select(:id, :name)  # Only fetch ID and name

7. Fetching Users with a Specific Email Domain

User.where("email LIKE ?", "%@gmail.com")

8. Fetching the Most Recent Posts

Post.order(created_at: :desc).limit(5)

9. Using pluck for Efficient Data Retrieval

User.pluck(:email)  # Fetch only emails as an array

10. Checking if a Record Exists Efficiently

User.exists?(email: 'john@example.com')

11. Including Associations (eager loading to avoid N+1 queries)

Post.includes(:comments).where(comments: { body: 'Great post!' })


Advanced Queries with Joins

1. Joining Tables (INNER JOIN)

Post.joins(:user).where(users: { name: 'John' })

2. Self Join Example

A self-join is useful when dealing with hierarchical relationships, such as an employee-manager structure.

Model Setup

class Employee < ApplicationRecord
  belongs_to :manager, class_name: 'Employee', optional: true
  has_many :subordinates, class_name: 'Employee', foreign_key: 'manager_id'
end

Sample Data

idnamemanager_id
1AliceNULL
2Bob1
3Carol1
4Dave2

Query: Find Employees Who Report to Alice

Employee.joins(:manager).where(managers_employees: { name: 'Alice' })

Result:

idnamemanager_id
2Bob1
3Carol1

This query fetches employees who report to Alice (i.e., those where manager_id = 1).

3. Fetching Users with No Posts (LEFT JOIN with NULL check)

User.left_outer_joins(:posts).where(posts: { id: nil })

4. Counting Posts Per User

User.joins(:posts).group('users.id').count

Complex Queries in Active Record

1. Fetching Posts with the Most Comments

Post.joins(:comments)
    .group('posts.id')
    .order('COUNT(comments.id) DESC')
    .limit(1)

2. Fetching Posts with More than 5 Comments

Post.joins(:comments)
    .group(:id)
    .having('COUNT(comments.id) > ?', 5)

3. Finding Users Who Liked the Most Comments

User.joins(comments: :likes)
    .group('users.id')
    .select('users.id, users.name, COUNT(likes.id) AS likes_count')
    .order('likes_count DESC')
    .limit(1)

4. Fetching Posts Belonging to Multiple Categories

Post.joins(:categories).group('posts.id').having('COUNT(categories.id) > ?', 1)

5. Fetching the Last Comment of Each Post

Comment.select('DISTINCT ON (post_id) *').order('post_id, created_at DESC')

6. Fetching Users Who Haven’t Commented on a Specific Post

User.where.not(id: Comment.where(post_id: 10).select(:user_id))

7. Fetching Users Who Have Commented on Every Post

User.joins(:comments).group(:id).having('COUNT(DISTINCT comments.post_id) = ?', Post.count)

8. Finding Posts With No Comments

Post.left_outer_joins(:comments).where(comments: { id: nil })

9. Fetching the User Who Created the Most Posts

User.joins(:posts)
    .group('users.id')
    .select('users.id, users.name, COUNT(posts.id) AS post_count')
    .order('post_count DESC')
    .limit(1)

10. Fetching the Most Liked Comment

Comment.joins(:likes)
    .group('comments.id')
    .order('COUNT(likes.id) DESC')
    .limit(1)

11. Fetching Comments with More than 3 Likes and Their Associated Posts

Comment.joins(:likes, :post)
    .group('comments.id', 'posts.id')
    .having('COUNT(likes.id) > ?', 3)

12. Finding Users Who Haven’t Liked Any Comments

User.left_outer_joins(:likes).where(likes: { id: nil })

13. Fetching Users, Their Posts, and the Count of Comments on Each Post

User.joins(posts: :comments)
    .group('users.id', 'posts.id')
    .select('users.id, users.name, posts.id AS post_id, COUNT(comments.id) AS comment_count')
    .order('comment_count DESC')

Importance of inverse_of in Model Associations

What is inverse_of?

The inverse_of option in Active Record associations helps Rails correctly link objects in memory, avoiding unnecessary database queries and ensuring bidirectional association consistency.

Example Usage

class User < ApplicationRecord
  has_many :posts, inverse_of: :user
end

class Post < ApplicationRecord
  belongs_to :user, inverse_of: :posts
end

Why Use inverse_of?

  • Performance Optimization: Prevents extra queries by using already loaded objects.
  • Ensures Data Consistency: Updates associations without additional database fetches.
  • Enables Nested Attributes: Helps when using accepts_nested_attributes_for.

Example:

user = User.new(name: 'Alice')
post = user.posts.build(title: 'First Post')
post.user == user  # True without needing an additional query

Best Practices to use in Rails Projects

1. Using Scopes for Readability

class Post < ApplicationRecord
  scope :recent, -> { order(created_at: :desc) }
end

Post.recent.limit(10)  # Fetch recent posts

2. Using find_each for Large Datasets

User.find_each(batch_size: 100) do |user|
  puts user.email
end

3. Avoiding SELECT * for Performance

User.select(:id, :name).load

4. Avoiding N+1 Queries with includes

Post.includes(:comments).each do |post|
  puts post.comments.count
end


Conclusion

Mastering Active Record queries is essential for writing performant and maintainable Rails applications. By using joins, scopes, batch processing, and eager loading, you can write clean and efficient queries that scale well.

Do you have any favorite Active Record query tricks? Share them in the comments!

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! 🚀

Rails 🛤 DSLs Explained: How Ruby Makes Configuration Elegant

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

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


What is a DSL?

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

Two types of DSLs exist:

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

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


1. Routes in Rails: A Classic Example of DSL

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

Example:

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

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

How Does This Work?

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

Why Use a DSL for Routes?

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

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


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

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

Example:

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

What is config Here?

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

Why a DSL for Configuration?

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

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

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

Example:

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

How Does This Work?

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

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

Why Use a DSL for Testing?

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

4. Defining Methods Dynamically: ActiveSupport::Concern

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

Example:

module Trackable
  extend ActiveSupport::Concern

  included do
    before_save :track_changes
  end

  private
  def track_changes
    puts "Tracking changes!"
  end
end

class User < ApplicationRecord
  include Trackable
end

How This Works:

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

Why a DSL for Mixins?

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

Conclusion: Why Rails Embraces DSLs

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

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

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

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

Enjoy Ruby 🚀

Understanding Message ✉️ Passing in Ruby

Ruby is often celebrated for its expressive and dynamic nature, and one of its most fascinating yet under-appreciated feature is message passing. Unlike many other languages that focus purely on method calls, Ruby inherits the concept of sending messages from Smalltalk and embraces it throughout the language.

Let’s explore what message passing is, why Ruby uses it, and how you can leverage it effectively in your code.


What is Message Passing in Ruby?

In Ruby, every method call is actually a message being sent to an object. When you invoke a method on an object, Ruby sends a message to that object, asking it to execute a particular behavior.

Example:

str = "Hello, Ruby!"
puts str.reverse   # Standard method call

Behind the scenes, Ruby treats this as sending the :reverse message to the str object:

puts str.send(:reverse) # Equivalent to str.reverse

This concept extends beyond just methods—it applies to variable access and operators too! Even str.length is just Ruby sending the :length message to str.


Why Does Ruby Use Message Passing?

Message passing provides several advantages:

  1. Encapsulation and Flexibility
    • Instead of directly accessing methods, sending messages allows objects to decide how they respond to method calls.This abstraction makes code more modular and adaptable, allowing behavior to change at runtime without modifying existing method definitions.
class DynamicResponder
  def method_missing(name, *args)
    "I received a message: #{name}, but I don't have a method for it."
  end
end

obj = DynamicResponder.new
puts obj.some_method  # => "I received a message: some_method, but I don't have a method for it."

2. Dynamic Method Invocation

  • You can invoke methods dynamically at runtime using symbols instead of hardcoded method names.
method_name = :upcase
puts "ruby".send(method_name) # => "RUBY"

3. Metaprogramming Capabilities

  • Ruby allows defining methods dynamically using define_method, making it easier to create flexible APIs.
class Person
  [:first_name, :last_name].each do |method|
    define_method(method) do |value|
      instance_variable_set("@#{method}", value)
    end
  end
end

p = Person.new
p.first_name("John")
p.last_name("Doe")

Using send to Pass Messages Dynamically

The send method allows you to invoke methods dynamically using symbols or strings representing method names.

Example 1: Calling Methods Dynamically

str = "ruby messages"
puts str.send(:reverse)  # => "segassem ybur"
puts str.send(:[], 4..9) # => " messa"

Example 2: Checking Method Availability with respond_to?

puts str.respond_to?(:reverse) # => true
puts str.respond_to?(:last)    # => false

This ensures safe message passing by verifying that an object responds to a method before calling it.

Example 3: Avoiding Private Method Calls with public_send

class Secret
  private
  def hidden_message
    "This is private!"
  end
end

secret = Secret.new
puts secret.send(:hidden_message)      # Works! (bypasses visibility)
puts secret.public_send(:hidden_message) # Error! (Respects visibility)

Use public_send to ensure that only public methods are invoked dynamically.


Defining Methods Dynamically with define_method

Ruby also allows defining methods dynamically at runtime using define_method:

Example 1: Creating Methods on the Fly

class Person
  [:first_name, :last_name].each do |method|
    define_method(method) do |value|
      instance_variable_set("@#{method}", value)
    end
  end
end

p = Person.new
p.first_name("John")
p.last_name("Doe")

This creates first_name and last_name methods dynamically.

Example 2: Generating Getters and Setters

class Dynamic
  attr_accessor :data
end

obj = Dynamic.new
obj.data = "Dynamic Method"
puts obj.data # => "Dynamic Method"


Handling Undefined Methods with method_missing

If an object receives a message (method call) that it does not understand, Ruby provides a safety net: method_missing.

Example: Catching Undefined Method Calls

class Robot
  def method_missing(name, *args)
    "I don’t know how to #{name}!"
  end
end

r = Robot.new
puts r.dance # => "I don’t know how to dance!"
puts r.fly   # => "I don’t know how to fly!"

This feature is commonly used in DSLs (Domain Specific Languages) to handle flexible method invocations.


How Ruby Differs from Other Languages

Many languages support method calls, but few treat method calls as message passing the way Ruby does. For example:

  • Python: Uses getattr(obj, method_name) for dynamic calls, but lacks an equivalent to method_missing.
  • JavaScript: Uses obj[method_name]() for indirect invocation but doesn’t treat calls as messages.
  • Java: Requires reflection (Method.invoke), making it less fluid than Ruby.
  • Smalltalk: The original message-passing language, which inspired Ruby’s approach.

When to Use Message Passing Effectively

Use send for dynamic method invocation:

  • Useful for reducing boilerplate in frameworks like Rails.
  • Ideal when working with DSLs or metaprogramming scenarios.

Use respond_to? before send to avoid errors:

  • Ensures that the object actually has the method before attempting to call it.

Use define_method for dynamic method creation:

  • Helps in cases where multiple similar methods are needed.

Use method_missing cautiously:

  • Can be powerful, but should be handled carefully to avoid debugging nightmares.

Final Thoughts

Ruby’s message passing mechanism is one of the reasons it excels in metaprogramming and dynamic method invocation. Understanding this feature allows developers to write more flexible and expressive code while keeping it clean and readable. The best example using this features you can see here: https://github.com/rails/rails . None other than Rails Framework itself!!

By embracing message passing, you can unlock new levels of code abstraction, modularity, and intelligent method handling in your Ruby projects.

Enjoy Ruby 🚀

Understanding 💭 include, extend, and prepend in Ruby

Ruby is well known for its flexibility and expressive syntax. One of its powerful features is the ability to share functionality using modules. While many languages offer mixins, Ruby stands out with three key methods: include, extend, and prepend. Understanding these methods can significantly improve code reuse and clarity. Let’s explore each with examples!

include → Adds Module Methods as Instance Methods

When you use include in a class, it injects the module’s methods as instance methods of the class.

Example: Using include for instance methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  include Greet
end

user = User.new
puts user.hello # => "Hello from Greet module!"

Scope: The methods from Greet become instance methods in User.


extend → Adds Module Methods as Class Methods

When you use extend in a class, it injects the module’s methods as class methods.

Example: Using extend for class methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  extend Greet
end

puts User.hello # => "Hello from Greet module!"

Scope: The methods from Greet become class methods in User, instead of instance methods.


prepend → Adds Module Methods Before Instance Methods

prepend works like include, but the methods from the module take precedence over the class’s own methods.

Example: Using prepend to override instance methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  prepend Greet
  
  def hello
    "Hello from User class!"
  end
end

user = User.new
puts user.hello # => "Hello from Greet module!"

Scope: The methods from Greet override User‘s methods because prepend places Greet before the class in the method lookup chain.


Method Lookup Order

Understanding how Ruby resolves method calls is essential when using these techniques.

The method lookup order is as follows:

  1. Prepend modules (highest priority)
  2. Instance methods in the class itself
  3. Include modules
  4. Superclass methods
  5. Object, Kernel, BasicObject

To visualize this, use the ancestors method:

class User
  prepend Greet
end

puts User.ancestors
# => [Greet, User, Object, Kernel, BasicObject]


How Ruby Differs from Other Languages

  • Ruby is unique in dynamically modifying method resolution order (MRO) using prepend, include, and extend.
  • In Python, multiple inheritance is used with super(), but prepend-like behavior does not exist.
  • JavaScript relies on prototypes instead of module mixins, making Ruby’s approach cleaner and more structured.

Quick Summary

KeywordInjects Methods AsWorks OnTakes Precedence?
includeInstance methodsInstancesNo
extendClass methodsClassesN/A
prependInstance methodsInstancesYes (overrides class methods)

By understanding and using include, extend, and prepend, you can make your Ruby code more modular, reusable, and expressive. Master these techniques to level up your Ruby skills!

Enjoy Ruby 🚀