Ruby on Rails Interview Questions and Answers
Ace your Rails interviews with questions on MVC architecture, ActiveRecord, testing with RSpec, deployment strategies, and optimization.
๐ Jump to Question
Ruby on Rails Interview Questions and Answers
Table of Contents
- Rails Basics
- MVC Architecture
- ActiveRecord & Database
- Routes & Controllers
- Views & Helpers
- Authentication & Authorization
- Testing
- Performance & Optimization
- Security
- Deployment
- Rails 7+ Features
- Common Interview Questions
- Advanced ActiveRecord
- Background Jobs
- API Development
- Real-time Features
- File Uploads
- Internationalization
- Caching Strategies
- Database Optimization
- Common Gems
- Troubleshooting & Debugging
- Rails Anti-patterns
- Code Organization
- Interview Scenarios
Rails Basics
Q1: What is Ruby on Rails?
Answer: Ruby on Rails is a server-side web application framework written in Ruby. It follows the MVC (Model-View-Controller) pattern and emphasizes convention over configuration.
Key Principles:
- Convention over Configuration - Rails has sensible defaults
- Don't Repeat Yourself (DRY) - Write code once, reuse everywhere
- Opinionated Software - Rails encourages specific ways of doing things
Features:
- Full-stack framework (frontend to database)
- Integrated testing tools
- Database abstraction with ActiveRecord
- RESTful design
- Asset pipeline for CSS/JS
- Built-in security features
Companies using Rails: GitHub, Shopify, Airbnb, Basecamp, Twitch
Q2: Explain Convention over Configuration
Answer: Rails makes assumptions about what you want to do, so you write less configuration code.
Examples:
# Model naming - Rails automatically knows the table name
class User < ApplicationRecord
# No need to specify table name - it assumes 'users'
end
# Controller naming - Rails knows which view to render
class UsersController < ApplicationController
def index
@users = User.all
# Automatically renders app/views/users/index.html.erb
end
end
# Database conventions
# Primary key: 'id'
# Foreign key: 'user_id'
# Timestamps: 'created_at', 'updated_at'
Q3: What is the Rails stack?
Answer: Rails includes multiple components:
- Action Pack - Controllers and views
- Action Controller - Routes and controller logic
- Action View - Templates and view helpers
- Active Record - Database ORM
- Action Mailer - Email sending
- Active Job - Background jobs
- Action Cable - WebSockets/real-time features
- Active Storage - File uploads
- Action Text - Rich text content
- Railties - Core Rails code
MVC Architecture
Q4: Explain MVC in Rails
Answer: MVC separates application logic into three interconnected components:
Browser โ Router โ Controller โ Model โ Database
โ โ
โโโโโโโ View โโโโโโโโโ
Model:
- Handles data and business logic
- Communicates with database
- Contains validations, associations, and scopes
class User < ApplicationRecord
validates :email, presence: true
has_many :posts
def full_name
"#{first_name} #{last_name}"
end
end
View:
- Presents data to user
- Contains HTML with embedded Ruby
- What the user sees
<h1><%= @user.full_name %></h1>
<p>Email: <%= @user.email %></p>
Controller:
- Handles HTTP requests
- Gets data from Model
- Sends data to View
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
end
Q5: What is the request-response cycle in Rails?
Answer:
- Browser sends HTTP request
- Rails router matches URL to controller action
- Controller action executes
- Controller interacts with Model (if needed)
- Controller renders View
- HTML sent back to browser
ActiveRecord & Database
Q6: What is ActiveRecord?
Answer: ActiveRecord is Rails' ORM (Object-Relational Mapping) layer. It maps database tables to Ruby classes and rows to objects.
Key Features:
- CRUD operations without SQL
- Associations between models
- Validations
- Callbacks
- Query interface
- Migrations
Example:
# Find a user
user = User.find(1)
# Create a post for that user
post = user.posts.create(title: "Hello", body: "World")
# Query with conditions
active_users = User.where(active: true).order(:name)
Q7: Explain associations in Rails
Answer: Associations define relationships between models.
Types:
# belongs_to - Child belongs to parent
class Post < ApplicationRecord
belongs_to :user
belongs_to :category, optional: true
end
# has_many - Parent has many children
class User < ApplicationRecord
has_many :posts
has_many :comments
has_many :following, through: :relationships
end
# has_one - One-to-one relationship
class User < ApplicationRecord
has_one :profile
end
class Profile < ApplicationRecord
belongs_to :user
end
# has_and_belongs_to_many - Many-to-many
class Student < ApplicationRecord
has_and_belongs_to_many :courses
end
class Course < ApplicationRecord
has_and_belongs_to_many :students
end
# has_many :through - Many-to-many with join model
class Doctor < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
Q8: What are validations and callbacks?
Answer:
Validations ensure data integrity before saving to database:
class User < ApplicationRecord
# Presence
validates :name, :email, presence: true
# Uniqueness
validates :email, uniqueness: true
# Length
validates :password, length: { minimum: 6 }
# Format
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
# Numericality
validates :age, numericality: { greater_than: 0 }
# Inclusion
validates :role, inclusion: { in: %w[user admin editor] }
# Custom validation
validate :email_domain
end
Callbacks run at specific points in object lifecycle:
class User < ApplicationRecord
before_validation :normalize_email
after_create :send_welcome_email
before_save :encrypt_password
after_destroy :cleanup_files
private
def normalize_email
self.email = email.to_s.downcase.strip
end
end
Q9: What are migrations?
Answer: Migrations are version control for your database schema. They allow you to modify the database structure over time.
# Generate migration
# rails generate migration AddAgeToUsers age:integer
class AddAgeToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :age, :integer
end
end
# Common migration methods:
create_table :products do |t|
t.string :name
t.text :description
t.decimal :price
t.references :category, foreign_key: true
t.timestamps
end
add_column :users, :admin, :boolean, default: false
remove_column :users, :old_column
rename_column :users, :name, :full_name
change_column :users, :bio, :text
add_index :users, :email, unique: true
add_foreign_key :posts, :users
Commands:
rails db:migrate # Run pending migrations
rails db:rollback # Undo last migration
rails db:migrate:status # Check migration status
Q10: How to handle N+1 queries?
Answer: N+1 query problem occurs when you load parent records and then loop through to load child records.
Problem:
# Bad - N+1 queries
@posts = Post.all
@posts.each do |post|
puts post.user.name # Makes a query for EACH post
end
# 1 query for posts + N queries for users = N+1
Solutions:
# Solution 1: eager_load (LEFT JOIN)
@posts = Post.eager_load(:user).all
# Solution 2: includes (smart loading)
@posts = Post.includes(:user).all
# Solution 3: preload (separate queries)
@posts = Post.preload(:user).all
# For multiple associations
@posts = Post.includes(:user, :comments).all
# Nested associations
@posts = Post.includes(user: :profile).all
Routes & Controllers
Q11: Explain Rails routing
Answer: Routes map URLs to controller actions.
Resourceful Routes:
# config/routes.rb
Rails.application.routes.draw do
# RESTful routes for users
resources :users
# Limited routes
resources :posts, only: [:index, :show]
# Nested routes
resources :users do
resources :posts
end
# Custom routes
get '/about', to: 'pages#about'
post '/login', to: 'sessions#create'
# Root route
root 'home#index'
# API routes
namespace :api do
namespace :v1 do
resources :users
end
end
end
Generated Routes:
HTTP Verb Path Controller#Action
GET /users users#index
GET /users/new users#new
POST /users users#create
GET /users/:id users#show
GET /users/:id/edit users#edit
PATCH/PUT /users/:id users#update
DELETE /users/:id users#destroy
Q12: What are strong parameters?
Answer: Strong parameters protect against mass assignment vulnerabilities by whitelisting parameters.
class UsersController < ApplicationController
def create
# Only allow name and email, reject everything else
@user = User.new(user_params)
end
private
def user_params
params.require(:user).permit(:name, :email, :password)
end
end
# Nested parameters
def post_params
params.require(:post).permit(
:title,
:body,
comments_attributes: [:id, :body, :_destroy]
)
end
# Array parameters
def user_params
params.require(:user).permit(:name, tag_ids: [])
end
Q13: What are filters in controllers?
Answer: Filters are methods that run before, after, or around controller actions.
class ApplicationController < ActionController::Base
# Before filter
before_action :authenticate_user!
# With options
before_action :find_post, only: [:show, :edit, :update]
before_action :require_admin, except: [:index, :show]
# After filter
after_action :log_action
# Around filter
around_action :wrap_in_transaction
private
def authenticate_user!
redirect_to login_path unless current_user
end
def find_post
@post = Post.find(params[:id])
end
def wrap_in_transaction
ActiveRecord::Base.transaction do
yield
end
end
end
Views & Helpers
Q14: What are partials?
Answer: Partials are reusable view fragments.
<%# app/views/shared/_header.html.erb %>
<header>
<nav>
<%= link_to "Home", root_path %>
<%= link_to "About", about_path %>
</nav>
</header>
<%# Using the partial %>
<%= render "shared/header" %>
<%# Partial with locals %>
<%= render "user_card", user: @user %>
<%# Collection partial %>
<%= render @users %> <%# Renders _user.html.erb for each %>
<%# In _user.html.erb %>
<div class="user">
<h3><%= user.name %></h3>
<p><%= user.email %></p>
</div>
Q15: What are helpers?
Answer: Helpers are methods that can be used in views to keep templates clean.
# app/helpers/application_helper.rb
module ApplicationHelper
# Format date
def formatted_date(date)
date.strftime("%B %d, %Y")
end
# Active link class
def active_link_to(name, path)
link_to name, path, class: ("active" if current_page?(path))
end
# Avatar helper
def avatar_for(user, size: 50)
if user.avatar.attached?
image_tag user.avatar.variant(resize: "#{size}x#{size}")
else
image_tag "default_avatar.png", size: "#{size}x#{size}"
end
end
end
<%# Usage in view %>
<%= formatted_date(@post.created_at) %>
<%= active_link_to "Home", root_path %>
<%= avatar_for(@user) %>
Authentication & Authorization
Q16: How do you handle authentication in Rails?
Answer: Authentication verifies who a user is.
Option 1: Devise gem (most common)
# Gemfile
gem 'devise'
# Install
rails generate devise:install
rails generate devise User
rails db:migrate
# In views
<% if user_signed_in? %>
Welcome, <%= current_user.email %>
<%= link_to "Logout", destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to "Login", new_user_session_path %>
<%= link_to "Sign up", new_user_registration_path %>
<% end %>
Option 2: has_secure_password (built-in)
# Migration
add_column :users, :password_digest, :string
# Model
class User < ApplicationRecord
has_secure_password
validates :email, presence: true, uniqueness: true
end
# Sessions controller
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_path
else
flash.now[:alert] = "Invalid credentials"
render :new
end
end
end
Q17: How do you handle authorization?
Answer: Authorization determines what a user can do.
Option 1: Pundit
# Gemfile
gem 'pundit'
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def update?
user.admin? || record.user == user
end
def destroy?
user.admin?
end
end
# In controller
def update
@post = Post.find(params[:id])
authorize @post
# ...
end
# In view
<% if policy(@post).update? %>
<%= link_to "Edit", edit_post_path(@post) %>
<% end %>
Option 2: CanCanCan
# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.admin?
can :manage, :all
else
can :read, Post
can :create, Post
can :update, Post, user_id: user.id
end
end
end
Testing
Q18: How do you test in Rails?
Answer: Rails has built-in testing with Minitest, but RSpec is more common.
RSpec Setup:
# Gemfile
group :test, :development do
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'faker'
end
# Model test
# spec/models/user_spec.rb
RSpec.describe User, type: :model do
it { should validate_presence_of(:email) }
it { should have_many(:posts) }
it "creates a valid user" do
user = create(:user)
expect(user).to be_valid
end
end
# Controller test
# spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, type: :controller do
describe "GET #index" do
it "returns success" do
get :index
expect(response).to be_successful
end
end
end
# Request test
# spec/requests/users_spec.rb
RSpec.describe "Users API", type: :request do
it "returns users" do
get "/api/v1/users"
expect(response).to have_http_status(200)
end
end
Performance & Optimization
Q19: How do you optimize Rails applications?
Answer:
1. Database Optimization
# Add indexes
add_index :posts, :user_id
add_index :comments, [:commentable_id, :commentable_type]
# Counter cache
class Post < ApplicationRecord
has_many :comments, counter_cache: true
end
# Select only needed columns
User.select(:id, :name).all
2. Caching
# Fragment caching
<% cache @post do %>
<%= render @post %>
<% end %>
# Russian doll caching
<% cache [@post, "show"] do %>
<% cache @post do %>
<h1><%= @post.title %></h1>
<% end %>
<% cache @post.comments do %>
<%= render @post.comments %>
<% end %>
<% end %>
# Low-level caching
def expensive_calculation
Rails.cache.fetch("key", expires_in: 1.hour) do
# expensive operation
end
end
3. Background Jobs
# app/jobs/send_email_job.rb
class SendEmailJob < ApplicationJob
queue_as :default
def perform(user_id)
user = User.find(user_id)
UserMailer.welcome(user).deliver_now
end
end
# Enqueue
SendEmailJob.perform_later(user.id)
4. Asset Optimization
# Compress assets
config.assets.css_compressor = :sass
config.assets.js_compressor = :terser
# Use CDN
config.action_controller.asset_host = "https://cdn.example.com"
Security
Q20: What security features does Rails provide?
Answer:
1. CSRF Protection
# config/application.rb
protect_from_forgery with: :exception
# In forms (automatically included)
<%= form_with model: @user do %>
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
<% end %>
2. SQL Injection Protection
# Bad - vulnerable
User.where("name = '#{params[:name]}'")
# Good - safe
User.where(name: params[:name])
User.where("name = ?", params[:name])
3. XSS Protection
<%= user_input %> <!-- Automatically escaped -->
<%= raw user_input %> <!-- Dangerous, avoid -->
4. Strong Parameters
def user_params
params.require(:user).permit(:name, :email)
end
5. Secure Headers
# config/application.rb
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff'
}
Deployment
Q21: How do you deploy a Rails app?
Answer:
Heroku (simplest):
# Setup
heroku create myapp
heroku addons:create heroku-postgresql
# Deploy
git push heroku main
heroku run rails db:migrate
heroku open
Configuration for production:
# config/environments/production.rb
Rails.application.configure do
# Force SSL
config.force_ssl = true
# Cache
config.cache_store = :redis_cache_store
# Logging
config.log_level = :info
# Assets
config.assets.compile = false
end
Environment variables:
# Set secrets
heroku config:set SECRET_KEY_BASE=your_secret
heroku config:set DATABASE_URL=postgresql://...
Rails 7+ Features
Q22: What's new in Rails 7?
Answer:
1. Hotwire (Turbo + Stimulus)
# app/views/posts/_post.html.erb
<%= turbo_frame_tag dom_id(post) do %>
<h3><%= post.title %></h3>
<%= link_to "Edit", edit_post_path(post) %>
<% end %>
# Real-time updates
<%= turbo_stream_from "posts" %>
2. Import Maps (no Webpacker)
# config/importmap.rb
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js"
3. Encrypted Credentials
rails credentials:edit
# In code
Rails.application.credentials.aws[:access_key_id]
4. ActiveRecord async queries
# Non-blocking queries
posts = Post.where(published: true).load_async
comments = Comment.where(post_id: posts.map(&:id)).load_async
Common Interview Questions
Q23: What is the difference between render and redirect_to?
Answer:
- render - renders a view template (stays in same request)
- redirect_to - sends new HTTP request to different URL
def create
@user = User.new(user_params)
if @user.save
redirect_to @user # New request
else
render :new # Same request, shows form with errors
end
end
Q24: Explain save, save!, update, update!
Answer:
- save - returns true/false, doesn't raise error
- save! - raises exception on failure
- update - updates and saves, returns true/false
- update! - updates and saves, raises on failure
Q25: What are concerns in Rails?
Answer: Concerns allow sharing code between models or controllers.
# app/models/concerns/taggable.rb
module Taggable
extend ActiveSupport::Concern
included do
has_many :taggings, as: :taggable
has_many :tags, through: :taggings
end
def tag_list
tags.map(&:name).join(', ')
end
end
# Use in model
class Post < ApplicationRecord
include Taggable
end
class Product < ApplicationRecord
include Taggable
end
Q26: What is the asset pipeline?
Answer: The asset pipeline processes and serves CSS, JavaScript, and images.
Features:
- Concatenation - combine files
- Minification - reduce file size
- Fingerprinting - cache busting
- Preprocessing - SASS, CoffeeScript
Directory structure:
app/assets/
โโโ images/
โโโ javascripts/
โ โโโ application.js
โโโ stylesheets/
โโโ application.css
Q27: Explain polymorphic associations
Answer: Polymorphic associations allow a model to belong to multiple other models.
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
def change
create_table :comments do |t|
t.text :body
t.references :commentable, polymorphic: true
t.timestamps
end
end
end
Q28: What are scopes?
Answer: Scopes are reusable query definitions.
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc).limit(5) }
scope :by_author, ->(author_id) { where(author_id: author_id) }
# Can be chained
# Post.published.recent.by_author(1)
end
Q29: Explain includes, joins, preload, eager_load
Answer:
# includes - smart loading (chooses between preload and eager_load)
Post.includes(:comments)
# joins - INNER JOIN, doesn't load association
Post.joins(:comments).where(comments: { approved: true })
# preload - separate queries
Post.preload(:comments)
# eager_load - LEFT OUTER JOIN
Post.eager_load(:comments)
Q30: What are decorators/presenters?
Answer: Decorators add view-specific logic to models.
# Using Draper gem
class PostDecorator < Draper::Decorator
delegate_all
def published_date
published_at.strftime("%B %d, %Y")
end
def excerpt(length = 100)
body.truncate(length)
end
end
# In controller
@post = Post.find(params[:id]).decorate
# In view
<%= @post.published_date %>
<%= @post.excerpt(50) %>
# Ruby on Rails Interview Questions - Continued
## Table of Contents (Continued)
13. [Advanced ActiveRecord](#13-advanced-activerecord)
14. [Background Jobs](#14-background-jobs)
15. [API Development](#15-api-development)
16. [Real-time Features](#16-real-time-features)
17. [File Uploads](#17-file-uploads)
18. [Internationalization](#18-internationalization)
19. [Caching Strategies](#19-caching-strategies)
20. [Database Optimization](#20-database-optimization)
21. [Common Gems](#21-common-gems)
22. [Troubleshooting & Debugging](#22-troubleshooting--debugging)
23. [Rails Anti-patterns](#23-rails-anti-patterns)
24. [Code Organization](#24-code-organization)
25. [Interview Scenarios](#25-interview-scenarios)
---
## Advanced ActiveRecord
### Q31: What is the difference between `find` and `find_by`?
**Answer:**
```ruby
# find - Finds by ID, raises exception if not found
user = User.find(1) # => User object or ActiveRecord::RecordNotFound
# find_by - Finds by any attribute, returns nil if not found
user = User.find_by(email: "john@example.com") # => User or nil
# find_by! - Finds by attribute, raises if not found
user = User.find_by!(email: "john@example.com") # => User or exception
# where - Returns collection (always an array-like object)
users = User.where(active: true) # => ActiveRecord::Relation
Q32: Explain pluck vs select
Answer:
# select - Returns ActiveRecord objects (slower, more memory)
users = User.select(:id, :name).limit(10)
users.each { |u| puts u.name } # Objects have id and name only
# pluck - Returns array of values (faster, less memory)
names = User.limit(10).pluck(:name) # => ["John", "Jane", ...]
# pluck multiple columns
data = User.limit(10).pluck(:id, :name, :email)
# => [[1, "John", "john@example.com"], ...]
# pick - Get first value only
User.pick(:name) # => "John" (first user's name)
Q33: What are transactions in Rails?
Answer: Transactions ensure multiple database operations succeed or fail together.
# Basic transaction
ActiveRecord::Base.transaction do
user.save!
account.update!(balance: account.balance - amount)
# If anything fails, everything rolls back
end
# Nested transactions
User.transaction do
user.update!(name: "New Name")
Post.transaction(requires_new: true) do
post.update!(title: "New Title")
raise ActiveRecord::Rollback if something_wrong? # Only rollback inner
end
end
# Different database connections
ApplicationRecord.transaction do
User.update_all(active: false)
Profile.transaction do
Profile.update_all(visible: false)
end
end
Q34: Explain locking mechanisms
Answer:
Optimistic Locking:
# Add lock_version column
# migration: add_column :products, :lock_version, :integer, default: 0
product = Product.find(1)
product.update!(price: 100) # Raises if lock_version changed
# Automatic - Rails checks lock_version on update
Pessimistic Locking:
# Lock row for update (database-level lock)
product = Product.lock.find(1)
product.update!(price: 100) # Other sessions wait
# With timeout
Product.transaction do
product = Product.lock("FOR UPDATE NOWAIT").find(1)
# Raises if row is locked
end
# Different lock types
Product.lock("FOR SHARE").find(1) # Shared lock (read-only)
Q35: What are enums in Rails?
Answer: Enums map database integers to readable names.
# Model
class Order < ApplicationRecord
enum :status, [:pending, :processing, :completed, :cancelled]
# or with hash
enum :role, { user: 0, editor: 1, admin: 2 }
end
# Migration
add_column :orders, :status, :integer, default: 0
# Usage
order = Order.new
order.pending? # => true
order.processing! # Update to processing status
# Scopes automatically created
Order.pending # All pending orders
Order.completed # All completed orders
# Query by status
Order.where(status: :pending)
Order.where.not(status: [:completed, :cancelled])
Background Jobs
Q36: How do you implement background jobs in Rails?
Answer:
ActiveJob (built-in):
# Generate job
rails generate job send_welcome_email
# app/jobs/send_welcome_email_job.rb
class SendWelcomeEmailJob < ApplicationJob
queue_as :default
def perform(user_id)
user = User.find(user_id)
UserMailer.welcome(user).deliver_now
end
end
# Enqueue job
SendWelcomeEmailJob.perform_later(user.id)
SendWelcomeEmailJob.set(wait: 1.hour).perform_later(user.id)
SendWelcomeEmailJob.set(queue: :urgent).perform_later(user.id)
Sidekiq (most popular):
# Gemfile
gem 'sidekiq'
# app/workers/hard_worker.rb
class HardWorker
include Sidekiq::Worker
def perform(name, count)
puts "Working on #{name} #{count}"
end
end
# Enqueue
HardWorker.perform_async('bob', 5)
HardWorker.perform_in(5.minutes, 'bob', 5)
# Sidekiq web UI
# mount Sidekiq::Web => '/sidekiq' in routes.rb
Q37: What are different queuing backends?
Answer:
# config/application.rb
config.active_job.queue_adapter = :sidekiq # or :resque, :delayed_job
# Sidekiq (Redis) - Fast, web UI
gem 'sidekiq'
# Resque (Redis) - Simple, web UI
gem 'resque'
# Delayed Job (Database) - No extra dependencies
gem 'delayed_job_active_record'
# Good Job (PostgreSQL) - Uses Postgres advisory locks
gem 'good_job'
# Que (PostgreSQL) - Uses Postgres SKIP LOCKED
gem 'que'
# Queue options
class ApplicationJob < ActiveJob::Base
queue_as :default
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
discard_on ActiveJob::DeserializationError
end
Q38: How do you handle failed jobs?
Answer:
# Automatic retries
class MyJob < ApplicationJob
retry_on StandardError, wait: :exponentially_longer, attempts: 5
retry_on NetworkError, wait: 10.seconds, attempts: 3
discard_on PermanentError do |job, error|
# Log or notify about permanent failure
end
def perform
# job code
end
end
# Manual retry in Sidekiq
# Retry from web UI or programmatically
worker.retry
# Custom retry logic
class CustomJob
include Sidekiq::Worker
sidekiq_options retry: 5
def perform
# job code
rescue TemporaryError => e
raise e if retry_count >= 3
sleep 5
retry
end
end
API Development
Q39: How do you build APIs in Rails?
Answer:
API-only mode:
rails new myapi --api
Serializers:
# app/serializers/user_serializer.rb
class UserSerializer
def initialize(user)
@user = user
end
def as_json
{
id: @user.id,
name: @user.name,
email: @user.email,
created_at: @user.created_at
}
end
end
# In controller
def show
@user = User.find(params[:id])
render json: UserSerializer.new(@user)
end
JBuilder (built-in):
# app/views/api/v1/users/index.json.jbuilder
json.array! @users do |user|
json.id user.id
json.name user.name
json.email user.email
json.posts_count user.posts.count
end
# app/views/api/v1/users/show.json.jbuilder
json.user do
json.extract! @user, :id, :name, :email, :created_at
json.posts @user.posts do |post|
json.extract! post, :id, :title
end
end
Fast JSON API (jsonapi-serializer):
# Gemfile
gem 'jsonapi-serializer'
class UserSerializer
include JSONAPI::Serializer
attributes :id, :name, :email, :created_at
has_many :posts
end
# Controller
render json: UserSerializer.new(@users).serializable_hash
Q40: How to version APIs?
Answer:
# routes.rb
namespace :api do
namespace :v1 do
resources :users
resources :posts
end
namespace :v2 do
resources :users
resources :posts
end
end
# controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
def index
@users = User.all
render json: @users
end
end
end
end
# Version via headers
class ApiController < ApplicationController
before_action :check_version
def check_version
if request.headers['API-Version'] == '2'
extend Api::V2::Compatibility
end
end
end
Q41: How to handle API authentication?
Answer:
Token-based:
# Gemfile
gem 'jwt'
class Api::V1::BaseController < ApplicationController
before_action :authenticate!
private
def authenticate!
token = request.headers['Authorization']&.split(' ')&.last
payload = JWT.decode(token, Rails.application.secret_key_base)[0]
@current_user = User.find(payload['user_id'])
rescue JWT::DecodeError
render json: { error: 'Unauthorized' }, status: :unauthorized
end
end
# Generate token
payload = { user_id: user.id, exp: 24.hours.from_now.to_i }
token = JWT.encode(payload, Rails.application.secret_key_base)
API Keys:
class ApplicationController
before_action :require_api_key
def require_api_key
key = ApiKey.find_by(token: params[:api_key])
render json: { error: 'Invalid API key' }, status: :unauthorized unless key
end
end
OAuth (Doorkeeper):
# Gemfile
gem 'doorkeeper'
# Protect API
class ApiController < ApplicationController
before_action :doorkeeper_authorize!
def current_user
@current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end
end
Q42: How to handle rate limiting?
Answer:
# Using rack-attack
# config/initializers/rack_attack.rb
class Rack::Attack
throttle('api/ip', limit: 100, period: 1.minute) do |req|
req.ip if req.path.start_with?('/api/')
end
throttle('authenticated/api', limit: 1000, period: 1.hour) do |req|
req.env['HTTP_AUTHORIZATION'] if req.path.start_with?('/api/')
end
blocklist('block bad IPs') do |req|
['1.2.3.4', '5.6.7.8'].include?(req.ip)
end
end
# Custom throttling
class ApiController < ApplicationController
before_action :check_rate_limit
def check_rate_limit
key = "rate_limit:#{current_user.id}:#{Time.now.to_i / 60}"
count = Rails.cache.increment(key, 1, expires_in: 1.minute)
if count > 100
render json: { error: 'Rate limit exceeded' }, status: :too_many_requests
end
end
end
Real-time Features
Q43: How to implement real-time features in Rails?
Answer:
Action Cable (WebSockets):
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
def receive(data)
message = current_user.messages.create!(content: data['message'])
ActionCable.server.broadcast("chat_#{params[:room]}", message)
end
end
# app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "general" }, {
received(data) {
// Append message to DOM
},
speak(message) {
this.perform('receive', { message: message })
}
})
# Broadcast from anywhere
ActionCable.server.broadcast("chat_general", { user: "John", message: "Hello" })
Turbo Streams (Rails 7):
<%# app/views/posts/index.html.erb %>
<%= turbo_stream_from "posts" %>
<div id="posts">
<%= render @posts %>
</div>
<%# app/views/posts/_post.html.erb %>
<%= turbo_frame_tag dom_id(post) do %>
<h3><%= post.title %></h3>
<% end %>
<%# Controller %>
def create
@post = Post.create(post_params)
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.prepend("posts", @post)
end
end
end
Q44: What is Hotwire?
Answer: Hotwire is Rails 7's default approach to modern web apps without much JavaScript.
Components:
-
Turbo - Makes page updates fast
- Turbo Drive: Fast page loads
- Turbo Frames: Decompose pages into independent pieces
- Turbo Streams: Deliver page updates over WebSocket
-
Stimulus - Modest JavaScript framework
// app/javascript/controllers/hello_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["name"] greet() { console.log(`Hello, ${this.nameTarget.value}`) } }<div data-controller="hello"> <input data-hello-target="name" type="text"> <button data-action="click->hello#greet">Greet</button> </div>
File Uploads
Q45: How to handle file uploads in Rails?
Answer:
Active Storage (built-in):
# Setup
rails active_storage:install
rails db:migrate
# Model
class User < ApplicationRecord
has_one_attached :avatar
has_many_attached :documents
end
# Form
<%= form.file_field :avatar %>
# Controller
def user_params
params.require(:user).permit(:name, :email, :avatar)
end
# Display
<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>
<%= link_to "Download", @user.avatar %>
# Direct uploads
<%= form.file_field :avatar, direct_upload: true %>
# Multiple files
<%= form.file_field :documents, multiple: true %>
Direct upload to cloud:
# config/storage.yml
amazon:
service: S3
access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
region: us-east-1
bucket: myapp-uploads
# config/environments/production.rb
config.active_storage.service = :amazon
Q46: How to process images?
Answer:
# Gemfile
gem 'image_processing'
# Model
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize_to_limit: [50, 50]
attachable.variant :medium, resize_to_limit: [200, 200]
attachable.variant :large, resize_to_limit: [500, 500]
end
end
# In views
<%= image_tag @user.avatar.variant(:thumb) %>
# Custom processing
class ImageProcessor
def self.process(image)
image
.processed
.resize_to_limit(800, 800)
.saver(quality: 80)
end
end
# Use processor
image.variant(processor: ImageProcessor)
Q47: File validation
Answer:
class User < ApplicationRecord
has_one_attached :avatar
validate :avatar_content_type
validate :avatar_size
private
def avatar_content_type
return unless avatar.attached?
allowed_types = ['image/jpeg', 'image/png', 'image/gif']
unless allowed_types.include?(avatar.content_type)
errors.add(:avatar, 'must be a JPEG, PNG, or GIF')
end
end
def avatar_size
return unless avatar.attached?
if avatar.byte_size > 5.megabytes
errors.add(:avatar, 'should be less than 5MB')
end
end
end
Internationalization
Q48: How to handle multiple languages in Rails?
Answer:
Setup I18n:
# config/application.rb
config.i18n.available_locales = [:en, :es, :fr]
config.i18n.default_locale = :en
config.i18n.fallbacks = true
Locale files:
# config/locales/en.yml
en:
hello: "Hello"
goodbye: "Goodbye"
user:
name: "Name"
email: "Email"
submit: "Save User"
# config/locales/es.yml
es:
hello: "Hola"
goodbye: "Adiรณs"
user:
name: "Nombre"
email: "Correo"
submit: "Guardar Usuario"
In views:
<h1><%= t('hello') %></h1>
<%= form.label :name, t('user.name') %>
<%= form.submit t('user.submit') %>
<%# With interpolation %>
<%= t('welcome', name: @user.name) %>
In controllers:
def set_locale
I18n.locale = params[:locale] || session[:locale] || I18n.default_locale
session[:locale] = I18n.locale
end
# URL helpers
# config/routes.rb
scope "/:locale", locale: /en|es|fr/ do
resources :users
end
Model translations:
# Gemfile
gem 'globalize'
class Post < ApplicationRecord
translates :title, :body
# Add translations table
# rails generate migration CreatePostTranslations
end
# Usage
I18n.locale = :es
post.title = "Tรญtulo"
post.save
Caching Strategies
Q49: What caching strategies are available in Rails?
Answer:
1. Page Caching (legacy)
caches_page :index
2. Action Caching (with gem)
# Gemfile
gem 'actionpack-action_caching'
caches_action :index, expires_in: 1.hour
3. Fragment Caching
<% cache @product do %>
<div class="product">
<%= render @product %>
</div>
<% end %>
<% cache_if @product.featured?, @product do %>
<!-- Expensive content -->
<% end %>
<% cache_unless admin_signed_in?, @product do %>
<!-- Content for non-admins -->
<% end %>
4. Russian Doll Caching
<% cache @post do %>
<h1><%= @post.title %></h1>
<% cache @post.comments do %>
<%= render @post.comments %>
<% end %>
<% end %>
5. Low-level Caching
class Product < ApplicationRecord
def expensive_calculation
Rails.cache.fetch("product_#{id}_calc", expires_in: 1.hour) do
# Expensive database queries or calculations
result = perform_calculation
result
end
end
end
# Cache with version
Rails.cache.fetch("key", version: "v1") { expensive }
6. HTTP Caching
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
if stale?(last_modified: @product.updated_at, etag: @product)
# Response will be rendered
render json: @product
end
end
def index
@products = Product.all
fresh_when last_modified: @products.maximum(:updated_at),
etag: @products
end
end
7. SQL Caching
# Same query within same request is cached
User.find(1)
User.find(1) # Uses cache, no DB query
8. Cache Stores
# config/environments/production.rb
config.cache_store = :redis_cache_store, {
url: ENV['REDIS_URL'],
namespace: 'cache',
expires_in: 1.day
}
# Memory store (development)
config.cache_store = :memory_store, { size: 64.megabytes }
Database Optimization
Q50: How to optimize database queries?
Answer:
1. Add Indexes
# migration
add_index :users, :email, unique: true
add_index :posts, [:user_id, :created_at]
# Check slow queries
# Enable in PostgreSQL
ActiveRecord::Base.connection.execute("SET logging_collector=on;")
2. Batch Processing
# Bad - loads all records
User.all.each { |user| user.update(active: true) }
# Good - batches
User.find_each(batch_size: 1000) do |user|
user.update(active: true)
end
# find_in_batches gives array of records
User.find_in_batches(batch_size: 1000) do |users|
User.where(id: users.map(&:id)).update_all(active: true)
end
3. Select Only Needed Columns
# Bad
users = User.all
# Good
users = User.select(:id, :name, :email)
# Using pluck
names = User.where(active: true).pluck(:name)
4. Avoid N+1 with includes
# Bad
@posts = Post.all
@posts.each { |p| puts p.user.name } # N+1 queries
# Good
@posts = Post.includes(:user).all
5. Use Counter Cache
# migration
add_column :users, :posts_count, :integer, default: 0
# model
class Post < ApplicationRecord
belongs_to :user, counter_cache: true
end
# Now use user.posts_count instead of user.posts.count
6. Database Views
# Create view in migration
def up
execute <<-SQL
CREATE VIEW user_summaries AS
SELECT users.id, users.name, COUNT(posts.id) as post_count
FROM users
LEFT JOIN posts ON users.id = posts.user_id
GROUP BY users.id;
SQL
end
# Model for view
class UserSummary < ApplicationRecord
self.table_name = 'user_summaries'
self.primary_key = 'id'
end
7. Use EXISTS for Existence Checks
# Bad
users = User.where("(SELECT COUNT(*) FROM posts WHERE posts.user_id = users.id) > 0")
# Good
users = User.where("EXISTS (SELECT 1 FROM posts WHERE posts.user_id = users.id)")
8. Bulk Operations
# Bulk insert
users_data = 1000.times.map { { name: "User #{_1}", email: "user#{_1}@example.com" } }
User.insert_all(users_data)
# Bulk update
User.where(active: false).update_all(active: true)
Common Gems
Q51: What are essential gems for Rails projects?
Answer:
Development:
group :development do
gem 'pry-rails' # Better console
gem 'better_errors' # Better error pages
gem 'binding_of_caller' # REPL on error pages
gem 'bullet' # N+1 query detection
gem 'rubocop' # Code style
gem 'annotate' # Schema comments in models
gem 'letter_opener' # Preview emails
end
Testing:
group :test, :development do
gem 'rspec-rails' # Testing framework
gem 'factory_bot_rails' # Test data
gem 'faker' # Fake data
gem 'shoulda-matchers' # One-liner tests
gem 'database_cleaner' # Clean database between tests
gem 'simplecov' # Test coverage
end
Production:
gem 'puma' # Web server
gem 'sidekiq' # Background jobs
gem 'redis' # Redis client
gem 'scout_apm' # Performance monitoring
gem 'lograge' # Better logging
gem 'rack-attack' # Rate limiting
Authentication/Authorization:
gem 'devise' # Authentication
gem 'pundit' # Authorization
gem 'cancancan' # Authorization (alternative)
API:
gem 'jbuilder' # JSON views
gem 'active_model_serializers' # JSON serialization
gem 'rack-cors' # CORS support
gem 'versionist' # API versioning
Admin:
gem 'rails_admin' # Admin interface
gem 'activeadmin' # Admin interface
gem 'administrate' # Admin interface (Rails 5+)
Search:
gem 'ransack' # Search
gem 'pg_search' # Full-text search with PostgreSQL
gem 'elasticsearch-model' # Elasticsearch
gem 'sunspot_rails' # Solr search
File Uploads:
gem 'shrine' # File uploads (flexible)
gem 'carrierwave' # File uploads (simpler)
gem 'mini_magick' # Image processing
gem 'aws-sdk-s3' # AWS S3
Pagination:
gem 'kaminari' # Pagination
gem 'will_paginate' # Pagination (simpler)
SEO:
gem 'meta-tags' # Meta tags for SEO
gem 'sitemap_generator' # Generate sitemaps
gem 'friendly_id' # SEO-friendly URLs
Troubleshooting & Debugging
Q52: How do you debug a Rails application?
Answer:
1. Console Debugging
# In code
byebug # or binding.pry
puts "Debug: #{variable}"
Rails.logger.debug "User: #{user.inspect}"
# Rails console
rails console
User.find(1)
2. Pry Debugging
# Gemfile
gem 'pry-rails'
gem 'pry-byebug'
# In code
binding.pry
# Then use:
# next - step over
# step - step into
# continue - continue execution
# whereami - show current code
3. Logging
# config/environments/development.rb
config.log_level = :debug
# Custom logs
Rails.logger.info("Processing user #{user.id}")
Rails.logger.debug("Params: #{params.inspect}")
# Tagged logging
logger.tagged("User #{user.id}") do
logger.info("Processing payment")
end
4. Exception Tracking
# Gemfile
gem 'sentry-ruby'
gem 'sentry-rails'
# config/initializers/sentry.rb
Sentry.init do |config|
config.dsn = 'your-dsn'
config.breadcrumbs_logger = [:active_support_logger]
end
# Track manually
Sentry.capture_message("Something went wrong")
Sentry.capture_exception(exception)
5. Performance Profiling
# Gemfile
gem 'rack-mini-profiler'
# In code
Rack::MiniProfiler.step("Expensive operation") do
# code to profile
end
# Memory profiling
require 'memory_profiler'
report = MemoryProfiler.report do
# code to profile
end
report.pretty_print
6. Query Debugging
# See SQL in logs
ActiveRecord::Base.logger = Logger.new(STDOUT)
# Benchmark queries
Benchmark.measure { User.where(active: true).to_a }
# Explain query
puts User.where(active: true).explain
Rails Anti-patterns
Q53: What are common Rails anti-patterns?
Answer:
1. Fat Models, Skinny Controllers (Overdone)
# Bad - Model too fat
class User < ApplicationRecord
def send_welcome_email
# email logic
end
def generate_report
# reporting logic
end
def process_payment
# payment logic
end
end
# Good - Use service objects
class User < ApplicationRecord
# Only data and associations
end
class UserMailer
def send_welcome(user)
# email logic
end
end
class ReportGenerator
def generate(user)
# reporting logic
end
end
2. Callback Hell
# Bad - Too many callbacks
class Order < ApplicationRecord
before_save :calculate_tax
before_save :apply_discount
before_save :validate_inventory
after_save :send_confirmation
after_save :update_inventory
after_save :notify_warehouse
end
# Good - Use explicit methods
class OrdersController < ApplicationController
def create
@order = Order.new(order_params)
if @order.save
OrderProcessor.new(@order).process
end
end
end
3. Nested Routes Too Deep
# Bad
resources :users do
resources :posts do
resources :comments do
resources :likes
end
end
end
# Good
resources :users do
resources :posts, only: [:index, :new, :create]
end
resources :posts do
resources :comments
resources :likes
end
4. Logic in Views
<%# Bad %>
<% if @user.admin? && @user.posts.any? && @user.created_at > 1.month.ago %>
<%= @user.posts.select { |p| p.published? }.map(&:title).join(", ") %>
<% end %>
<%# Good - Use helpers or decorators %>
<%= display_admin_posts(@user) %>
5. Ignoring Database Indexes
# Bad - Missing indexes
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.references :user # Missing index!
t.timestamps
end
end
end
# Good - Add indexes
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.references :user, index: true
t.timestamps
end
add_index :posts, [:user_id, :created_at]
end
end
Code Organization
Q54: How to organize code beyond MVC?
Answer:
1. Service Objects
# app/services/user_creator.rb
class UserCreator
def initialize(params)
@params = params
end
def call
@user = User.new(@params)
if @user.save
send_welcome_email
create_default_settings
track_analytics
end
@user
end
private
def send_welcome_email
UserMailer.welcome(@user).deliver_later
end
end
# Controller
def create
@user = UserCreator.new(user_params).call
# ...
end
2. Query Objects
# app/queries/active_users_query.rb
class ActiveUsersQuery
def initialize(relation = User.all)
@relation = relation
end
def call
@relation.where(active: true)
end
def with_posts
@relation.joins(:posts).distinct
end
def recent(days = 30)
@relation.where('last_active_at > ?', days.days.ago)
end
end
# Usage
users = ActiveUsersQuery.new.call.recent
3. Form Objects
# app/forms/registration_form.rb
class RegistrationForm
include ActiveModel::Model
attr_accessor :name, :email, :password, :terms
validates :name, presence: true
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, length: { minimum: 6 }
validates :terms, acceptance: true
def save
return false unless valid?
ActiveRecord::Base.transaction do
user = User.create!(name: name, email: email, password: password)
user.create_profile!
user
end
end
end
4. Policy Objects
# app/policies/post_policy.rb
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
@user = user
@post = post
end
def edit?
user.admin? || user == post.author
end
def publish?
user.editor? || user.admin?
end
end
5. Decorators/Presenters
# app/decorators/user_decorator.rb
class UserDecorator < SimpleDelegator
def full_name
"#{first_name} #{last_name}".strip
end
def joined_date
created_at.strftime("%B %d, %Y")
end
def status_badge
active? ? "Active" : "Inactive"
end
end
6. Value Objects
# app/value_objects/money.rb
class Money
include Comparable
attr_reader :amount, :currency
def initialize(amount, currency = "USD")
@amount = amount
@currency = currency
end
def +(other)
raise "Currency mismatch" unless currency == other.currency
Money.new(amount + other.amount, currency)
end
def <=>(other)
amount <=> other.amount if currency == other.currency
end
end
Interview Scenarios
Q55: Design a Blog Platform
Answer:
# Models
class User < ApplicationRecord
has_many :posts
has_many :comments
enum :role, [:reader, :author, :editor, :admin]
def can_edit?(post)
admin? || post.author == self
end
end
class Post < ApplicationRecord
belongs_to :user, counter_cache: true
has_many :comments, dependent: :destroy
has_many :taggings
has_many :tags, through: :taggings
validates :title, :body, presence: true
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
def tag_list=(names)
self.tags = names.split(',').map do |name|
Tag.find_or_create_by(name: name.strip)
end
end
def tag_list
tags.map(&:name).join(', ')
end
end
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post, counter_cache: true
validates :body, presence: true
end
class Tag < ApplicationRecord
has_many :taggings
has_many :posts, through: :taggings
validates :name, presence: true, uniqueness: true
end
# Routes
resources :posts do
resources :comments, only: [:create, :destroy]
member do
patch :publish
patch :unpublish
end
collection do
get :drafts
get :feed, defaults: { format: 'rss' }
end
end
# Controllers
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def index
@posts = Post.published.recent
@posts = @posts.tagged_with(params[:tag]) if params[:tag]
@posts = @posts.page(params[:page])
end
def show
@post = Post.find(params[:id])
@comment = Comment.new
end
def create
@post = current_user.posts.new(post_params)
if @post.save
redirect_to @post, notice: "Post created!"
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, :body, :tag_list)
end
end
# Views
# app/views/posts/_post.html.erb
<div class="post">
<h2><%= link_to post.title, post %></h2>
<p class="meta">
By <%= post.user.name %> on <%= post.created_at.strftime("%B %d, %Y") %>
Tags: <%= post.tags.map { |t| link_to t.name, tag_path(t) }.join(", ").html_safe %>
</p>
<div class="body">
<%= truncate(post.body, length: 500) %>
</div>
<p><%= pluralize(post.comments_count, "comment") %></p>
</div>
Q56: Build an E-commerce Cart System
Answer:
# Models
class Product < ApplicationRecord
has_many :line_items
has_many :orders, through: :line_items
validates :name, :price, presence: true
validates :stock, numericality: { greater_than_or_equal_to: 0 }
end
class Cart < ApplicationRecord
has_many :line_items, dependent: :destroy
has_many :products, through: :line_items
def add_product(product_id, quantity = 1)
current_item = line_items.find_by(product_id: product_id)
if current_item
current_item.quantity += quantity
else
current_item = line_items.build(product_id: product_id, quantity: quantity)
end
current_item
end
def total_price
line_items.sum(&:total_price)
end
def empty?
line_items.empty?
end
end
class LineItem < ApplicationRecord
belongs_to :product
belongs_to :cart, optional: true
belongs_to :order, optional: true
validates :quantity, numericality: { greater__than: 0 }
def total_price
product.price * quantity
end
end
class Order < ApplicationRecord
belongs_to :user
has_many :line_items, dependent: :destroy
validates :address, presence: true
enum :status, [:pending, :paid, :shipped, :delivered, :cancelled]
def total_price
line_items.sum(&:total_price)
end
def process_payment(payment_method)
return false unless pending?
# Process payment logic
if payment_method.charge(total_price)
paid!
reduce_stock!
true
end
end
private
def reduce_stock!
line_items.each do |item|
item.product.decrement!(:stock, item.quantity)
end
end
end
# Controllers
class CartsController < ApplicationController
def show
@cart = current_cart
@line_item = LineItem.new
end
def destroy
current_cart.destroy
session[:cart_id] = nil
redirect_to root_path, notice: "Cart cleared"
end
end
class LineItemsController < ApplicationController
def create
product = Product.find(params[:product_id])
@cart = current_cart
if product.stock >= params[:quantity].to_i
@line_item = @cart.add_product(product.id, params[:quantity])
if @line_item.save
redirect_to cart_path, notice: "Item added to cart"
end
else
redirect_to product, alert: "Not enough stock"
end
end
def update
@line_item = LineItem.find(params[:id])
if @line_item.update(quantity: params[:quantity])
redirect_to cart_path
end
end
end
# Helpers
module CartsHelper
def current_cart
if session[:cart_id]
Cart.find(session[:cart_id])
else
cart = Cart.create
session[:cart_id] = cart.id
cart
end
end
end
Q57: Implement a Social Media Following System
Answer:
# Models
class User < ApplicationRecord
has_many :posts
has_many :comments
# Follow relationships
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id", dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id", dependent: :destroy
has_many :followers, through: :passive_relationships, source: :follower
# Timeline
def feed
Post.where(user_id: following_ids + [id])
.order(created_at: :desc)
end
# Follow helpers
def follow(other_user)
following << other_user unless self == other_user
end
def unfollow(other_user)
following.delete(other_user)
end
def following?(other_user)
following.include?(other_user)
end
end
class Relationship < ApplicationRecord
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, uniqueness: { scope: :followed_id }
validate :cannot_follow_self
private
def cannot_follow_self
if follower_id == followed_id
errors.add(:base, "Cannot follow yourself")
end
end
end
class Post < ApplicationRecord
belongs_to :user
has_many :likes
has_many :likers, through: :likes, source: :user
validates :body, presence: true, length: { maximum: 280 }
def liked_by?(user)
likes.exists?(user: user)
end
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :post
validates :user_id, uniqueness: { scope: :post_id }
end
# Controllers
class RelationshipsController < ApplicationController
before_action :authenticate_user!
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
format.html { redirect_back(fallback_location: @user) }
format.turbo_stream
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
format.html { redirect_back(fallback_location: @user) }
format.turbo_stream
end
end
end
# Routes
resources :users do
member do
get :following, :followers
end
end
resources :relationships, only: [:create, :destroy]
# Notifications (using Active Job)
class NewFollowerNotificationJob < ApplicationJob
def perform(follower_id, followed_id)
follower = User.find(follower_id)
followed = User.find(followed_id)
Notification.create!(
user: followed,
message: "#{follower.name} started following you"
)
# Send email or push notification
NotificationMailer.new_follower(follower, followed).deliver_later
end
end
Q58: Design a Task Management System (Todo List)
Answer:
# Models
class Project < ApplicationRecord
belongs_to :user
has_many :tasks, dependent: :destroy
validates :name, presence: true
enum :status, [:active, :archived]
scope :recent, -> { order(updated_at: :desc) }
def progress
return 0 if tasks.none?
(tasks.completed.count.to_f / tasks.count * 100).round
end
end
class Task < ApplicationRecord
belongs_to :project
belongs_to :assignee, class_name: "User", optional: true
validates :title, presence: true
validate :due_date_cannot_be_in_past
enum :priority, [:low, :medium, :high, :urgent]
enum :status, [:pending, :in_progress, :completed, :cancelled]
scope :overdue, -> { where("due_date < ? AND status != ?", Date.today, 2) }
scope :today, -> { where(due_date: Date.today) }
scope :upcoming, -> { where("due_date > ?", Date.today) }
def completed?
status == "completed"
end
def overdue?
due_date.present? && due_date < Date.today && !completed?
end
private
def due_date_cannot_be_in_past
if due_date.present? && due_date < Date.today
errors.add(:due_date, "can't be in the past")
end
end
end
class Tag < ApplicationRecord
has_many :taggings
has_many :tasks, through: :taggings
end
class Tagging < ApplicationRecord
belongs_to :tag
belongs_to :task
end
# Controllers
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update, :destroy]
def index
@projects = current_user.projects.recent
@stats = {
total_tasks: Task.where(project: @projects).count,
completed: Task.where(project: @projects, status: :completed).count,
overdue: Task.where(project: @projects).overdue.count
}
end
def show
@tasks = @project.tasks
@tasks = @tasks.where(status: params[:status]) if params[:status]
@tasks = @tasks.where(priority: params[:priority]) if params[:priority]
@tasks = @tasks.includes(:assignee).order(:due_date, :priority)
end
def create
@project = current_user.projects.new(project_params)
if @project.save
redirect_to @project, notice: "Project created"
else
render :new, status: :unprocessable_entity
end
end
private
def set_project
@project = current_user.projects.find(params[:id])
end
end
class TasksController < ApplicationController
def create
@project = current_user.projects.find(params[:project_id])
@task = @project.tasks.new(task_params)
if @task.save
redirect_to @project, notice: "Task added"
else
redirect_to @project, alert: @task.errors.full_messages.join(", ")
end
end
def update
@task = Task.find(params[:id])
if @task.update(task_params)
respond_to do |format|
format.html { redirect_back(fallback_location: @task.project) }
format.json { render json: { status: @task.status } }
format.turbo_stream
end
end
end
def batch_update
tasks = Task.where(id: params[:task_ids])
tasks.update_all(status: params[:status])
redirect_back(fallback_location: projects_path)
end
private
def task_params
params.require(:task).permit(:title, :description, :due_date,
:priority, :status, :assignee_id, tag_ids: [])
end
end
# Views
<%# app/views/projects/show.html.erb %>
<div data-controller="tasks">
<h1><%= @project.name %></h1>
<div class="progress-bar">
<div class="progress" style="width: <%= @project.progress %>%"></div>
</div>
<div class="filters">
<%= link_to "All", project_path(@project) %>
<%= link_to "Pending", project_path(@project, status: :pending) %>
<%= link_to "Completed", project_path(@project, status: :completed) %>
</div>
<div id="tasks">
<%= render @project.tasks %>
</div>
<%= form_with model: [@project, Task.new], data: { controller: "reset-form" } do |f| %>
<%= f.text_field :title, placeholder: "Add a new task..." %>
<%= f.date_field :due_date %>
<%= f.select :priority, Task.priorities.keys %>
<%= f.submit "Add" %>
<% end %>
</div>
Q59: Build a Notification System
Answer:
# Models
class Notification < ApplicationRecord
belongs_to :recipient, class_name: "User"
belongs_to :notifiable, polymorphic: true
scope :unread, -> { where(read_at: nil) }
scope :recent, -> { order(created_at: :desc) }
def read?
read_at.present?
end
def mark_as_read!
update(read_at: Time.current)
end
end
# Notifications from different sources
class CommentNotification < Notification
def message
"#{notifiable.user.name} commented on your post"
end
def link
post_path(notifiable.post)
end
end
class FollowNotification < Notification
def message
"#{notifiable.follower.name} started following you"
end
def link
user_path(notifiable.follower)
end
end
# Notification service
class NotificationService
def initialize(recipient)
@recipient = recipient
end
def notify(message, link: nil, notifiable: nil)
Notification.create!(
recipient: @recipient,
message: message,
link: link,
notifiable: notifiable
)
# Send real-time notification
broadcast_notification
end
def notify_many(users, message, link: nil)
notifications = users.map do |user|
{ recipient_id: user.id, message: message, link: link, created_at: Time.current }
end
Notification.insert_all(notifications)
broadcast_bulk
end
private
def broadcast_notification
Turbo::StreamsChannel.broadcast_prepend_to(
"notifications_#{@recipient.id}",
target: "notifications",
partial: "notifications/notification",
locals: { notification: self }
)
end
end
# Controllers
class NotificationsController < ApplicationController
before_action :authenticate_user!
def index
@notifications = current_user.notifications
.recent
.page(params[:page])
.per(20)
end
def mark_as_read
@notification = current_user.notifications.find(params[:id])
@notification.mark_as_read!
respond_to do |format|
format.html { redirect_to notifications_path }
format.turbo_stream
end
end
def mark_all_read
current_user.notifications.unread.update_all(read_at: Time.current)
redirect_to notifications_path, notice: "All notifications marked as read"
end
def destroy
@notification = current_user.notifications.find(params[:id])
@notification.destroy
redirect_to notifications_path
end
end
# Background job for notifications
class SendNotificationJob < ApplicationJob
queue_as :notifications
def perform(user_id, message, type, data = {})
user = User.find(user_id)
# Create in-app notification
Notification.create!(
recipient: user,
message: message,
metadata: data
)
# Send email if user has email notifications enabled
if user.email_notifications?
NotificationMailer.notification(user, message, type).deliver_later
end
# Send push notification if user has mobile app
if user.push_token.present?
PushNotificationService.send(user.push_token, message)
end
end
end
# Routes
resources :notifications, only: [:index, :destroy] do
collection do
patch :mark_all_read
end
member do
patch :mark_as_read
end
end
# Views
<%# app/views/layouts/application.html.erb %>
<%= turbo_frame_tag "notifications" do %>
<%= render "notifications/badge", count: current_user&.notifications&.unread&.count %>
<% end %>
<%# app/views/notifications/_notification.html.erb %>
<%= turbo_frame_tag dom_id(notification) do %>
<div class="notification <%= 'unread' unless notification.read? %>">
<div class="content">
<%= notification.message %>
<small><%= time_ago_in_words(notification.created_at) %> ago</small>
</div>
<% unless notification.read? %>
<%= button_to "โ", mark_as_read_notification_path(notification),
method: :patch, class: "mark-read" %>
<% end %>
<%= link_to "View", notification.link if notification.link %>
</div>
<% end %>
Q60: Implement Search Functionality
Answer:
# Using Ransack (simple)
class ProductsController < ApplicationController
def index
@q = Product.ransack(params[:q])
@products = @q.result.includes(:category).page(params[:page])
end
end
<%# View %>
<%= search_form_for @q do |f| %>
<%= f.label :name_cont, "Name contains" %>
<%= f.search_field :name_cont %>
<%= f.label :price_gteq, "Min price" %>
<%= f.number_field :price_gteq %>
<%= f.label :category_id_eq, "Category" %>
<%= f.collection_select :category_id_eq, Category.all, :id, :name, include_blank: true %>
<%= f.submit "Search" %>
<% end %>
# Using pg_search (PostgreSQL full-text)
# Gemfile
gem 'pg_search'
class Product < ApplicationRecord
include PgSearch::Model
pg_search_scope :search_full_text,
against: [:name, :description, :sku],
associated_against: {
category: [:name],
tags: [:name]
},
using: {
tsearch: { prefix: true, dictionary: 'english' }
}
scope :filter_by_price, ->(min, max) { where(price: min..max) if min && max }
scope :filter_by_category, ->(category_id) { where(category_id: category_id) if category_id.present? }
end
class ProductsController < ApplicationController
def index
@products = Product.all
if params[:query].present?
@products = @products.search_full_text(params[:query])
end
if params[:min_price].present? || params[:max_price].present?
min = params[:min_price].presence || 0
max = params[:max_price].presence || Float::INFINITY
@products = @products.filter_by_price(min, max)
end
@products = @products.page(params[:page])
end
end
# Using Elasticsearch (for complex search)
# Gemfile
gem 'elasticsearch-model'
gem 'elasticsearch-rails'
class Product < ApplicationRecord
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
settings index: { number_of_shards: 1 } do
mappings dynamic: false do
indexes :name, type: :text, analyzer: :english
indexes :description, type: :text, analyzer: :english
indexes :price, type: :float
indexes :category_id, type: :integer
indexes :created_at, type: :date
end
end
def as_indexed_json(options = {})
{
name: name,
description: description,
price: price,
category_id: category_id,
created_at: created_at
}
end
def self.search(query, filters = {})
response = __elasticsearch__.search(
query: {
bool: {
must: query.present? ? { match: { _all: query } } : { match_all: {} },
filter: filters
}
},
sort: [{ _score: :desc }, { created_at: :desc }],
highlight: {
fields: {
name: {},
description: {}
}
}
)
response.records
end
end
# Advanced search with facets
class ProductsController < ApplicationController
def search
search_params = {
query: params[:q],
filters: {
range: {
price: {
gte: params[:min_price],
lte: params[:max_price]
}.compact
}.presence,
term: {
category_id: params[:category_id]
}.presence
}.compact
}
@products = Product.search(search_params).page(params[:page])
@facets = {
categories: Category.joins(:products)
.group(:id, :name)
.count,
price_ranges: {
under_10: Product.where('price < 10').count,
under_50: Product.where('price BETWEEN 10 AND 50').count,
under_100: Product.where('price BETWEEN 50 AND 100').count,
over_100: Product.where('price > 100').count
}
}
end
end