Hi, I’m Abhilash! A seasoned web developer with 15 years of experience specializing in Ruby and Ruby on Rails. Since 2010, I’ve built scalable, robust web applications and worked with frameworks like Angular, Sinatra, Laravel, Node.js, Vue and React.
Passionate about clean, maintainable code and continuous learning, I share insights, tutorials, and experiences here. Let’s explore the ever-evolving world of web development together!
To attach multiple images to a Product model in Rails 8, Active Storage provides the best way using has_many_attached. Below are the steps to set up multiple image attachments in a local development environment.
1️⃣ Install Active Storage (if not already installed)
We have already done this step if you are following this series. Else run the following command to generate the necessary database migrations:
rails active_storage:install
rails db:migrate
This will create two tables in your database:
active_storage_blobs → Stores metadata of uploaded files.
active_storage_attachments → Creates associations between models and uploaded files.
2️⃣ Update the Product Model
Configuring specific variants is done the same way as has_one_attached, by calling the variant method on the yielded attachable object:
add in app/models/product.rb:
class Product < ApplicationRecord
has_many_attached :images do |attachable|
attachable.variant :normal, resize_to_limit: [540, 720]
attachable.variant :thumb, resize_to_limit: [100, 100]
end
end
You just have to mention the above and rails will create everything for you!
Variants rely on ImageProcessing gem for the actual transformations of the file, so you must add gem "image_processing" to your Gemfile if you wish to use variants.
By default, images will be processed with ImageMagick using the MiniMagick gem, but you can also switch to the libvips processor operated by the ruby-vips gem.
3️⃣ Configure Active Storage for Local Development
By default, Rails stores uploaded files in storage/ under your project directory.
Ensure your config/environments/development.rb has:
config.active_storage.service = :local
And check config/storage.yml to ensure you have:
local:
service: Disk
root: <%= Rails.root.join("storage") %>
This will store the uploaded files in storage/.
4️⃣ Add File Uploads in Controller
Modify app/controllers/products_controller.rb to allow multiple image uploads:
class ProductsController < ApplicationController
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product, notice: "Product was successfully created."
else
render :new
end
end
private
def product_params
params.require(:product).permit(:name, :description, images: [])
end
end
Notice images: [] → This allows multiple images to be uploaded.
Meanwhile we are setting up some UI for our app using Tailwind CSS, I have uploaded 2 images to our product in the rich text editor. Let’s discuss about this in this post.
Understanding Active Storage in Rails 8: A Deep Dive into Image Uploads
In our Rails 8 application, we recently tested uploading two images to a product using the rich text editor. This process internally triggers several actions within Active Storage. Let’s break down what happens behind the scenes.
How Active Storage Handles Image Uploads
When an image is uploaded, Rails 8 processes it through Active Storage, creating a new blob entry and storing it in the disk service. The following request is fired:
Processing by ActiveStorage::DirectUploadsController#create as JSON
Parameters: {"blob" => {"filename" => "floral-kurtha.jpg", "content_type" => "image/jpeg", "byte_size" => 107508, "checksum" => "GgNgNxxxxxxxjdPOLw=="}}
This request initiates a database entry in active_storage_blobs:
This process triggers the ActiveStorage::DiskController, handling file storage via a PUT request:
Started PUT "/rails/active_storage/disk/eyJfcmFpbHMiOxxxxx"
Disk Storage (0.9ms) Uploaded file to key: hut9d0zxssxxxxxx
Completed 204 No Content in 96ms
Retrieving Images from Active Storage
After successfully storing the file, the application fetches the image via a GET request:
Started GET "/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOxxxxxxxxxxfQ==--f9c556012577xxxxxxxxxxxxfa21/floral-kurtha-2.jpg"
This request is handled by:
Processing by ActiveStorage::Blobs::RedirectController#show as JPEG
The file is then served via the ActiveStorage::DiskController#show:
Redirected to http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsiZGxxxxxxxxxd048aae4ab5c30/floral-kurtha-2.jpg
Updating Records with Active Storage Attachments
When updating a product, the system also updates its associated images. The following Active Storage updates occur:
UPDATE "action_text_rich_texts" SET "body" = .... WHERE "action_text_rich_texts"."id" = 1
UPDATE "active_storage_blobs" SET "metadata" = '{"identified":true}' WHERE "active_storage_blobs"."id" = 3
INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ('embeds', 'ActionText::RichText', 1, 3, '2025-03-31 11:46:13.464597')
Additionally, Rails updates the updated_at timestamp of the associated records:
UPDATE "products" SET "updated_at" = '2025-03-31 11:46:13.523640' WHERE "products"."id" = 1
Best Practices for Active Storage in Rails 8
Use Direct Uploads: This improves performance by uploading files directly to cloud storage (e.g., AWS S3, Google Cloud Storage) instead of routing them through your Rails server.
Attach Images Efficiently: Use has_one_attached or has_many_attached for file associations in models.
Avoid Serving Files via Rails: Use a CDN or proxy service to serve images instead of relying on Rails controllers.
Clean Up Unused Blobs: Regularly remove orphaned blob records using ActiveStorage::Blob.unattached.destroy_all.
Optimize Image Processing: Use variants (image.variant(resize: "300x300").processed) to generate resized images efficiently.
In Rails 8, Active Storage uses two main tables for handling file uploads:
1. active_storage_blobs Table
This table stores metadata about the uploaded files but not the actual files. Each row represents a unique file (or “blob”) uploaded to Active Storage.
Columns in active_storage_blobs Table:
id – Unique identifier for the blob.
key – A unique key used to retrieve the file.
filename – The original name of the uploaded file.
content_type – The MIME type (e.g., image/jpeg, application/pdf).
metadata – JSON data storing additional information (e.g., width/height for images).
service_name – The storage service (e.g., local, amazon, google).
byte_size – File size in bytes.
checksum – A checksum to verify file integrity.
created_at – Timestamp when the file was uploaded.
👉 Purpose: This table allows a single file to be attached to multiple records without duplicating the file itself.
Why Does Rails Need Both Tables?
Separation of Concerns:
active_storage_blobstracks the files themselves.
active_storage_attachmentslinks them to models.
Efficient File Management:
The same file can be used in multiple places without storing it multiple times.
If a file is no longer attached to any record, Rails can remove it safely.
Supports Different Attachments:
A model can have different types of attachments (avatar, cover_photo, documents).
A single model can have multiple files attached (has_many_attached).
Example Usage in Rails 8
class User < ApplicationRecord
has_one_attached :avatar # Single file
has_many_attached :photos # Multiple files
end
When a file is uploaded, an entry is added to active_storage_blobs, and an association is created in active_storage_attachments.
How Rails Queries These Tables
user.avatar # Fetches from `active_storage_blobs` via `active_storage_attachments`
user.photos.each { |photo| puts photo.filename } # Fetches multiple attached files
Conclusion
Rails 8 uses two tables to decouple file storage from model associations, enabling better efficiency, flexibility, and reusability. This structure allows models to reference files without duplicating them, making Active Storage a powerful solution for file management in Rails applications. 🚀
Where Are Files Stored in Rails 8 by Default?
By default, Rails 8 stores uploaded files using Active Storage’s disk service, meaning files are saved in the storage/ directory within your Rails project.
Default Storage Location:
Files are stored in: storage/ ├── cache/ (temporary files) ├── store/ (permanent storage) └── variant/ (image transformations like resizing)
The exact file path inside storage/ is determined by the key column in the active_storage_blobs table. For example, if a blob entry has: key = 'xyz123abcd' then the file is stored at: storage/store/xyz123abcd
How to Change the Storage Location?
You can configure storage in config/storage.yml. For example:
local:
service: Disk
root: <%= Rails.root.join("storage") %>
# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
amazon:
service: S3
access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
region: us-east-1
bucket: your_own_bucket-<%= Rails.env %>
Then, update config/environments/development.rb (or production.rb) to use:
config.active_storage.service = :local # or :amazon for S3
How to Get the Stored File Path in Rails 8 Active Storage
Since Rails stores files in a structured directory inside storage/, the actual file path can be determined using the key stored in the active_storage_blobs table.
Get the File Path in Local Storage
If you’re using the Disk service (default for development and test), you can retrieve the stored file path manually:
Files are stored in the storage/ directory by default.
Use rails_blob_url or service_url to get an accessible URL.
Use variant to generate resized versions.
For production, it’s best to use a cloud storage service like Amazon S3.
Understanding has_one_attached and has_many_attached in Rails 8
Rails 8 provides a built-in way to handle file attachments through Active Storage. The key methods for attaching files to models are:
has_one_attached – For a single file attachment.
has_many_attached – For multiple file attachments.
Let’s break down what they do and why they are useful.
1. has_one_attached
This is used when a model should have a single file attachment. For example, a User model may have only one profile picture.
Usage:
class User < ApplicationRecord
has_one_attached :avatar
end
How It Works:
When you upload a file, Active Storage creates an entry in the active_storage_blobs table.
The active_storage_attachments table links this file to the record.
If a new file is attached, the old one is automatically replaced.
Example: Attaching and Displaying an Image
user = User.find(1)
user.avatar.attach(io: File.open("/path/to/avatar.jpg"), filename: "avatar.jpg", content_type: "image/jpeg")
# Checking if an avatar exists
user.avatar.attached? # => true
# Displaying the image in a view
<%= image_tag user.avatar.variant(resize: "100x100").processed if user.avatar.attached? %>
2. has_many_attached
Use this when a model can have multiple file attachments. For instance, a Product model may have multiple images.
Usage:
class Product < ApplicationRecord
has_many_attached :images
end
How It Works:
Multiple files can be attached to a single record.
Active Storage tracks all file uploads in the active_storage_blobs and active_storage_attachments tables.
Deleting an attachment removes it from storage.
Example: Attaching and Displaying Multiple Images
product = Product.find(1)
product.images.attach([
{ io: File.open("/path/to/image1.jpg"), filename: "image1.jpg", content_type: "image/jpeg" },
{ io: File.open("/path/to/image2.jpg"), filename: "image2.jpg", content_type: "image/jpeg" }
])
# Checking if images exist
product.images.attached? # => true
# Displaying all images in a view
<% if product.images.attached? %>
<% product.images.each do |image| %>
<%= image_tag image.variant(resize: "200x200").processed %>
<% end %>
<% end %>
Benefits of Using has_one_attached & has_many_attached
Simplifies File Attachments – Directly associates files with Active Record models.
No Need for Extra Tables – Unlike some gems (e.g., CarrierWave), Active Storage doesn’t require additional tables for storing file paths.
Easy Cloud Storage Integration – Works seamlessly with Amazon S3, Google Cloud Storage, and Azure.
Variant Processing – Generates resized versions of images using variant (e.g., thumbnails).
Automatic Cleanup – Old attachments are automatically removed when replaced.
Final Thoughts
Active Storage in Rails 8 provides a seamless way to manage file uploads, integrating directly with models while handling storage efficiently. By understanding how it processes uploads internally, we can better optimize performance and ensure a smooth user experience.
In an upcoming blog, we’ll dive deeper into Turbo Streams and how they enhance real-time updates in Rails applications.
SSH (Secure Shell) is used to establish secure remote connections over an unsecured network, enabling secure access, management, and data transfer on remote systems, including running commands, transferring files, and managing applications.
Setup SSH keys:
To create an SSH key and add it to your GitHub account, follow these steps:
1. Generate an SSH Key
ssh-keygen -t ed25519 -C "your-email@example.com"
Replace "your-email@example.com" with your GitHub email.
If prompted, press Enter to save the key in the default location (~/.ssh/id_ed25519).
(If xclip is not installed, use sudo apt install xclip on Linux)
5. Add the SSH Key to GitHub
Go to GitHub → Settings → SSH and GPG keys (GitHub SSH Keys).
Click New SSH Key.
Paste the copied key into the field and give it a title.
Click Add SSH Key.
6. Test the SSH Connection
ssh -T git@github.com
You should see a message like:
Hi username! You've successfully authenticated, but GitHub does not provide shell access.
Now you can clone, push, and pull repositories without entering your GitHub password!
You may be wondering what is ed25519 ?
ed25519 is a modern cryptographic algorithm used for generating SSH keys. It is an alternative to the older RSA algorithm and is considered more secure and faster.
Why Use ed25519 Instead of RSA?
Stronger Security – ed25519 provides 128-bit security, while RSA requires a 4096-bit key for similar security.
Smaller Key Size – The generated keys are much shorter than RSA keys, making them faster to use.
Faster Performance – ed25519 is optimized for speed, especially on modern hardware.
Resistant to Certain Attacks – Unlike RSA, ed25519 is resistant to side-channel attacks.
Why GitHub Recommends ed25519?
Since 2021, GitHub suggests using ed25519 over RSA because of better security and efficiency.
Older RSA keys (e.g., 1024-bit) are now considered weak.
When Should You Use ed25519?
Always, unless you’re working with old systems that do not support it.
If you need maximum security, speed, and smaller key sizes.
Example: Creating an ed25519 SSH Key
ssh-keygen -t ed25519 -C "your-email@example.com"
This creates a strong and secure SSH key for GitHub authentication.
What is the SSH Agent?
The SSH agent is a background process that securely stores your SSH private keys and manages authentication.
Instead of entering your private key passphrase every time you use SSH (e.g., for git push), the agent remembers your key after you add it.
Why Do We Need the SSH Agent?
Avoid Entering Your Passphrase Repeatedly
If your SSH key has a passphrase, you would normally need to enter it every time you use git push or ssh.
The agent caches the key in memory so you don’t need to enter the passphrase every time.
Automatic Authentication
Once the agent has your key, it can sign SSH requests for authentication automatically.
Keeps Your Private Key Secure
Your private key stays in memory and is not exposed on disk after adding it.
What Do These Commands Do?
1️⃣ Start the SSH Agent
eval "$(ssh-agent -s)"
This starts the SSH agent in the background.
The eval "$(ssh-agent -s)" command initializes the agent and prints its process ID (PID), confirming it’s running.
2️⃣ Add the SSH Key to the Agent
ssh-add ~/.ssh/id_ed25519
This adds your private key (~/.ssh/id_ed25519) to the agent.
Now, the agent will remember the key and use it when making SSH connections.
How to Check If Your Key is Added?
Run:
ssh-add -l
This lists all keys currently added to the agent. If your key is listed, it means the agent is working correctly.
Example: How This Helps?
Without the agent:
git push origin main
# Every time, you need to enter your SSH passphrase
With the agent:
ssh-add ~/.ssh/id_ed25519
git push origin main
# No need to enter your passphrase again!
Conclusion
The SSH agent stores your private key securely in memory.
It automates authentication, so you don’t need to enter your passphrase for every SSH request.
Adding the key with ssh-add lets Git and SSH use it without prompting.
Now, you can git push and git pull seamlessly!
But the question is: Can this make unauthorized persons to access with this commands without ssh passphrase?
Does using ssh-agent make your system less secure? Let’s break it down.
Can Someone Else Use My SSH Key Without a Passphrase?
✅ No, If Your System is Secure
The SSH agent only runs under your user session.
Other users on the same machine cannot access your agent.
Your private key is never written to disk in an unprotected way.
The agent only allows authentication, not direct access to your key.
⚠️ Yes, If Someone Gets Access to Your Session
If an attacker gains access to your system (e.g., via malware, or if you leave your computer unlocked), they might use your active SSH agent to authenticate without needing your passphrase.
However, they cannot extract your private key from the agent.
How to Improve Security?
If you want extra security, here are a few things you can do:
1️⃣ Remove Keys from Agent When Not Needed
After using your SSH key, you can remove it from the agent:
ssh-add -D
This removes all stored keys. Next time you push, you’ll need to re-enter your passphrase.
2️⃣ Use -t (Timeout) for Auto Removal
To automatically remove the key after a set time:
ssh-add -t 3600 ~/.ssh/id_ed25519 # Removes the key after 1 hour
3️⃣ Lock Your Screen When Away
If someone gets access to your logged-in session, they could use your agent to authenticate without needing the passphrase.
Always lock your screen (Ctrl + L or Win + L on Windows/Linux, Cmd + Ctrl + Q on Mac) when stepping away.
4️⃣ Disable Agent Forwarding (Extra Security)
By default, SSH agent forwarding (ssh -A) can expose your keys to remote servers. If you don’t need it, disable it by editing:
nano ~/.ssh/config
And adding:
Host *
ForwardAgent no
Summary
The SSH agent only runs in your session, so no one else can access it unless they get control of your user session.
Attackers cannot steal your private key from the agent, but if they have access to your session, they could use it.
To be safe, remove keys when not needed (ssh-add -D), use timeouts (-t), and always lock your computer.
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.
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.
❌ Merge commits are not always bad! Sometimes, they are necessary:
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.
Merging Changes from a Release Branch
Example: Merging release-v1.0 into main should have a merge commit.
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
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.
# 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
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.
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:
Open your shell configuration file: vim ~/.zshrc
Search for the alias section: <esc> / alias <enter>
Add your alias: alias gs="git status"
Save and exit: <esc> :wq
Reload your configuration: source ~/.zshrc
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:
Open VS Code.
Go to Settings → Keyboard Shortcuts.
Search for Toggle Terminal.
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:
Open Command Palette (Cmd + Shift + P) and search for “Lint by RuboCop”.
Go to Extensions Tab and install “VS Code RuboCop”.
In VS Code Settings, search for “Rubocop” and check Ruby -> Rubocop -> Execute Path.
Find the RuboCop installation path: whereis rubocop Example output: ~/.local/share/mise/installs/ruby/3.4.1/bin/rubocop/
Update the Execute Path in VS Code to: ~/.local/share/mise/installs/ruby/3.4.1/bin/
If RuboCop still returns an empty output, check .rubocop.yml in your project: ~/rails/design_studio/.rubocop.yml
If the issue persists, ensure the gem is installed: gem install rubocop
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.
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.
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:
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:
Create a Product model with title and description attributes.
Add a brand_id foreign key as a reference.
Apply a NOT NULL constraint on title (! shortcut).
Generate a corresponding migration file.
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:
Feature
rails g resource
rails 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.
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)
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!
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]
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.
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.