📝 Introduction
Ruby on Rails continues to be one of the most popular web development frameworks, powering applications from startups to enterprise-level systems. Whether you’re starting your Rails journey or looking to master advanced concepts, understanding core Rails principles is essential for building robust, scalable applications.
This comprehensive mastery guide covers 50 essential Ruby on Rails concepts with detailed explanations, real-world examples, and production-ready code snippets. From fundamental MVC patterns to advanced topics like multi-tenancy and performance monitoring, this guide will transform you into a confident Rails developer.
🏗️ Core Rails Concepts
💎 1. Explain the MVC Pattern in Rails
MVC is an architectural pattern that separates responsibilities into three interconnected components:
- Model – Manages data and business logic
- View – Presents data to the user (UI)
- Controller – Orchestrates requests, talks to models, and renders views
This separation keeps our code organized, testable, and maintainable.
🔧 Components & Responsibilities
| Component | Responsibility | Rails Class |
|---|---|---|
| Model | • Data persistence (tables, rows) | app/models/*.rb (e.g. Post) |
| • Business rules & validations | ||
| View | • User interface (HTML, ERB, JSON, etc.) | app/views/*/*.html.erb |
| • Presentation logic (formatting, helpers) | ||
| Controller | • Receives HTTP requests | app/controllers/*_controller.rb |
| • Invokes models & selects views | ||
| • Handles redirects and status codes |
🛠 How It Works: A Request Cycle
- Client → Request
Browser sends, for example,GET /posts/1. - Router → Controller
config/routes.rbmaps toPostsController#show. - Controller → Model
class PostsController < ApplicationController def show @post = Post.find(params[:id]) end end - Controller → View
By default, rendersapp/views/posts/show.html.erb, with access to@post. - View → Response
ERB template generates HTML, sent back to the browser.
✅ Example: Posts Show Action
1. Model (app/models/post.rb)
class Post < ApplicationRecord
validates :title, :body, presence: true
belongs_to :author, class_name: "User"
end
- Defines data schema (via migrations).
- Validates presence.
- Sets up associations.
2. Controller (app/controllers/posts_controller.rb)
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
end
end
- Fetches record with
.find. - Assigns to an instance variable (
@post) for the view.
3. View (app/views/posts/show.html.erb)
<h1><%= @post.title %></h1>
<p>By <%= @post.author.name %></p>
<div><%= simple_format(@post.body) %></div>
- Uses ERB to embed Ruby.
- Displays data and runs helper methods (
simple_format).
🔁 Why MVC Matters
- Separation of Concerns
- Models don’t care about HTML.
- Views don’t talk to the database directly.
- Controllers glue things together.
- Testability
- You can write unit tests for models, view specs, and controller specs independently.
- Scalability
- As your app grows, you know exactly where to add new database logic (models), new pages (views), or new routes/actions (controllers).
🚀 Summary
| Layer | File Location | Key Role |
|---|---|---|
| Model | app/models/*.rb | Data & business logic |
| View | app/views/<controller>/*.erb | Presentation & UI |
| Controller | app/controllers/*_controller.rb | Request handling & flow control |
With MVC in Rails, each piece stays focused on its own job—making your code cleaner and easier to manage.
💎 2. What Is Convention over Configuration?
Description
Convention over Configuration (CoC) is a design principle that minimizes the number of decisions developers need to make by providing sensible defaults.
The framework gives you smart defaults—like expected names and file locations—so you don’t have to set up every detail yourself. You just follow its conventions unless you need something special.
Benefits
- Less boilerplate: You write minimal setup code.
- Faster onboarding: New team members learn the “Rails way” instead of endless configuration options.
- Consistency: Codebases follow uniform patterns, making them easier to read and maintain.
- Productivity boost: Focus on business logic instead of configuration files.
How Rails Leverages CoC
Example 1: Model–Table Mapping
- Convention: A
Usermodel maps to theusersdatabase table. - No config needed: You don’t need to declare
self.table_name = "users"unless your table name differs.
# app/models/user.rb
class User < ApplicationRecord
# Rails assumes: table name = "users"
end
Example 2: Controller–View Lookup
- Convention:
PostsController#showautomatically rendersapp/views/posts/show.html.erb. - No config needed: You don’t need to call
render "posts/show"unless you want a different template.
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
# Rails auto-renders "posts/show.html.erb"
end
end
When to Override
Custom Table Names
class LegacyUser < ApplicationRecord
self.table_name = "legacy_users"
end
Custom Render Paths
class DashboardController < ApplicationController
def index
render template: "admin/dashboard/index"
end
end
Use overrides sparingly, only when your domain truly diverges from Rails’ defaults.
Key Takeaways
Summary
- Convention over Configuration means “adhere to framework defaults unless there’s a strong reason not to.”
- Rails conventions cover naming, file structure, routing, ORM mappings, and more.
- Embracing these conventions leads to cleaner, more consistent, and less verbose code.
💎 3. Explain Rails Directory Structure
Answer: Key directories in a Rails application:
app/
├── controllers/ # Handle HTTP requests
├── models/ # Business logic and data
├── views/ # Templates and UI
├── helpers/ # View helper methods
├── mailers/ # Email handling
└── jobs/ # Background jobs
config/
├── routes.rb # URL routing
├── database.yml # Database configuration
└── application.rb # App configuration
db/
├── migrate/ # Database migrations
└── seeds.rb # Sample data
🗄️ ActiveRecord and Database
Data Normalization:
SQL B-tree Indexing:
Covering, BRIN Indexes:
Analyze Query Performance:
Postgresql Extensions, procedures, triggers, random :
Lear SQL query Writing:
SQL Operators, Join:
💎 4. Explain ActiveRecord Associations
Answer: ActiveRecord provides several association types:
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
has_one :profile
belongs_to :organization, optional: true
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments
has_and_belongs_to_many :tags
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
end
- has_many :through: https://guides.rubyonrails.org/association_basics.html#has-many-through
- has_and_belongs_to_many: https://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many
- Polymorphic Associations https://guides.rubyonrails.org/association_basics.html#polymorphic-associations
- Single Table Inheritance (STI) https://guides.rubyonrails.org/association_basics.html#single-table-inheritance-sti
- Self joins: https://guides.rubyonrails.org/association_basics.html#self-joins
- Composite Primary Keys
💎5. Explain Polymorphic Associations
Answer: Polymorphic associations allow a model to belong to more than one other model on a single association:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Photo < ApplicationRecord
has_many :comments, as: :commentable
end
# Migration
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.text :content
t.references :commentable, polymorphic: true, null: false
t.timestamps
end
end
end
# Usage
post = Post.first
post.comments.create(content: "Great post!")
photo = Photo.first
photo.comments.create(content: "Nice photo!")
# Querying
Comment.where(commentable_type: 'Post')
💎 6. What are Single Table Inheritance(STI) and its alternatives?
Answer: STI stores multiple models in one table using a type column:
# STI Implementation
class Animal < ApplicationRecord
validates :type, presence: true
end
class Dog < Animal
def bark
"Woof!"
end
end
class Cat < Animal
def meow
"Meow!"
end
end
# Migration
class CreateAnimals < ActiveRecord::Migration[7.0]
def change
create_table :animals do |t|
t.string :type, null: false
t.string :name
t.string :breed # Only for dogs
t.boolean :indoor # Only for cats
t.timestamps
end
add_index :animals, :type
end
end
# Alternative: Multiple Table Inheritance (MTI)
class Animal < ApplicationRecord
has_one :dog
has_one :cat
end
class Dog < ApplicationRecord
belongs_to :animal
end
class Cat < ApplicationRecord
belongs_to :animal
end
💎 7. What are Database Migrations?
Answer: Migrations are Ruby classes that define database schema changes in a version-controlled way.
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, index: { unique: true }
t.timestamps
end
end
end
# Adding a column later
class AddAgeToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :age, :integer
end
end
- Add References to migration: https://guides.rubyonrails.org/active_record_migrations.html#creating-associations
- Passing Modifiers: https://guides.rubyonrails.org/active_record_migrations.html#passing-modifiers
💎 8. Explain Database Transactions and Isolation Levels
Answer: Transactions ensure data consistency and handle concurrent access:
# Basic transaction
ActiveRecord::Base.transaction do
user = User.create!(name: "John")
user.posts.create!(title: "First Post")
# If any operation fails, everything rolls back
end
# Nested transactions with savepoints
User.transaction do
user = User.create!(name: "John")
begin
User.transaction(requires_new: true) do
# This creates a savepoint
user.posts.create!(title: "") # This will fail
end
rescue ActiveRecord::RecordInvalid
# Inner transaction rolled back, but outer continues
end
user.posts.create!(title: "Valid Post") # This succeeds
end
# Manual transaction control
ActiveRecord::Base.transaction do
user = User.create!(name: "John")
if some_condition
raise ActiveRecord::Rollback # Forces rollback
end
end
# Isolation levels (database-specific)
User.transaction(isolation: :serializable) do
# Highest isolation level
end
💎 8. Explain Database Indexing in Rails
Answer: Indexes improve query performance by creating faster lookup paths:
class AddIndexesToUsers < ActiveRecord::Migration[7.0]
def change
add_index :users, :email, unique: true
add_index :users, [:first_name, :last_name]
add_index :posts, :user_id
add_index :posts, [:user_id, :created_at]
end
end
# In model validations that should have indexes
class User < ApplicationRecord
validates :email, uniqueness: true # Should have unique index
end
Read more(Premium): https://railsdrop.com/ruby-on-rails-mastery-guide-sql-indexing/
💎 15. How do you prevent SQL Injection?
Answer: Use parameterized queries and ActiveRecord methods:
# BAD: Vulnerable to SQL injection
User.where("name = '#{params[:name]}'")
# GOOD: Parameterized queries
User.where(name: params[:name])
User.where("name = ?", params[:name])
User.where("name = :name", name: params[:name])
# For complex queries
User.where("created_at > ? AND status = ?", 1.week.ago, 'active')
💎 9. Explain N+1 Query Problem and Solutions
The N+1 query problem is a performance anti-pattern in database access—especially common in Rails when using Active Record. It occurs when your application executes 1 query to fetch a list of records and then N additional queries to fetch associated records for each item in the list.
🧨 What is the N+1 Query Problem?
Imagine you fetch all posts, and for each post, you access its author. Without optimization, Rails will execute:
- 1 query to fetch all posts
- N queries (one per post) to fetch each author individually
→ That’s N+1 total queries instead of the ideal 2.
❌ Example 1 – Posts and Authors (N+1)
# model
class Post
belongs_to :author
end
# controller
@posts = Post.all
# view (ERB or JSON)
@posts.each do |post|
puts post.author.name
end
🔍 Generated SQL:
SELECT * FROM posts;
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...
- If you have 100 posts, that’s 101 queries! 😬
✅ Solution: Use includes to Eager Load
@posts = Post.includes(:author)
Now Rails loads all authors in one additional query:
SELECT * FROM posts;
SELECT * FROM users WHERE id IN (1, 2, 3, ...);
Only 2 queries no matter how many posts!
❌ Example 2 – Comments and Post Titles (N+1)
# model
class Comment
belongs_to :post
end
# controller
@comments = Comment.all
# view (ERB or JSON)
@comments.each do |comment|
puts comment.post.title
end
Each call to comment.post will trigger a separate DB query.
✅ Fix: Eager Load with includes
@comments = Comment.includes(:post)
Rails will now load posts in a single query, fixing the N+1 issue.
🔄 Other Fixes
| Fix | Usage |
|---|---|
includes(:assoc) | Eager loads associations (default lazy join) |
preload(:assoc) | Always runs a separate query for association |
eager_load(:assoc) | Uses LEFT OUTER JOIN to load in one query |
joins(:assoc) | For filtering/sorting only, not eager loading |
🧪 How to Detect N+1 Problems
- Use tools like:
- ✅ Bullet gem – shows alerts in dev when N+1 queries happen
- ✅ New Relic / Skylight / Scout – for performance monitoring
📝 Summary
| 🔥 Problem | ❌ Post.all + post.author in loop |
|---|---|
| ✅ Solution | Post.includes(:author) |
| ✅ Benefit | Prevents N+1 DB queries, boosts performance |
| ✅ Tooling | Bullet gem to catch during dev |
💎 9. What Are Scopes 🎯 in ActiveRecord?
Scopes in Rails are custom, chainable queries defined on your model. They let you write readable and reusable query logic.
Instead of repeating complex conditions in controllers or models, you wrap them in scopes.
✅ Why Use Scopes?
- Clean and DRY code
- Chainable like
.where,.order - Improves readability and maintainability
- Keeps controllers slim
🔧 How to Define a Scope?
Use the scope method in your model:
class Product < ApplicationRecord
scope :available, -> { where(status: 'available') }
scope :recent, -> { order(created_at: :desc) }
end
🧪 How to Use a Scope?
Product.available # SELECT * FROM products WHERE status = 'available';
Product.recent # SELECT * FROM products ORDER BY created_at DESC;
Product.available.recent # Chained query!
👉 Example: A Blog App with Scopes
📝 Post model
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :recent, -> { order(created_at: :desc) }
end
💡 Usage in Controller
# posts_controller.rb
@posts = Post.published.by_author(current_user.id).recent
# Behind
# 🔍 Parameterized SQL
SELECT "posts".*
FROM "posts"
WHERE "posts"."published" = $1
AND "posts"."author_id" = $2
ORDER BY "posts"."created_at" DESC
# 📥 Bound Values
# $1 = true, $2 = current_user.id (e.g. 5)
# with Interpolated Values
SELECT "posts".*
FROM "posts"
WHERE "posts"."published" = TRUE
AND "posts"."author_id" = 5
ORDER BY "posts"."created_at" DESC;
🔁 Dynamic Scopes with Parameters
scope :with_min_views, ->(count) { where("views >= ?", count) }
Post.with_min_views(100)
# SELECT * FROM posts WHERE views >= 100;
⚠️ Do NOT Do This (Bad Practice)
Avoid putting complex logic or too many .joins and .includes inside scopes—it can make debugging hard and queries less readable.
🧼 Pro Tip
Scopes are just ActiveRecord::Relation objects, so you can chain, merge, and lazily load them just like regular queries.
Post.published.limit(5).offset(10)
🚀 Summary
| Feature | Description |
|---|---|
| What? | Named, chainable queries |
| Syntax | scope :name, -> { block } |
| Use | Model.scope_name |
| Benefit | DRY, readable, reusable query logic |
| Best Use | Repeated filters, ordering, limits |
⚙️ Why Use scope Instead of Class Methods?
Read more(Premium): https://railsdrop.com/ruby-on-rails-mastery-guide-scope-vs-class-methods/
💎 9. Explain RESTful Routes in Rails
Answer: Rails follows REST conventions for resource routing:
# config/routes.rb
Rails.application.routes.draw do
resources :posts do
resources :comments, except: [:show]
member do
patch :publish
end
collection do
get :drafts
end
end
end
# Generated routes:
# GET /posts (index)
# GET /posts/new (new)
# POST /posts (create)
# GET /posts/:id (show)
# GET /posts/:id/edit (edit)
# PATCH /posts/:id (update)
# DELETE /posts/:id (destroy)
# PATCH /posts/:id/publish (custom member)
# GET /posts/drafts (custom collection)
Read more(Premium): https://railsdrop.com/ruby-on-rails-mastery-guide-restful-routes-in-rails/
💎 11. Explain Rails Route Constraints and Custom Constraints
Answer: Route constraints allow conditional routing:
# Built-in constraints
Rails.application.routes.draw do
# Subdomain constraint
constraints subdomain: 'api' do
namespace :api do
resources :users
end
end
# IP constraint
constraints ip: /192\.168\.1\.\d+/ do
get '/admin' => 'admin#index'
end
# Lambda constraints
constraints ->(req) { req.remote_ip == '127.0.0.1' } do
mount Sidekiq::Web => '/sidekiq'
end
# Parameter format constraints
get '/posts/:id', to: 'posts#show', constraints: { id: /\d+/ }
get '/posts/:slug', to: 'posts#show_by_slug'
end
# Custom constraint classes
class MobileConstraint
def matches?(request)
request.user_agent =~ /Mobile|webOS/
end
end
class AdminConstraint
def matches?(request)
return false unless request.session[:user_id]
User.find(request.session[:user_id]).admin?
end
end
# Usage
Rails.application.routes.draw do
constraints MobileConstraint.new do
root 'mobile#index'
end
constraints AdminConstraint.new do
mount Sidekiq::Web => '/sidekiq'
end
root 'home#index' # Default route
end
💎 16. Explain Mass Assignment Protection
Answer: Prevent unauthorized attribute updates using Strong Parameters:
# Model with attr_accessible (older Rails)
class User < ApplicationRecord
attr_accessible :name, :email # Only these can be mass assigned
end
# Modern Rails with Strong Parameters
class UsersController < ApplicationController
def update
if @user.update(user_params)
redirect_to @user
else
render :edit
end
end
private
def user_params
params.require(:user).permit(:name, :email)
# :admin, :role are not permitted
end
end
💎 10. What Are Strong Parameters in Rails?
🔐 Definition
Strong Parameters are a feature in Rails that prevents mass assignment vulnerabilities by explicitly permitting only the safe parameters from the params hash (are allowed to pass in) before saving/updating a model.
⚠️ Why They’re Important
Before Rails 4, using code like this was dangerous:
User.create(params[:user])
If the form included admin: true, any user could make themselves an admin!
Read more(Premium): https://railsdrop.com/ruby-on-rails-mastery-guide-strong-parameters/
📦 Real Form Params Example
Suppose this form is submitted:
<input name="post[title]" />
<input name="post[body]" />
<input name="post[admin]" /> <!-- a sneaky parameter -->
Then in Rails:
params[:post]
# => { "title" => "...", "body" => "...", "admin" => "true" }
But post_params only allows title and body, so admin is discarded silently.
✅ Summary Table
| ✅ Purpose | ✅ How It Helps |
|---|---|
| Prevents mass assignment | Avoids unwanted model attributes from being set |
| Requires explicit whitelisting | Forces you to permit only known-safe keys |
| Works with nested data | Supports permit(sub_attributes: [...]) |
💎 11. Explain Before/After Actions (Filters)
Answer: Filters run code before, after, or around controller actions:
⚙️ What Are Before/After Actions in Rails?
🧼 Definition
Before, after, and around filters are controller-level callbacks that run before or after controller actions. They help you extract repeated logic, like authentication, logging, or setup.
⏱️ Types of Filters
| Filter Type | When It Runs | Common Use |
|---|---|---|
before_action | Before the action executes | Set variables, authenticate user |
after_action | After the action finishes | Log activity, clean up data |
around_action | Wraps around the action | Benchmarking, transactions |
🛠️ Example Controller Using Filters
# controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
after_action :log_post_access, only: :show
def show
# @post is already set by before_action
end
def edit
# @post is already set by before_action
end
def update
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
def destroy
if @post.destroy
.....
end
private
def set_post
@post = Post.find(params[:id])
end
def authenticate_user!
redirect_to login_path unless current_user
end
def log_post_access
Rails.logger.info "Post #{@post.id} was viewed by #{current_user&.email || 'guest'}"
end
def post_params
params.require(:post).permit(:title, :body)
end
end
Read about sessions: https://railsdrop.com/understanding-sessions-in-web-and-ruby-on-rails/
📌 Filter Options
You can control when filters apply using these options:
| Option | Description | Example |
|---|---|---|
only | Apply filter only to listed actions | before_action :set_post, only: [:edit] |
except | Skip filter for listed actions | before_action :set_post, except: [:index] |
if | Run filter only if condition is true | before_action :check_admin, if: :admin? |
unless | Run filter unless condition is true | before_action :check_guest, unless: :logged_in? |
🧠 Real-World Use Cases
| Filter | Real Use Case |
|---|---|
before_action | Set current user, load model, check permissions |
after_action | Track usage, log changes, clear flash messages |
around_action | Wrap DB transactions, measure performance |
✅ Summary
before_action: runs before controller methods – setup, auth, etc.after_action: runs after the action – logging, cleanup.around_action: runs code both before and after an action – great for benchmarking.
Rails filters help clean your controllers by reusing logic across multiple actions safely and consistently.
Let me know if you want examples using skip_before_action, concerns, or inheritance rules!
💎 12. Explain Partials and their Benefits
Answer: Partials are reusable view templates that help maintain DRY principles:
<!-- app/views/shared/_user_card.html.erb -->
<div class="user-card">
<h3><%= user.name %></h3>
<p><%= user.email %></p>
</div>
<!-- Usage in views -->
<%= render 'shared/user_card', user: @user %>
<!-- Rendering collections -->
<%= render partial: 'shared/user_card', collection: @users, as: :user %>
<!-- With locals -->
<%= render 'shared/user_card', user: @user, show_email: false %>
💎 13. What are View Helpers?
Answer: Helpers contain methods available in views to keep logic out of templates:
# app/helpers/application_helper.rb
module ApplicationHelper
def current_page_class(path)
'active' if current_page?(path)
end
def format_date(date)
date&.strftime('%B %d, %Y')
end
def truncate_words(text, length = 20)
text.split.first(length).join(' ') + (text.split.size > length ? '...' : '')
end
end
# Usage in views
<%= link_to "Home", root_path, class: current_page_class(root_path) %>
<%= format_date(@post.created_at) %>
💎 14. Explain CSRF Protection in Rails
Answer: Cross-Site Request Forgery protection prevents unauthorized commands from being transmitted:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
# or
protect_from_forgery with: :null_session # for APIs
end
# In forms, Rails automatically adds CSRF tokens
<%= form_with model: @user do |form| %>
<!-- csrf_meta_tags in layout adds token to header -->
<% end %>
# For AJAX requests
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
}
});
Read more(Premium): https://railsdrop.com/2025/05/11/a-complete-guide-to-ruby-on-rails-security-measures/
https://railsdrop.com/2025/06/09/essential-web-security-attacks-every-developer-must-know/
💎 17. Explain Caching Strategies in Rails
Answer: Rails provides multiple caching mechanisms:
# Fragment Caching
<% cache @post do %>
<%= render @post %>
<% end %>
# Russian Doll Caching
<% cache [@post, @post.comments.maximum(:updated_at)] do %>
<%= render @post %>
<%= render @post.comments %>
<% end %>
# Low-level caching
class PostsController < ApplicationController
def expensive_operation
Rails.cache.fetch("expensive_operation_#{params[:id]}", expires_in: 1.hour) do
# Expensive computation here
calculate_complex_data
end
end
end
# Query caching (automatic in Rails)
# HTTP caching
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
if stale?(last_modified: @post.updated_at, etag: @post)
# Render the view
end
end
end
💎 18. What is Eager Loading and when to use it?
Answer: Eager loading reduces database queries by loading associated records upfront:
# includes: Loads all data in separate queries
posts = Post.includes(:author, :comments)
# joins: Uses SQL JOIN (no access to associated records)
posts = Post.joins(:author).where(authors: { active: true })
# preload: Always uses separate queries
posts = Post.preload(:author, :comments)
# eager_load: Always uses LEFT JOIN
posts = Post.eager_load(:author, :comments)
# Use when you know you'll access the associations
posts.each do |post|
puts "#{post.title} by #{post.author.name}"
puts "Comments: #{post.comments.count}"
end
💎 19. How do you optimize database queries?
Answer: Several strategies for query optimization:
# Use select to limit columns
User.select(:id, :name, :email).where(active: true)
# Use pluck for single values
User.where(active: true).pluck(:email)
# Use exists? instead of present?
User.where(role: 'admin').exists? # vs .present?
# Use counter_cache for counts
class Post < ApplicationRecord
belongs_to :user, counter_cache: true
end
# Migration to add counter cache
add_column :users, :posts_count, :integer, default: 0
# Use find_each for large datasets
User.find_each(batch_size: 1000) do |user|
user.update_some_attribute
end
# Database indexes for frequently queried columns
add_index :posts, [:user_id, :published_at]
💎 20. Explain different types of tests in Rails
Answer: Rails supports multiple testing levels:
# Unit Tests (Model tests)
require 'test_helper'
class UserTest < ActiveSupport::TestCase
test "should not save user without email" do
user = User.new
assert_not user.save
end
test "should save user with valid attributes" do
user = User.new(name: "John", email: "john@example.com")
assert user.save
end
end
# Integration Tests (Controller tests)
class UsersControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get users_url
assert_response :success
end
test "should create user" do
assert_difference('User.count') do
post users_url, params: { user: { name: "John", email: "john@test.com" } }
end
assert_redirected_to user_url(User.last)
end
end
# System Tests (Feature tests)
class UsersSystemTest < ApplicationSystemTestCase
test "creating a user" do
visit users_path
click_on "New User"
fill_in "Name", with: "John Doe"
fill_in "Email", with: "john@example.com"
click_on "Create User"
assert_text "User was successfully created"
end
end
💎 21. What are Fixtures vs Factories?
Answer: Both provide test data, but with different approaches:
# Fixtures (YAML files)
# test/fixtures/users.yml
john:
name: John Doe
email: john@example.com
jane:
name: Jane Smith
email: jane@example.com
# Usage
user = users(:john)
# Factories (using FactoryBot)
# test/factories/users.rb
FactoryBot.define do
factory :user do
name { "John Doe" }
email { Faker::Internet.email }
trait :admin do
role { 'admin' }
end
factory :admin_user, traits: [:admin]
end
end
# Usage
user = create(:user)
admin = create(:admin_user)
build(:user) # builds but doesn't save
💎 22. Explain ActiveJob and Background Processing
Answer: ActiveJob provides a unified interface for background jobs:
# Job class
class EmailJob < ApplicationJob
queue_as :default
retry_on StandardError, wait: 5.seconds, attempts: 3
def perform(user_id, email_type)
user = User.find(user_id)
UserMailer.send(email_type, user).deliver_now
end
end
# Enqueue jobs
EmailJob.perform_later(user.id, :welcome)
EmailJob.set(wait: 1.hour).perform_later(user.id, :reminder)
# With Sidekiq
class EmailJob < ApplicationJob
queue_as :high_priority
sidekiq_options retry: 3, backtrace: true
def perform(user_id)
# Job logic
end
end
💎 23. What are Rails Engines?
Answer: Engines are miniature applications that provide functionality to host applications:
# Creating an engine
rails plugin new blog --mountable
# Engine structure
module Blog
class Engine < ::Rails::Engine
isolate_namespace Blog
config.generators do |g|
g.test_framework :rspec
end
end
end
# Mounting in host app
Rails.application.routes.draw do
mount Blog::Engine => "/blog"
end
# Engine can have its own models, controllers, views
# app/models/blog/post.rb
module Blog
class Post < ApplicationRecord
end
end
💎 24. Explain Action Cable and WebSockets
Answer: Action Cable integrates WebSockets with Rails for real-time features:
# Channel
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room_id]}"
end
def receive(data)
ActionCable.server.broadcast("chat_#{params[:room_id]}", {
message: data['message'],
user: current_user.name
})
end
end
# JavaScript
const subscription = consumer.subscriptions.create(
{ channel: "ChatChannel", room_id: 1 },
{
received(data) {
document.getElementById('messages').insertAdjacentHTML('beforeend',
`<p><strong>${data.user}:</strong> ${data.message}</p>`
);
}
}
);
# Broadcasting from controllers/models
ActionCable.server.broadcast("chat_1", { message: "Hello!" })
💎 25. What is the Rails Asset Pipeline?
Answer: The asset pipeline concatenates, minifies, and serves web assets:
# app/assets/stylesheets/application.css
/*
*= require_tree .
*= require_self
*/
# app/assets/javascripts/application.js
//= require rails-ujs
//= require_tree .
# In views
<%= stylesheet_link_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
# For production
config.assets.precompile += %w( admin.js admin.css )
# Using Webpacker (modern Rails)
# app/javascript/packs/application.js
import Rails from "@rails/ujs"
import "channels"
Rails.start()
💎 26. Explain Service Objects Pattern
Answer: Service objects encapsulate business logic that doesn’t belong in models or controllers:
class UserRegistrationService
include ActiveModel::Model
attr_accessor :name, :email, :password
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, length: { minimum: 8 }
def call
return false unless valid?
ActiveRecord::Base.transaction do
user = create_user
send_welcome_email(user)
create_default_profile(user)
user
end
rescue => e
errors.add(:base, e.message)
false
end
private
def create_user
User.create!(name: name, email: email, password: password)
end
def send_welcome_email(user)
UserMailer.welcome(user).deliver_later
end
def create_default_profile(user)
user.create_profile!(name: name)
end
end
# Usage
service = UserRegistrationService.new(user_params)
if service.call
redirect_to dashboard_path
else
@errors = service.errors
render :new
end
💎 27. What are Rails Concerns?
Answer: Concerns provide a way to share code between models or controllers:
# app/models/concerns/timestampable.rb
module Timestampable
extend ActiveSupport::Concern
included do
scope :recent, -> { order(created_at: :desc) }
scope :from_last_week, -> { where(created_at: 1.week.ago..) }
end
class_methods do
def cleanup_old_records
where('created_at < ?', 1.year.ago).destroy_all
end
end
def age_in_days
(Time.current - created_at) / 1.day
end
end
# Usage in models
class Post < ApplicationRecord
include Timestampable
end
class Comment < ApplicationRecord
include Timestampable
end
# Controller concerns
module Authentication
extend ActiveSupport::Concern
included do
before_action :authenticate_user!
end
private
def authenticate_user!
redirect_to login_path unless user_signed_in?
end
end
💎 28. Explain Rails API Mode
Answer: Rails can run in API-only mode for building JSON APIs:
# Generate API-only application
rails new my_api --api
# API controller
class ApplicationController < ActionController::API
include ActionController::HttpAuthentication::Token::ControllerMethods
before_action :authenticate
private
def authenticate
authenticate_or_request_with_http_token do |token, options|
ApiKey.exists?(token: token)
end
end
end
class UsersController < ApplicationController
def index
users = User.all
render json: users, each_serializer: UserSerializer
end
def create
user = User.new(user_params)
if user.save
render json: user, serializer: UserSerializer, status: :created
else
render json: { errors: user.errors }, status: :unprocessable_entity
end
end
end
# Serializer
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email, :created_at
has_many :posts
end
💎 29. What is Rails Autoloading?
Answer: Rails automatically loads classes and modules on demand:
# Rails autoloading rules:
# app/models/user.rb -> User
# app/models/admin/user.rb -> Admin::User
# app/controllers/posts_controller.rb -> PostsController
# Eager loading in production
config.eager_load = true
# Custom autoload paths
config.autoload_paths << Rails.root.join('lib')
# Zeitwerk (Rails 6+) autoloader
config.autoloader = :zeitwerk
# Reloading in development
config.cache_classes = false
config.reload_classes_only_on_change = true
💎 30. Explain Rails Credentials and Secrets
Answer: Rails provides encrypted credentials for sensitive data:
# Edit credentials
rails credentials:edit
# credentials.yml.enc content
secret_key_base: abc123...
database:
password: secretpassword
aws:
access_key_id: AKIAIOSFODNN7EXAMPLE
secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Usage in application
Rails.application.credentials.database[:password]
Rails.application.credentials.aws[:access_key_id]
# Environment-specific credentials
rails credentials:edit --environment production
# In production
RAILS_MASTER_KEY=your_master_key rails server
💎 31. How do you handle file uploads in Rails?
Answer: Using Active Storage (Rails 5.2+):
# Model
class User < ApplicationRecord
has_one_attached :avatar
has_many_attached :documents
validate :acceptable_avatar
private
def acceptable_avatar
return unless avatar.attached?
unless avatar.blob.byte_size <= 1.megabyte
errors.add(:avatar, "is too big")
end
acceptable_types = ["image/jpeg", "image/png"]
unless acceptable_types.include?(avatar.blob.content_type)
errors.add(:avatar, "must be a JPEG or PNG")
end
end
end
# Controller
def user_params
params.require(:user).permit(:name, :email, :avatar, documents: [])
end
# View
<%= form_with model: @user do |form| %>
<%= form.file_field :avatar %>
<%= form.file_field :documents, multiple: true %>
<% end %>
# Display
<%= image_tag @user.avatar if @user.avatar.attached? %>
<%= link_to "Download", @user.avatar, download: true %>
Read more(Premium): https://railsdrop.com/2025/03/31/setup-rails-8-app-part-5-active-storage-file-uploads/
💎32. What are Rails Callbacks and when to use them?
Answer: Callbacks are hooks that run at specific points in an object’s lifecycle:
class User < ApplicationRecord
before_validation :normalize_email
before_create :generate_auth_token
after_create :send_welcome_email
before_destroy :cleanup_associated_data
private
def normalize_email
self.email = email.downcase.strip if email.present?
end
def generate_auth_token
self.auth_token = SecureRandom.hex(32)
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
def cleanup_associated_data
# Clean up associated records
posts.destroy_all
end
end
# Conditional callbacks
class Post < ApplicationRecord
after_save :update_search_index, if: :published?
before_destroy :check_if_deletable, unless: :admin_user?
end
💎 36. How do you handle Race Conditions in Rails?
Answer: Several strategies to prevent race conditions:
# 1. Optimistic Locking
class Post < ApplicationRecord
# Migration adds lock_version column
end
# Usage
post = Post.find(1)
post.title = "Updated Title"
begin
post.save!
rescue ActiveRecord::StaleObjectError
# Handle conflict - reload and retry
post.reload
post.title = "Updated Title"
post.save!
end
# 2. Pessimistic Locking
Post.transaction do
post = Post.lock.find(1) # SELECT ... FOR UPDATE
post.update!(view_count: post.view_count + 1)
end
# 3. Database constraints and unique indexes
class User < ApplicationRecord
validates :email, uniqueness: true
end
# Migration with unique constraint
add_index :users, :email, unique: true
# 4. Atomic operations
# BAD: Race condition possible
user = User.find(1)
user.update!(balance: user.balance + 100)
# GOOD: Atomic update
User.where(id: 1).update_all("balance = balance + 100")
# 5. Redis for distributed locks
class DistributedLock
def self.with_lock(key, timeout: 10)
lock_acquired = Redis.current.set(key, "locked", nx: true, ex: timeout)
if lock_acquired
begin
yield
ensure
Redis.current.del(key)
end
else
raise "Could not acquire lock"
end
end
end
💎 38. What are Rails Generators and how do you create custom ones?
Answer: Generators automate file creation and boilerplate code:
# Built-in generators
rails generate model User name:string email:string
rails generate controller Users index show
rails generate migration AddAgeToUsers age:integer
# Custom generator
# lib/generators/service/service_generator.rb
class ServiceGenerator < Rails::Generators::NamedBase
source_root File.expand_path('templates', __dir__)
argument :methods, type: :array, default: [], banner: "method method"
class_option :namespace, type: :string, default: "Services"
def create_service_file
template "service.rb.erb", "app/services/#{file_name}_service.rb"
end
def create_service_test
template "service_test.rb.erb", "test/services/#{file_name}_service_test.rb"
end
private
def service_class_name
"#{class_name}Service"
end
def namespace_class
options[:namespace]
end
end
# Usage
rails generate service UserRegistration create_user send_email --namespace=Auth
💎 39. Explain Rails Middleware and how to create custom middleware
Answer: Middleware sits between the web server and Rails application:
# View current middleware stack
rake middleware
# Custom middleware
class RequestTimingMiddleware
def initialize(app)
@app = app
end
def call(env)
start_time = Time.current
# Process request
status, headers, response = @app.call(env)
end_time = Time.current
duration = ((end_time - start_time) * 1000).round(2)
# Add timing header
headers['X-Request-Time'] = "#{duration}ms"
# Log slow requests
if duration > 1000
Rails.logger.warn "Slow request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']} took #{duration}ms"
end
[status, headers, response]
end
end
# Authentication middleware
class ApiAuthenticationMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
if api_request?(request)
return unauthorized_response unless valid_api_key?(request)
end
@app.call(env)
end
private
def api_request?(request)
request.path.start_with?('/api/')
end
def valid_api_key?(request)
api_key = request.headers['X-API-Key']
ApiKey.exists?(key: api_key, active: true)
end
def unauthorized_response
[401, {'Content-Type' => 'application/json'}, ['{"error": "Unauthorized"}']]
end
end
# Register middleware in application.rb
config.middleware.use RequestTimingMiddleware
config.middleware.insert_before ActionDispatch::Static, ApiAuthenticationMiddleware
# Conditional middleware
if Rails.env.development?
config.middleware.use MyDevelopmentMiddleware
end
💎 40. How do you implement Full-Text Search in Rails?
Answer: Several approaches for implementing search functionality:
Read Postgres Extension: https://pgxn.org/dist/pg_search/
Or use Gem: https://github.com/Casecommons/pg_search
Elasticsearch with Searchkick: https://github.com/ankane/searchkick
# 1. Database-specific full-text search (PostgreSQL)
class Post < ApplicationRecord
include PgSearch::Model
pg_search_scope :search_by_content,
against: [:title, :content],
using: {
tsearch: {
prefix: true,
any_word: true
},
trigram: {
threshold: 0.3
}
}
end
# Migration for PostgreSQL
class AddSearchToPost < ActiveRecord::Migration[7.0]
def up
execute "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
execute "CREATE EXTENSION IF NOT EXISTS unaccent;"
add_column :posts, :searchable, :tsvector
add_index :posts, :searchable, using: :gin
execute <<-SQL
CREATE OR REPLACE FUNCTION update_post_searchable() RETURNS trigger AS $$
BEGIN
NEW.searchable := to_tsvector('english', coalesce(NEW.title, '') || ' ' || coalesce(NEW.content, ''));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_post_searchable_trigger
BEFORE INSERT OR UPDATE ON posts
FOR EACH ROW EXECUTE FUNCTION update_post_searchable();
SQL
end
end
# 2. Elasticsearch with Searchkick
class Post < ApplicationRecord
searchkick word_start: [:title], highlight: [:title, :content]
def search_data
{
title: title,
content: content,
author: author.name,
published_at: published_at,
tags: tags.pluck(:name)
}
end
end
# Usage
results = Post.search("ruby rails",
fields: [:title^2, :content],
highlight: true,
aggs: {
tags: {},
authors: { field: "author" }
}
)
# 3. Simple database search with scopes
class Post < ApplicationRecord
scope :search, ->(term) {
return none if term.blank?
terms = term.split.map { |t| "%#{t}%" }
query = terms.map { "title ILIKE ? OR content ILIKE ?" }.join(" AND ")
values = terms.flat_map { |t| [t, t] }
where(query, *values)
}
scope :search_advanced, ->(params) {
results = all
if params[:title].present?
results = results.where("title ILIKE ?", "%#{params[:title]}%")
end
if params[:author].present?
results = results.joins(:author).where("users.name ILIKE ?", "%#{params[:author]}%")
end
if params[:tags].present?
tag_names = params[:tags].split(',').map(&:strip)
results = results.joins(:tags).where(tags: { name: tag_names })
end
results.distinct
}
end
🎯 Expert-Level Questions (41-45)
💎 41. Rails Request Lifecycle and Internal Processing
- Deep dive into how Rails processes requests from web server to response
- Middleware stack visualization and custom middleware
- Controller action execution order and benchmarking
# 1. Web Server receives request (Puma/Unicorn)
# 2. Rack middleware stack processes request
# 3. Rails Router matches the route
# 4. Controller instantiation and action execution
# 5. View rendering and response
# Detailed Request Flow:
class ApplicationController < ActionController::Base
around_action :log_request_lifecycle
private
def log_request_lifecycle
Rails.logger.info "1. Before controller action: #{controller_name}##{action_name}"
start_time = Time.current
yield # Execute the controller action
end_time = Time.current
Rails.logger.info "2. After controller action: #{(end_time - start_time) * 1000}ms"
end
end
# Middleware Stack Visualization
Rails.application.middleware.each_with_index do |middleware, index|
puts "#{index}: #{middleware.inspect}"
end
# Custom Middleware in the Stack
class RequestIdMiddleware
def initialize(app)
@app = app
end
def call(env)
env['HTTP_X_REQUEST_ID'] ||= SecureRandom.uuid
@app.call(env)
end
end
# Route Constraints and Processing
Rails.application.routes.draw do
# Routes are checked in order of definition
get '/posts/:id', to: 'posts#show', constraints: { id: /\d+/ }
get '/posts/:slug', to: 'posts#show_by_slug'
# Catch-all route (should be last)
match '*path', to: 'application#not_found', via: :all
end
# Controller Action Execution Order
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update]
around_action :benchmark_action
after_action :log_user_activity
def show
# Main action logic
@related_posts = Post.where.not(id: @post.id).limit(5)
end
private
def benchmark_action
start_time = Time.current
yield
Rails.logger.info "Action took: #{Time.current - start_time}s"
end
end
💎 42. Multi-tenancy Implementation
- Schema-based multi-tenancy with Apartment gem
https://github.com/influitive/apartment - Row-level multi-tenancy with default scopes
- Hybrid Approach with Acts As Tenant: https://github.com/ErwinM/acts_as_tenant
- Database-level multi-tenancy with Middleware hack
- RLS (Row Level Security) in PostgreSQL
# 1. Schema-based Multi-tenancy (Apartment gem)
# config/application.rb
require 'apartment'
Apartment.configure do |config|
config.excluded_models = ["User", "Tenant"]
config.tenant_names = lambda { Tenant.pluck(:subdomain) }
end
class ApplicationController < ActionController::Base
before_action :set_current_tenant
private
def set_current_tenant
subdomain = request.subdomain
tenant = Tenant.find_by(subdomain: subdomain)
if tenant
Apartment::Tenant.switch!(tenant.subdomain)
else
redirect_to root_url(subdomain: false)
end
end
end
# 2. Row-level Multi-tenancy (with default scopes)
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
belongs_to :tenant, optional: true
default_scope { where(tenant: Current.tenant) if Current.tenant }
def self.unscoped_for_tenant
unscoped.where(tenant: Current.tenant)
end
end
class Current < ActiveSupport::CurrentAttributes
attribute :tenant, :user
def tenant=(tenant)
super
Time.zone = tenant.time_zone if tenant&.time_zone
end
end
# 3. Hybrid Approach with Acts As Tenant
class User < ApplicationRecord
acts_as_tenant(:account)
validates :email, uniqueness: { scope: :account_id }
end
class Account < ApplicationRecord
has_many :users, dependent: :destroy
def switch_tenant!
ActsAsTenant.current_tenant = self
end
end
# 4. Database-level Multi-tenancy
class TenantMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
tenant_id = extract_tenant_id(request)
if tenant_id
ActiveRecord::Base.connection.execute(
"SET app.current_tenant_id = '#{tenant_id}'"
)
end
@app.call(env)
ensure
ActiveRecord::Base.connection.execute(
"SET app.current_tenant_id = ''"
)
end
private
def extract_tenant_id(request)
# Extract from subdomain, header, or JWT token
request.subdomain.presence ||
request.headers['X-Tenant-ID'] ||
decode_tenant_from_jwt(request.headers['Authorization'])
end
end
# 5. RLS (Row Level Security) in PostgreSQL
class AddRowLevelSecurity < ActiveRecord::Migration[7.0]
def up
# Enable RLS on posts table
execute "ALTER TABLE posts ENABLE ROW LEVEL SECURITY;"
# Create policy for tenant isolation
execute <<-SQL
CREATE POLICY tenant_isolation ON posts
USING (tenant_id = current_setting('app.current_tenant_id')::integer);
SQL
end
end
💎 43. Database Connection Pooling and Sharding
- Connection pool configuration and monitoring
Database connection pooling is a technique where a cache of database connections is maintained to be reused by applications, rather than creating a new connection for each database interaction. This improves performance and resource utilization by minimizing the overhead of establishing new connections with each query - Rails 6+ native sharding support
- Custom sharding implementations
Database sharding is a method of splitting a large database into smaller, faster, and more manageable pieces called “shards”. These shards are distributed across multiple database servers, enabling better performance and scalability for large datasets - Read/write splitting strategies
# 1. Connection Pool Configuration
# config/database.yml
production:
adapter: postgresql
host: <%= ENV['DB_HOST'] %>
database: myapp_production
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 25 } %>
timeout: 5000
checkout_timeout: 5
reaping_frequency: 10
# Connection pool monitoring
class DatabaseConnectionPool
def self.status
ActiveRecord::Base.connection_pool.stat
end
# > ActiveRecord::Base.connection_pool.stat
# => {size: 5, connections: 0, busy: 0, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5.0}
def self.with_connection_info
pool = ActiveRecord::Base.connection_pool
{
size: pool.size,
active_connections: pool.checked_out.size,
available_connections: pool.available.size,
slow_queries_count: Rails.cache.fetch('slow_queries_count', expires_in: 1.minute) { 0 }
}
end
end
# 2. Database Sharding (Rails 6+)
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
default: { writing: :primary, reading: :primary_replica },
shard_one: { writing: :primary_shard_one, reading: :primary_shard_one_replica }
}
end
class User < ApplicationRecord
# Shard by user ID
def self.shard_for(user_id)
user_id % 2 == 0 ? :default : :shard_one
end
def self.find_by_sharded_id(user_id)
shard = shard_for(user_id)
connected_to(shard: shard) { find(user_id) }
end
end
# 3. Custom Sharding Implementation
class ShardedModel < ApplicationRecord
self.abstract_class = true
class << self
def shard_for(key)
"shard_#{key.hash.abs % shard_count}"
end
def on_shard(shard_name)
establish_connection(database_config[shard_name])
yield
ensure
establish_connection(database_config['primary'])
end
def find_across_shards(id)
shard_count.times do |i|
shard_name = "shard_#{i}"
record = on_shard(shard_name) { find_by(id: id) }
return record if record
end
nil
end
private
def shard_count
Rails.application.config.shard_count || 4
end
def database_config
Rails.application.config.database_configuration[Rails.env]
end
end
end
# 4. Read/Write Splitting
class User < ApplicationRecord
# Automatic read/write splitting
connects_to database: { writing: :primary, reading: :replica }
def self.expensive_report
# Force read from replica
connected_to(role: :reading) do
select(:id, :name, :created_at)
.joins(:posts)
.group(:id)
.having('COUNT(posts.id) > ?', 10)
end
end
end
# Connection switching middleware
class DatabaseRoutingMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
# Use replica for GET requests
if request.get? && !admin_request?(request)
ActiveRecord::Base.connected_to(role: :reading) do
@app.call(env)
end
else
@app.call(env)
end
end
private
def admin_request?(request)
request.path.start_with?('/admin')
end
end
💎 44. Advanced Security Patterns and Best Practices
- Content Security Policy (CSP) implementation
- Rate limiting and DDoS protection
- Secure headers and HSTS
- Input sanitization and virus scanning
- Enterprise-level security measures
# 1. Content Security Policy (CSP)
class ApplicationController < ActionController::Base
content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https, :unsafe_inline
# Add nonce for inline scripts
policy.script_src :self, :https, :unsafe_eval if Rails.env.development?
end
content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
content_security_policy_nonce_directives = %w(script-src)
end
# 2. Rate Limiting and DDoS Protection
class ApiController < ApplicationController
include ActionController::HttpAuthentication::Token::ControllerMethods
before_action :rate_limit_api_requests
before_action :authenticate_api_token
private
def rate_limit_api_requests
key = "api_rate_limit:#{request.remote_ip}"
count = Rails.cache.fetch(key, expires_in: 1.hour) { 0 }
if count >= 1000 # 1000 requests per hour
render json: { error: 'Rate limit exceeded' }, status: 429
return
end
Rails.cache.write(key, count + 1, expires_in: 1.hour)
end
def authenticate_api_token
authenticate_or_request_with_http_token do |token, options|
api_key = ApiKey.find_by(token: token)
api_key&.active? && !api_key.expired?
end
end
end
# 3. Secure Headers and HSTS
class ApplicationController < ActionController::Base
before_action :set_security_headers
private
def set_security_headers
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
if request.ssl?
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
end
end
end
# 4. Input Sanitization and Validation
class UserInput
include ActiveModel::Model
include ActiveModel::Attributes
attribute :content, :string
attribute :email, :string
validates :content, presence: true, length: { maximum: 10000 }
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validate :no_malicious_content
validate :rate_limit_validation
private
def no_malicious_content
dangerous_patterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi,
/javascript:/i,
/vbscript:/i,
/onload\s*=/i,
/onerror\s*=/i
]
dangerous_patterns.each do |pattern|
if content&.match?(pattern)
errors.add(:content, 'contains potentially dangerous content')
break
end
end
end
def rate_limit_validation
# Implement user-specific validation rate limiting
key = "validation_attempts:#{email}"
attempts = Rails.cache.fetch(key, expires_in: 5.minutes) { 0 }
if attempts > 10
errors.add(:base, 'Too many validation attempts. Please try again later.')
else
Rails.cache.write(key, attempts + 1, expires_in: 5.minutes)
end
end
end
# 5. Secure File Upload with Virus Scanning
class Document < ApplicationRecord
has_one_attached :file
validate :acceptable_file
validate :virus_scan_clean
enum scan_status: { pending: 0, clean: 1, infected: 2 }
after_commit :scan_for_viruses, on: :create
private
def acceptable_file
return unless file.attached?
# Check file size
unless file.blob.byte_size <= 10.megabytes
errors.add(:file, 'is too large')
end
# Check file type
allowed_types = %w[application/pdf image/jpeg image/png text/plain]
unless allowed_types.include?(file.blob.content_type)
errors.add(:file, 'type is not allowed')
end
# Check filename for path traversal
if file.filename.to_s.include?('..')
errors.add(:file, 'filename is invalid')
end
end
def virus_scan_clean
return unless file.attached? && scan_status == 'infected'
errors.add(:file, 'failed virus scan')
end
def scan_for_viruses
VirusScanJob.perform_later(self)
end
end
class VirusScanJob < ApplicationJob
def perform(document)
# Use ClamAV or similar service
result = system("clamscan --no-summary #{document.file.blob.service.path_for(document.file.blob.key)}")
if $?.success?
document.update!(scan_status: :clean)
else
document.update!(scan_status: :infected)
document.file.purge # Remove infected file
end
end
end
💎 45. Application Performance Monitoring (APM) and Observability
- Custom metrics and instrumentation
- Database query analysis and slow query detection
- Background job monitoring
- Health check endpoints
- Real-time performance dashboards
# 1. Custom Metrics and Instrumentation
class ApplicationController < ActionController::Base
include MetricsCollector
around_action :collect_performance_metrics
after_action :track_user_behavior
private
def collect_performance_metrics
start_time = Time.current
start_memory = memory_usage
yield
end_time = Time.current
end_memory = memory_usage
MetricsCollector.record_request(
controller: controller_name,
action: action_name,
duration: (end_time - start_time) * 1000,
memory_delta: end_memory - start_memory,
status: response.status,
user_agent: request.user_agent
)
end
def memory_usage
`ps -o rss= -p #{Process.pid}`.to_i
end
end
module MetricsCollector
extend self
def record_request(metrics)
# Send to APM service (New Relic, Datadog, etc.)
Rails.logger.info("METRICS: #{metrics.to_json}")
# Custom metrics for business logic
if metrics[:controller] == 'orders' && metrics[:action] == 'create'
increment_counter('orders.created')
record_gauge('orders.creation_time', metrics[:duration])
end
# Performance alerts
if metrics[:duration] > 1000 # > 1 second
SlowRequestNotifier.notify(metrics)
end
end
def increment_counter(metric_name, tags = {})
StatsD.increment(metric_name, tags: tags)
end
def record_gauge(metric_name, value, tags = {})
StatsD.gauge(metric_name, value, tags: tags)
end
end
# 2. Database Query Analysis
class QueryAnalyzer
def self.analyze_slow_queries
ActiveSupport::Notifications.subscribe('sql.active_record') do |name, start, finish, id, payload|
duration = (finish - start) * 1000
if duration > 100 # queries taking more than 100ms
Rails.logger.warn({
event: 'slow_query',
duration: duration,
sql: payload[:sql],
binds: payload[:binds]&.map(&:value),
name: payload[:name],
connection_id: payload[:connection_id]
}.to_json)
# Send to APM
NewRelic::Agent.record_metric('Database/SlowQuery', duration)
end
end
end
end
# 3. Background Job Monitoring
class MonitoredJob < ApplicationJob
around_perform :monitor_job_performance
retry_on StandardError, wait: 5.seconds, attempts: 3
private
def monitor_job_performance
start_time = Time.current
job_name = self.class.name
begin
yield
# Record successful job metrics
duration = Time.current - start_time
MetricsCollector.record_gauge("jobs.#{job_name.underscore}.duration", duration * 1000)
MetricsCollector.increment_counter("jobs.#{job_name.underscore}.success")
rescue => error
# Record failed job metrics
MetricsCollector.increment_counter("jobs.#{job_name.underscore}.failure")
# Enhanced error tracking
ErrorTracker.capture_exception(error, {
job_class: job_name,
job_id: job_id,
queue_name: queue_name,
arguments: arguments,
executions: executions
})
raise
end
end
end
# 4. Health Check Endpoints
class HealthController < ApplicationController
skip_before_action :authenticate_user!
def check
render json: { status: 'ok', timestamp: Time.current.iso8601 }
end
def detailed
checks = {
database: database_check,
redis: redis_check,
storage: storage_check,
jobs: job_queue_check
}
overall_status = checks.values.all? { |check| check[:status] == 'ok' }
status_code = overall_status ? 200 : 503
render json: {
status: overall_status ? 'ok' : 'error',
checks: checks,
timestamp: Time.current.iso8601
}, status: status_code
end
private
def database_check
ActiveRecord::Base.connection.execute('SELECT 1')
{ status: 'ok', response_time: measure_time { ActiveRecord::Base.connection.execute('SELECT 1') } }
rescue => e
{ status: 'error', error: e.message }
end
def redis_check
Redis.current.ping
{ status: 'ok', response_time: measure_time { Redis.current.ping } }
rescue => e
{ status: 'error', error: e.message }
end
def measure_time
start_time = Time.current
yield
((Time.current - start_time) * 1000).round(2)
end
end
# 5. Real-time Performance Dashboard
class PerformanceDashboard
include ActionView::Helpers::NumberHelper
def self.current_stats
{
requests_per_minute: request_rate,
average_response_time: average_response_time,
error_rate: error_rate,
active_users: active_user_count,
database_stats: database_performance,
background_jobs: job_queue_stats
}
end
def self.request_rate
# Calculate from metrics store
Rails.cache.fetch('metrics:requests_per_minute', expires_in: 30.seconds) do
# Implementation depends on your metrics store
StatsD.get_rate('requests.total')
end
end
def self.database_performance
pool = ActiveRecord::Base.connection_pool
{
pool_size: pool.size,
active_connections: pool.checked_out.size,
available_connections: pool.available.size,
slow_queries_count: Rails.cache.fetch('slow_queries_count', expires_in: 1.minute) { 0 }
}
end
def self.job_queue_stats
if defined?(Sidekiq)
stats = Sidekiq::Stats.new
{
processed: stats.processed,
failed: stats.failed,
enqueued: stats.enqueued,
retry_size: stats.retry_size
}
else
{ message: 'Background job system not available' }
end
end
end
These additional 5 questions focus on enterprise-level concerns that senior Rails developers encounter in production environments, making this the most comprehensive Rails guide available with real-world, production-tested examples.
🎯 New Areas Added (Questions 46-50):
💎 46. 📧 ActionMailer and Email Handling
- Email configuration and delivery methods
- Email templates (HTML + Text)
- Background email processing
- Email testing and previews
- Email analytics and interceptors
# 1. Basic Mailer Setup
class UserMailer < ApplicationMailer
default from: 'noreply@example.com'
def welcome_email(user)
@user = user
@url = login_url
mail(
to: @user.email,
subject: 'Welcome to Our Platform!',
template_path: 'mailers/user_mailer',
template_name: 'welcome'
)
end
def password_reset(user, token)
@user = user
@token = token
@reset_url = edit_password_reset_url(token: @token)
mail(
to: @user.email,
subject: 'Password Reset Instructions',
reply_to: 'support@example.com'
)
end
def order_confirmation(order)
@order = order
@user = order.user
# Attach invoice PDF
attachments['invoice.pdf'] = order.generate_invoice_pdf
# Inline images
attachments.inline['logo.png'] = File.read(Rails.root.join('app/assets/images/logo.png'))
mail(
to: @user.email,
subject: "Order Confirmation ##{@order.id}",
delivery_method_options: { user_name: ENV['SMTP_USERNAME'] }
)
end
end
# 2. Email Templates (HTML + Text)
# app/views/user_mailer/welcome_email.html.erb
<%= content_for :title, "Welcome #{@user.name}!" %>
<div class="email-container">
<h1>Welcome to Our Platform!</h1>
<p>Hi <%= @user.name %>,</p>
<p>Thank you for joining us. Click the link below to get started:</p>
<p><%= link_to "Get Started", @url, class: "button" %></p>
</div>
# app/views/user_mailer/welcome_email.text.erb
Welcome <%= @user.name %>!
Thank you for joining our platform.
Get started: <%= @url %>
# 3. Email Configuration
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV['SMTP_SERVER'],
port: 587,
domain: ENV['DOMAIN'],
user_name: ENV['SMTP_USERNAME'],
password: ENV['SMTP_PASSWORD'],
authentication: 'plain',
enable_starttls_auto: true,
open_timeout: 5,
read_timeout: 5
}
# For SendGrid
config.action_mailer.smtp_settings = {
address: 'smtp.sendgrid.net',
port: 587,
authentication: :plain,
user_name: 'apikey',
password: ENV['SENDGRID_API_KEY']
}
# 4. Background Email Processing
class UserRegistrationService
def call
user = create_user
# Send immediately
UserMailer.welcome_email(user).deliver_now
# Send in background (recommended)
UserMailer.welcome_email(user).deliver_later
# Send at specific time
UserMailer.welcome_email(user).deliver_later(wait: 1.hour)
user
end
end
# 5. Email Testing and Previews
# test/mailers/user_mailer_test.rb
class UserMailerTest < ActionMailer::TestCase
test "welcome email" do
user = users(:john)
email = UserMailer.welcome_email(user)
assert_emails 1 do
email.deliver_now
end
assert_equal ['noreply@example.com'], email.from
assert_equal [user.email], email.to
assert_equal 'Welcome to Our Platform!', email.subject
assert_match 'Hi John', email.body.to_s
end
end
# Email Previews for development
# test/mailers/previews/user_mailer_preview.rb
class UserMailerPreview < ActionMailer::Preview
def welcome_email
UserMailer.welcome_email(User.first)
end
def password_reset
user = User.first
token = "sample-token-123"
UserMailer.password_reset(user, token)
end
end
# 6. Email Analytics and Tracking
class TrackableMailer < ApplicationMailer
after_action :track_email_sent
private
def track_email_sent
EmailAnalytics.track_sent(
mailer: self.class.name,
action: action_name,
recipient: message.to.first,
subject: message.subject,
sent_at: Time.current
)
end
end
# 7. Email Interceptors
class EmailInterceptor
def self.delivering_email(message)
# Prevent emails in staging
if Rails.env.staging?
message.to = ['staging@example.com']
message.cc = nil
message.bcc = nil
message.subject = "[STAGING] #{message.subject}"
end
# Add environment prefix
unless Rails.env.production?
message.subject = "[#{Rails.env.upcase}] #{message.subject}"
end
end
end
# Register interceptor
ActionMailer::Base.register_interceptor(EmailInterceptor)
💎 47. 🌍 Internationalization (I18n)
- Multi-language application setup
- Locale management and routing
- Translation files and fallbacks
- Model translations with Globalize
- Date/time localization
# 1. Basic I18n Configuration
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.i18n.available_locales = [:en, :es, :fr, :de, :ja]
config.i18n.default_locale = :en
config.i18n.fallbacks = true
# 2. Locale Files Structure
# config/locales/en.yml
en:
hello: "Hello"
welcome:
message: "Welcome %{name}!"
title: "Welcome to Our Site"
activerecord:
models:
user: "User"
post: "Post"
attributes:
user:
name: "Full Name"
email: "Email Address"
post:
title: "Title"
content: "Content"
errors:
models:
user:
attributes:
email:
taken: "Email address is already in use"
invalid: "Please enter a valid email address"
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"
time:
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%B %d, %Y %H:%M"
# config/locales/es.yml
es:
hello: "Hola"
welcome:
message: "¡Bienvenido %{name}!"
title: "Bienvenido a Nuestro Sitio"
activerecord:
models:
user: "Usuario"
post: "Publicación"
# 3. Controller Locale Handling
class ApplicationController < ActionController::Base
before_action :set_locale
private
def set_locale
I18n.locale = locale_from_params ||
locale_from_user ||
locale_from_header ||
I18n.default_locale
end
def locale_from_params
return unless params[:locale]
return unless I18n.available_locales.include?(params[:locale].to_sym)
params[:locale]
end
def locale_from_user
current_user&.locale if user_signed_in?
end
def locale_from_header
request.env['HTTP_ACCEPT_LANGUAGE']&.scan(/^[a-z]{2}/)&.first
end
# URL generation with locale
def default_url_options
{ locale: I18n.locale }
end
end
# 4. Routes with Locale
# config/routes.rb
Rails.application.routes.draw do
scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
root 'home#index'
resources :posts
resources :users
end
# Redirect root to default locale
root to: redirect("/#{I18n.default_locale}", status: 302)
end
# 5. View Translations
# app/views/posts/index.html.erb
<h1><%= t('posts.index.title') %></h1>
<p><%= t('posts.index.description', count: @posts.count) %></p>
<%= link_to t('posts.new'), new_post_path, class: 'btn btn-primary' %>
<% @posts.each do |post| %>
<div class="post">
<h3><%= post.title %></h3>
<p><%= t('posts.published_at', date: l(post.created_at, format: :short)) %></p>
<p><%= truncate(post.content, length: 150) %></p>
</div>
<% end %>
# 6. Model Translations (with Globalize gem)
class Post < ApplicationRecord
translates :title, :content
validates :title, presence: true
validates :content, presence: true
end
# Usage
post = Post.create(
title: "English Title",
content: "English content"
)
I18n.with_locale(:es) do
post.update(
title: "Título en Español",
content: "Contenido en español"
)
end
# Access translations
I18n.locale = :en
post.title # => "English Title"
I18n.locale = :es
post.title # => "Título en Español"
# 7. Form Helpers with I18n
<%= form_with model: @user do |f| %>
<div class="field">
<%= f.label :name, t('activerecord.attributes.user.name') %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email %>
</div>
<%= f.submit t('helpers.submit.user.create') %>
<% end %>
# 8. Pluralization
# config/locales/en.yml
en:
posts:
count:
zero: "No posts"
one: "1 post"
other: "%{count} posts"
# Usage in views
<%= t('posts.count', count: @posts.count) %>
# 9. Date and Time Localization
# Helper method
module ApplicationHelper
def localized_date(date, format = :default)
l(date, format: format) if date
end
def relative_time(time)
time_ago_in_words(time, locale: I18n.locale)
end
end
# Usage
<%= localized_date(@post.created_at, :long) %>
<%= relative_time(@post.created_at) %>
# 10. Locale Switching
# Helper for locale switcher
module ApplicationHelper
def locale_switcher
content_tag :div, class: 'locale-switcher' do
I18n.available_locales.map do |locale|
link_to_unless I18n.locale == locale,
locale.upcase,
url_for(locale: locale),
class: ('active' if I18n.locale == locale)
end.join(' | ').html_safe
end
end
end
💎 48. 🔧 Error Handling and Logging
- Global exception handling strategies
- Structured logging patterns
- Custom error classes and business logic errors
- API error responses
- Production error tracking
# 1. Global Exception Handling
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_standard_error
rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
rescue_from ActionController::ParameterMissing, with: :handle_bad_request
rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized
private
def handle_standard_error(exception)
ErrorLogger.capture_exception(exception, {
user_id: current_user&.id,
request_id: request.uuid,
url: request.url,
params: params.to_unsafe_h,
user_agent: request.user_agent
})
if Rails.env.development?
raise exception
else
render_error_page(500, 'Something went wrong')
end
end
def handle_not_found(exception)
ErrorLogger.capture_exception(exception, { level: 'info' })
render_error_page(404, 'Page not found')
end
def handle_bad_request(exception)
ErrorLogger.capture_exception(exception, { level: 'warning' })
render_error_page(400, 'Bad request')
end
def handle_unauthorized(exception)
ErrorLogger.capture_exception(exception, { level: 'warning' })
if user_signed_in?
render_error_page(403, 'Access denied')
else
redirect_to login_path, alert: 'Please log in to continue'
end
end
def render_error_page(status, message)
respond_to do |format|
format.html { render 'errors/error', locals: { message: message }, status: status }
format.json { render json: { error: message }, status: status }
end
end
end
# 2. Structured Logging
class ApplicationController < ActionController::Base
around_action :log_request_details
private
def log_request_details
start_time = Time.current
Rails.logger.info({
event: 'request_started',
request_id: request.uuid,
method: request.method,
path: request.path,
remote_ip: request.remote_ip,
user_agent: request.user_agent,
user_id: current_user&.id,
timestamp: start_time.iso8601
}.to_json)
begin
yield
ensure
duration = Time.current - start_time
Rails.logger.info({
event: 'request_completed',
request_id: request.uuid,
status: response.status,
duration_ms: (duration * 1000).round(2),
timestamp: Time.current.iso8601
}.to_json)
end
end
end
# 3. Custom Error Logger
class ErrorLogger
class << self
def capture_exception(exception, context = {})
error_data = {
exception_class: exception.class.name,
message: exception.message,
backtrace: exception.backtrace&.first(10),
context: context,
timestamp: Time.current.iso8601,
environment: Rails.env,
server: Socket.gethostname
}
# Log to Rails logger
Rails.logger.error(error_data.to_json)
# Send to external service (Sentry, Bugsnag, etc.)
if Rails.env.production?
Sentry.capture_exception(exception, extra: context)
end
# Store in database for analysis
ErrorReport.create!(
exception_class: exception.class.name,
message: exception.message,
backtrace: exception.backtrace.join("\n"),
context: context,
occurred_at: Time.current
)
end
def capture_message(message, level: 'info', context: {})
log_data = {
event: 'custom_log',
level: level,
message: message,
context: context,
timestamp: Time.current.iso8601
}
case level
when 'error'
Rails.logger.error(log_data.to_json)
when 'warning'
Rails.logger.warn(log_data.to_json)
else
Rails.logger.info(log_data.to_json)
end
end
end
end
# 4. Business Logic Error Handling
class OrderProcessingService
include ActiveModel::Model
class OrderProcessingError < StandardError; end
class PaymentError < OrderProcessingError; end
class InventoryError < OrderProcessingError; end
def call(order)
ActiveRecord::Base.transaction do
validate_inventory!(order)
process_payment!(order)
update_inventory!(order)
send_confirmation!(order)
order.update!(status: 'completed')
rescue PaymentError => e
order.update!(status: 'payment_failed', error_message: e.message)
ErrorLogger.capture_exception(e, { order_id: order.id, service: 'payment' })
false
rescue InventoryError => e
order.update!(status: 'inventory_failed', error_message: e.message)
ErrorLogger.capture_exception(e, { order_id: order.id, service: 'inventory' })
false
rescue => e
order.update!(status: 'failed', error_message: e.message)
ErrorLogger.capture_exception(e, { order_id: order.id, service: 'order_processing' })
false
end
end
private
def validate_inventory!(order)
order.line_items.each do |item|
unless item.product.sufficient_stock?(item.quantity)
raise InventoryError, "Insufficient stock for #{item.product.name}"
end
end
end
def process_payment!(order)
result = PaymentService.charge(order.total, order.payment_method)
raise PaymentError, result.error_message unless result.success?
end
end
# 5. Background Job Error Handling
class ProcessOrderJob < ApplicationJob
queue_as :default
retry_on StandardError, wait: 5.seconds, attempts: 3
retry_on PaymentService::TemporaryError, wait: 30.seconds, attempts: 5
discard_on ActiveJob::DeserializationError
def perform(order_id)
order = Order.find(order_id)
unless OrderProcessingService.new.call(order)
ErrorLogger.capture_message(
"Order processing failed for order #{order_id}",
level: 'error',
context: { order_id: order_id, attempt: executions }
)
end
rescue ActiveRecord::RecordNotFound => e
ErrorLogger.capture_exception(e, {
order_id: order_id,
message: "Order not found during processing"
})
# Don't retry for missing records
rescue => e
ErrorLogger.capture_exception(e, {
order_id: order_id,
job_id: job_id,
executions: executions
})
# Re-raise to trigger retry mechanism
raise
end
end
# 6. API Error Responses
module ApiErrorHandler
extend ActiveSupport::Concern
included do
rescue_from StandardError, with: :handle_api_error
rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
rescue_from ActiveRecord::RecordInvalid, with: :handle_validation_error
end
private
def handle_api_error(exception)
ErrorLogger.capture_exception(exception)
render json: {
error: {
type: 'internal_error',
message: 'An unexpected error occurred',
request_id: request.uuid
}
}, status: 500
end
def handle_not_found(exception)
render json: {
error: {
type: 'not_found',
message: 'Resource not found'
}
}, status: 404
end
def handle_validation_error(exception)
render json: {
error: {
type: 'validation_error',
message: 'Validation failed',
details: exception.record.errors.full_messages
}
}, status: 422
end
end
# 7. Custom Error Pages
# app/views/errors/error.html.erb
<div class="error-page">
<h1><%= message %></h1>
<p>We're sorry, but something went wrong.</p>
<% if Rails.env.development? %>
<div class="debug-info">
<h3>Debug Information</h3>
<p>Request ID: <%= request.uuid %></p>
<p>Time: <%= Time.current %></p>
</div>
<% end %>
<%= link_to "Go Home", root_path, class: "btn btn-primary" %>
</div>
💎 49. ⚙️ Rails Configuration and Environment Management
- Environment-specific configurations
- Custom configuration classes
- Secrets and credentials management
- Feature flags implementation
- Database configuration strategies
# 1. Environment-Specific Configuration
# config/environments/development.rb
Rails.application.configure do
config.cache_classes = false
config.eager_load = false
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_deliveries = false
config.active_storage.variant_processor = :mini_magick
# Custom development settings
config.log_level = :debug
config.cache_store = :memory_store
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
# Development-specific middleware
config.middleware.insert_before ActionDispatch::ShowExceptions, DeveloperMiddleware
end
# config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Performance settings
config.cache_store = :redis_cache_store, {
url: ENV['REDIS_URL'],
pool_size: ENV.fetch('RAILS_MAX_THREADS', 5),
pool_timeout: 5
}
# Security settings
config.force_ssl = true
config.ssl_options = {
redirect: { exclude: ->(request) { request.path =~ /health/ } }
}
# Logging
config.log_level = :info
config.log_formatter = ::Logger::Formatter.new
config.logger = ActiveSupport::Logger.new(STDOUT)
# Asset settings
config.assets.compile = false
config.assets.css_compressor = :sass
config.assets.js_compressor = :terser
end
# 2. Custom Configuration
# config/application.rb
module MyApp
class Application < Rails::Application
# Custom configuration
config.x.payment_gateway.url = ENV['PAYMENT_GATEWAY_URL']
config.x.payment_gateway.api_key = ENV['PAYMENT_GATEWAY_API_KEY']
config.x.payment_gateway.timeout = 30
config.x.upload_limits.max_file_size = 10.megabytes
config.x.upload_limits.allowed_types = %w[jpg jpeg png pdf]
config.x.features.new_dashboard = ENV['ENABLE_NEW_DASHBOARD'] == 'true'
config.x.features.advanced_search = Rails.env.production?
end
end
# Usage in application
class PaymentService
def self.gateway_url
Rails.application.config.x.payment_gateway.url
end
def self.api_key
Rails.application.config.x.payment_gateway.api_key
end
end
# 3. Custom Configuration Classes
class AppConfiguration
include ActiveModel::Model
include ActiveModel::Attributes
attribute :max_file_size, :integer, default: 10.megabytes
attribute :allowed_file_types, :string, default: 'jpg,jpeg,png,pdf'
attribute :email_from, :string, default: 'noreply@example.com'
attribute :cache_timeout, :integer, default: 1.hour
attribute :feature_flags, default: {}
validates :max_file_size, numericality: { greater_than: 0 }
validates :email_from, format: { with: URI::MailTo::EMAIL_REGEXP }
def self.instance
@instance ||= new(load_from_env)
end
def self.load_from_env
{
max_file_size: ENV.fetch('MAX_FILE_SIZE', 10.megabytes).to_i,
allowed_file_types: ENV.fetch('ALLOWED_FILE_TYPES', 'jpg,jpeg,png,pdf'),
email_from: ENV.fetch('EMAIL_FROM', 'noreply@example.com'),
cache_timeout: ENV.fetch('CACHE_TIMEOUT', 1.hour).to_i,
feature_flags: JSON.parse(ENV.fetch('FEATURE_FLAGS', '{}'))
}
end
def allowed_file_types_array
allowed_file_types.split(',').map(&:strip)
end
def feature_enabled?(feature)
feature_flags[feature.to_s] == true
end
end
# Usage
if AppConfiguration.instance.feature_enabled?(:new_dashboard)
# Show new dashboard
end
# 4. Database Configuration
# config/database.yml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>
development:
<<: *default
database: myapp_development
host: localhost
test:
<<: *default
database: myapp_test<%= ENV['TEST_ENV_NUMBER'] %>
host: localhost
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
pool: <%= ENV.fetch("DB_POOL_SIZE", 25) %>
checkout_timeout: <%= ENV.fetch("DB_CHECKOUT_TIMEOUT", 5) %>
reaping_frequency: <%= ENV.fetch("DB_REAPING_FREQUENCY", 10) %>
# Multiple databases
production:
primary:
<<: *default
url: <%= ENV['PRIMARY_DATABASE_URL'] %>
analytics:
<<: *default
url: <%= ENV['ANALYTICS_DATABASE_URL'] %>
migrations_paths: db/analytics_migrate
# 5. Initializers
# config/initializers/redis.rb
redis_config = {
url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0'),
timeout: 1,
reconnect_attempts: 3,
reconnect_delay: 0.5
}
Redis.current = Redis.new(redis_config)
# Connection pool for multi-threaded environments
REDIS_POOL = ConnectionPool.new(size: 10, timeout: 5) do
Redis.new(redis_config)
end
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
config.average_scheduled_poll_interval = 15
config.concurrency = ENV.fetch('SIDEKIQ_CONCURRENCY', 5).to_i
end
Sidekiq.configure_client do |config|
config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
end
# 6. Secrets and Credentials Management
# config/credentials.yml.enc (encrypted)
# Edit with: rails credentials:edit
secret_key_base: your_secret_key_base
database:
username: app_user
password: secure_password
aws:
access_key_id: your_access_key
secret_access_key: your_secret_key
bucket: your_s3_bucket
stripe:
publishable_key: pk_test_...
secret_key: sk_test_...
# Usage in application
Rails.application.credentials.aws[:access_key_id]
Rails.application.credentials.stripe[:secret_key]
# Environment-specific credentials
# config/credentials/production.yml.enc
rails credentials:edit --environment production
# 7. Feature Flags and Configuration
class FeatureFlag
FLAGS = {
new_dashboard: { default: false, description: 'Enable new dashboard UI' },
advanced_search: { default: true, description: 'Enable advanced search' },
payment_gateway_v2: { default: false, description: 'Use new payment gateway' }
}.freeze
def self.enabled?(flag_name)
return FLAGS[flag_name][:default] unless Rails.env.production?
# Check database configuration
flag = ConfigurationSetting.find_by(key: "feature_flag_#{flag_name}")
flag&.value == 'true' || FLAGS[flag_name][:default]
end
def self.enable!(flag_name)
ConfigurationSetting.find_or_create_by(key: "feature_flag_#{flag_name}") do |setting|
setting.value = 'true'
end
end
def self.disable!(flag_name)
ConfigurationSetting.find_or_create_by(key: "feature_flag_#{flag_name}") do |setting|
setting.value = 'false'
end
end
end
# Usage in views
<% if FeatureFlag.enabled?(:new_dashboard) %>
<%= render 'new_dashboard' %>
<% else %>
<%= render 'old_dashboard' %>
<% end %>
# 8. Environment Detection and Conditional Logic
class EnvironmentHelper
def self.staging?
Rails.env.staging? || ENV['RAILS_ENV'] == 'staging'
end
def self.production_like?
Rails.env.production? || staging?
end
def self.local_development?
Rails.env.development? && ENV['CODESPACE_NAME'].blank?
end
def self.docker_environment?
File.exist?('/.dockerenv')
end
end
# Conditional configuration based on environment
if EnvironmentHelper.production_like?
# Production configurations
Rails.application.config.force_ssl = true
Rails.application.config.log_level = :info
else
# Development configurations
Rails.application.config.log_level = :debug
Rails.application.config.action_mailer.delivery_method = :letter_opener
end
💎 50. 🚀 Deployment Strategies and DevOps
- Docker containerization
- CI/CD pipeline setup with GitHub Actions
- Kubernetes deployment
- Health checks and monitoring
- Blue-green deployment strategies
# 1. Docker Configuration
# Dockerfile
FROM ruby:3.1.0
# Install dependencies
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
# Set working directory
WORKDIR /myapp
# Install gems
COPY Gemfile Gemfile.lock ./
RUN bundle install
# Copy application code
COPY . .
# Precompile assets
RUN rails assets:precompile
# Expose port
EXPOSE 3000
# Start server
CMD ["rails", "server", "-b", "0.0.0.0"]
# docker-compose.yml
version: '3.8'
services:
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp_development
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:6-alpine
ports:
- "6379:6379"
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
- redis
environment:
DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
REDIS_URL: redis://redis:6379/0
volumes:
- .:/myapp
- bundle_cache:/usr/local/bundle
sidekiq:
build: .
command: bundle exec sidekiq
depends_on:
- db
- redis
environment:
DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
REDIS_URL: redis://redis:6379/0
volumes:
postgres_data:
bundle_cache:
# 2. Capistrano Deployment
# Gemfile
group :development do
gem 'capistrano', '~> 3.17'
gem 'capistrano-rails', '~> 1.6'
gem 'capistrano-rbenv', '~> 2.2'
gem 'capistrano-passenger', '~> 0.2'
gem 'capistrano-sidekiq', '~> 2.0'
end
# config/deploy.rb
lock '~> 3.17.0'
set :application, 'myapp'
set :repo_url, 'git@github.com:username/myapp.git'
set :deploy_to, '/var/www/myapp'
set :rbenv_ruby, '3.1.0'
set :linked_files, fetch(:linked_files, []).push(
'config/database.yml',
'config/master.key',
'.env.production'
)
set :linked_dirs, fetch(:linked_dirs, []).push(
'log',
'tmp/pids',
'tmp/cache',
'tmp/sockets',
'vendor/bundle',
'public/system',
'storage'
)
# Sidekiq configuration
set :sidekiq_config, 'config/sidekiq.yml'
set :sidekiq_env, fetch(:rails_env, 'production')
namespace :deploy do
desc 'Run database migrations'
task :migrate do
on roles(:db) do
within release_path do
execute :rake, 'db:migrate RAILS_ENV=production'
end
end
end
desc 'Clear application cache'
task :clear_cache do
on roles(:web) do
within release_path do
execute :rake, 'tmp:cache:clear RAILS_ENV=production'
end
end
end
after :updated, :migrate
after :migrate, :clear_cache
end
# config/deploy/production.rb
server 'production.example.com', user: 'deploy', roles: %w{app db web}
set :rails_env, 'production'
set :branch, 'main'
# 3. Kubernetes Deployment
# k8s/namespace.yml
apiVersion: v1
kind: Namespace
metadata:
name: myapp-production
# k8s/configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: myapp-production
data:
RAILS_ENV: "production"
RAILS_LOG_TO_STDOUT: "true"
RAILS_SERVE_STATIC_FILES: "true"
# k8s/secret.yml
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
namespace: myapp-production
type: Opaque
data:
database-url: <base64-encoded-database-url>
secret-key-base: <base64-encoded-secret-key>
redis-url: <base64-encoded-redis-url>
# k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-web
namespace: myapp-production
spec:
replicas: 3
selector:
matchLabels:
app: myapp-web
template:
metadata:
labels:
app: myapp-web
spec:
containers:
- name: web
image: myapp:latest
ports:
- containerPort: 3000
env:
- name: RAILS_ENV
valueFrom:
configMapKeyRef:
name: myapp-config
key: RAILS_ENV
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-url
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: myapp-secrets
key: secret-key-base
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
# k8s/service.yml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
namespace: myapp-production
spec:
selector:
app: myapp-web
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
# 4. CI/CD with GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:6
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.0
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
- name: Install dependencies
run: |
bundle install
yarn install
- name: Set up database
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/myapp_test
RAILS_ENV: test
run: |
bundle exec rails db:create
bundle exec rails db:migrate
- name: Run tests
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/myapp_test
REDIS_URL: redis://localhost:6379/0
RAILS_ENV: test
run: |
bundle exec rails test
bundle exec rails test:system
- name: Run security scan
run: |
bundle exec brakeman --exit-on-warn
bundle exec bundle-audit check --update
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myapp:latest
- name: Deploy to production
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
# Deploy using your preferred method
# This could be Capistrano, kubectl, or direct SSH
# 5. Health Checks and Monitoring
# config/routes.rb
Rails.application.routes.draw do
get '/health', to: 'health#check'
get '/health/detailed', to: 'health#detailed'
end
# app/controllers/health_controller.rb
class HealthController < ApplicationController
skip_before_action :authenticate_user!
def check
render json: { status: 'ok', timestamp: Time.current.iso8601 }
end
def detailed
checks = {
database: database_check,
redis: redis_check,
storage: storage_check,
jobs: job_queue_check
}
overall_status = checks.values.all? { |check| check[:status] == 'ok' }
status_code = overall_status ? 200 : 503
render json: {
status: overall_status ? 'ok' : 'error',
checks: checks,
timestamp: Time.current.iso8601
}, status: status_code
end
private
def database_check
ActiveRecord::Base.connection.execute('SELECT 1')
{ status: 'ok', response_time: measure_time { ActiveRecord::Base.connection.execute('SELECT 1') } }
rescue => e
{ status: 'error', error: e.message }
end
def redis_check
Redis.current.ping
{ status: 'ok', response_time: measure_time { Redis.current.ping } }
rescue => e
{ status: 'error', error: e.message }
end
def measure_time
start_time = Time.current
yield
((Time.current - start_time) * 1000).round(2)
end
end
# 6. Blue-Green Deployment Strategy
# deploy/blue_green.rb
class BlueGreenDeployer
def initialize(current_color)
@current_color = current_color
@next_color = current_color == 'blue' ? 'green' : 'blue'
end
def deploy
puts "Deploying to #{@next_color} environment..."
# Deploy to inactive environment
deploy_to_environment(@next_color)
# Run health checks
if health_check_passed?(@next_color)
# Switch traffic
switch_traffic_to(@next_color)
puts "Deployment successful! Traffic switched to #{@next_color}"
else
puts "Health checks failed! Rolling back..."
rollback
end
end
private
def deploy_to_environment(color)
# Implementation depends on your infrastructure
system("kubectl apply -f k8s/#{color}/")
end
def health_check_passed?(color)
# Check if the new environment is healthy
3.times do
response = Net::HTTP.get_response(URI("http://#{color}.myapp.com/health"))
return true if response.code == '200'
sleep 10
end
false
end
def switch_traffic_to(color)
# Update load balancer configuration
system("kubectl patch service myapp-service -p '{\"spec\":{\"selector\":{\"version\":\"#{color}\"}}}'")
end
end
🏃 Ruby Into Action
🏛️ Get to know about Rails Advanced Arcitecture
Check My premium content below:
Advanced Rails Engines: An Architects Guide
📚 Complete Summary
✅ Production-Ready Concepts: Multi-tenancy, sharding, connection pooling
✅ Security Best Practices: Advanced CSP, rate limiting, virus scanning
✅ Performance Monitoring: APM integration, health checks, observability
✅ Rails Internals: Request lifecycle, middleware stack, routing
✅ Scalability Patterns: Database scaling, tenant isolation, monitoring
The questions are organized into key areas:
🏗️ Core Rails Concepts (Questions 1-3)
- MVC Architecture
- Convention over Configuration
- Rails Directory Structure
🗄️ ActiveRecord & Database (Questions 4-8)
- Associations & Relationships
- Migrations & Schema Management
- N+1 Queries & Performance
- Scopes & Query Methods
- Database Indexing
🎮 Controllers & Routing (Questions 9-11)
- RESTful Routes & Resources
- Strong Parameters & Security
- Before/After Actions & Filters
🎨 Views & Templates (Questions 12-13)
- Partials & Code Reusability
- View Helpers & Logic Separation
🔒 Security (Questions 14-16, 44)
- CSRF Protection
- SQL Injection Prevention
- Mass Assignment Protection
- Advanced Security Patterns
- Content Security Policy
⚡ Performance & Optimization (Questions 17-19, 43, 45)
- Caching Strategies (Fragment, Russian Doll, HTTP)
- Eager Loading Techniques
- Database Query Optimization
- Connection Pooling
- Performance Monitoring & APM
🧪 Testing (Questions 20-21)
- Unit, Integration & System Tests
- Fixtures vs Factories
- Test-Driven Development
🔥 Advanced Features (Questions 22-32)
- ActiveJob & Background Processing
- Rails Engines & Modularity
- Action Cable & WebSockets
- Asset Pipeline & Webpacker
- Service Objects Pattern
- Rails Concerns
- API Mode Development
- Autoloading & Zeitwerk
- Rails Credentials & Secrets
- File Uploads with Active Storage
- Model Callbacks & Lifecycle
🎯 Expert-Level Topics (Questions 33-45)
- Polymorphic Associations
- Single Table Inheritance (STI)
- Database Transactions & Isolation
- Race Conditions & Concurrency
- Route Constraints & Custom Logic
- Rails Generators & Automation
- Custom Middleware Development
- Full-Text Search Implementation
- Rails Request Lifecycle & Internals
- Multi-tenancy Architecture
- Database Sharding & Connection Management
- Production Security Measures
- Application Performance Monitoring
🚀 Key Features of This Guide:
- Real-world examples with practical code snippets
- Production-ready solutions for common challenges
- Security best practices for enterprise applications
- Performance optimization techniques
- Architecture patterns for scalable applications
- Modern Rails features (Rails 6+ and 7+)
- Expert-level concepts for senior developer roles
✅ Complete Coverage Now Includes:
This guide now provides complete coverage of all major Rails areas that senior developers should master:
- Core Framework Knowledge – MVC, conventions, directory structure
- Database & ORM – ActiveRecord, associations, performance optimization
- Web Layer – Controllers, routing, views, templates
- Security – CSRF, SQL injection, mass assignment, advanced security
- Performance – Caching, eager loading, query optimization, APM
- Testing – Unit, integration, system tests
- Communication – Email handling and ActionMailer
- Globalization – Multi-language support and I18n
- Operations – Error handling, logging, monitoring
- Configuration – Environment management, feature flags
- DevOps – Deployment, containerization, CI/CD
- Advanced Topics – Background jobs, WebSockets, engines, middleware
- Expert Level – Concurrency, multi-tenancy, sharding, custom generators
Whether you’re preparing for a Rails interview or looking to level up your Rails expertise, this guide covers everything from fundamental concepts to advanced architectural patterns, deployment strategies, and production concerns that senior Rails developers encounter in enterprise environments.
Enjoy Rails !!!! Boooom 🚀