Ruby on Rails

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

  1. Rails Basics
  2. MVC Architecture
  3. ActiveRecord & Database
  4. Routes & Controllers
  5. Views & Helpers
  6. Authentication & Authorization
  7. Testing
  8. Performance & Optimization
  9. Security
  10. Deployment
  11. Rails 7+ Features
  12. Common Interview Questions
  13. Advanced ActiveRecord
  14. Background Jobs
  15. API Development
  16. Real-time Features
  17. File Uploads
  18. Internationalization
  19. Caching Strategies
  20. Database Optimization
  21. Common Gems
  22. Troubleshooting & Debugging
  23. Rails Anti-patterns
  24. Code Organization
  25. 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:

  1. Browser sends HTTP request
  2. Rails router matches URL to controller action
  3. Controller action executes
  4. Controller interacts with Model (if needed)
  5. Controller renders View
  6. 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:

  1. Turbo - Makes page updates fast

    • Turbo Drive: Fast page loads
    • Turbo Frames: Decompose pages into independent pieces
    • Turbo Streams: Deliver page updates over WebSocket
  2. 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