MVC stands for Model-View-Controller. It is a design pattern used in Rails to separate concerns in an application:
- Model: Represents the data and business logic of the application. It interacts with the database to retrieve and store information.
- View: The presentation layer that displays the data to the user. It is typically HTML or JSON for web apps.
- Controller: Acts as an intermediary between the Model and View. It processes user input, calls the necessary model methods, and renders the appropriate view.
# Model
class Post < ApplicationRecord
validates :title, presence: true
end
# Controller
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
end
end
# View (show.html.erb)
<h1><%= @post.title %></h1>
<p><%= @post.body %></p>
ActiveRecord is the Object-Relational Mapping (ORM) layer in Rails that facilitates communication between the application and the database. It maps database tables to Ruby classes, allowing you to interact with the database using Ruby objects.
ActiveRecord automatically handles CRUD operations. It allows you to query, insert, update, and delete database records without needing to write raw SQL. Example:
# Finding a record
user = User.find(1)
# Creating a new record
user = User.create(name: "John", email: "john@example.com")
# Updating a record
user.update(name: "John Doe")
# Deleting a record
user.destroy
Callbacks in Rails are methods that get called at certain points of an object's lifecycle. They allow you to hook into and modify the behavior of ActiveRecord objects before or after certain events, such as saving, updating, or destroying records.
Common Callbacks: before_save, after_save before_create, after_create before_update, after_update before_destroy, after_destroy Example:
class Post < ApplicationRecord
before_save :set_default_title
private
def set_default_title
self.title = "Untitled" if title.blank?
end
end
Mass assignment occurs when you assign values to multiple attributes of an object at once, typically using the update or create methods. This is a feature in Rails that lets you assign values to several attributes in a single step.
Mass assignment vulnerability occurs when an attacker can update attributes that they shouldn't be allowed to, such as sensitive fields.
Example of mass assignment:
# Allowed attributes: name, email
user = User.new(name: "John", email: "john@example.com", admin: true) # admin should not be assignable!
Follow-up: How do you protect your app against it?
You can prevent mass assignment vulnerabilities by using strong parameters in Rails, which explicitly define which attributes are allowed to be mass-assigned.
class UsersController < ApplicationController
def user_params
params.require(:user).permit(:name, :email) # Only allow name and email
end
end
Cross-Site Request Forgery (CSRF) is an attack where a malicious website can send unauthorized requests to a web application where the user is already authenticated. It tricks the user into performing an unintended action.
Follow-up: How is it handled in Rails? Rails protects against CSRF by using a CSRF token. This token is generated on each form and ensures that the request came from your site and not from an external source.
Rails includes the token in every form by default, and it’s checked on form submissions.
Example:
<%= form_with(url: posts_path, method: :post) do %>
<%= text_field_tag :title %>
<%= submit_tag "Create Post" %>
<% end %>
Rails automatically includes a hidden CSRF token in forms generated by form_with.
Database migrations in Rails are used to manage changes to the database schema over time. They allow you to version-control your database structure, making it easy to roll back or apply changes.
Migrations are stored in files in the db/migrate directory. They can be run using rails db:migrate. Example:
# Migration to add a title column to the posts table
class AddTitleToPosts < ActiveRecord::Migration[6.0]
def change
add_column :posts, :title, :string
end
end
Run the migration:
rails db:migrate
A scope in Rails is a way to define commonly-used queries that can be reused throughout the application. It’s defined within the model and is typically used to simplify querying the database.
Example:
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc).limit(5) }
end
# Usage
Post.published.recent
has_one: This association indicates that one object can have one related object. It is typically used on the "one" side of a relationship. belongs_to: This association indicates that an object belongs to another object. It is used on the "many" side of the relationship. Example:
class Author < ApplicationRecord
has_one :profile # Author has one profile
end
class Profile < ApplicationRecord
belongs_to :author # Profile belongs to an author
end
In this case, an Author has one Profile, and a Profile belongs to an Author.
The Rails Console (rails console or rails c) is an interactive REPL (Read-Eval-Print Loop) that allows developers to run Ruby and Rails commands inside an application’s environment.
Usage:
rails console # Start console in default (development) mode
rails console --sandbox # Run console with changes rolled back after exit
rails console production # Start console in production environment
Example:
# Create a new user
user = User.create(name: "Alice", email: "alice@example.com")
# Find a user
user = User.find_by(email: "alice@example.com")
# Update a user
user.update(name: "Alice Doe")
# Delete a user
user.destroy
etc
These controller filters in Rails run specific code before or after an action.
before_action
Runs before the controller action.
class ArticlesController < ApplicationController
before_action :authenticate_user
def index
@articles = Article.all
end
private
def authenticate_user
redirect_to login_path unless current_user
end
end
after_action
Runs after the action is executed.
class ArticlesController < ApplicationController
after_action :log_activity
def show
@article = Article.find(params[:id])
end
private
def log_activity
Rails.logger.info("Article viewed at #{Time.now}")
end
end
skip_before_action
Skips the execution of a before_action for certain actions.
class ArticlesController < ApplicationController
before_action :authenticate_user
skip_before_action :authenticate_user, only: [:index, :show]
def index
@articles = Article.all
end
end
Strong parameters prevent mass assignment vulnerabilities by requiring attributes to be explicitly permitted.
Unsafe (Vulnerable to Mass Assignment Attack)
user = User.new(params[:user]) # An attacker can set `admin: true`
Safe Approach (Strong Parameters)
class UsersController < ApplicationController
def create
@user = User.new(user_params)
@user.save
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
Routes define URL patterns and corresponding controller actions.
Basic Routes (config/routes.rb):
Rails.application.routes.draw do
get "/articles", to: "articles#index"
post "/articles", to: "articles#create"
get "/articles/:id", to: "articles#show", as: "article"
end
Follow-up: What is the difference between resources and resource?
Method Routes Generated Example
resources :users Creates 7 RESTful routes (index, show, new, create, , update, destroy) GET /users/:id/
resource :profile Creates only one route per action (no index route) GET /profile/
Model:
rails generate model User name:string email:string
Creates:
app/models/user.rb
db/migrate/xxxx_create_users.rb
Controller:
rails generate controller Users index show
Creates:
app/controllers/users_controller.rb
app/views/users/index.html.erb
app/views/users/show.html.erb
Migration:
rails generate migration AddAgeToUsers age:integer
rails db:migrate
This file defines all application routes that map URLs to controllers and actions.
Example:
Rails.application.routes.draw do
root "home#index"
resources :articles
end
root "home#index" → Homepage
resources :articles → Generates RESTful routes
What is the difference between render, redirect_to, and respond_to in controllers?
Method Purpose Example
render Renders a template render "show"
redirect_to Redirects to another URL redirect_to articles_path
respond_to Responds with different formats (HTML, JSON) `respond_to {
Flash Stores temporary messages that last one request. Commonly used for notifications.
flash[:notice] = "Article saved!"
redirect_to articles_path
Session
Stores data across multiple requests.
Used for user authentication.
session[:user_id] = @user.id
Session stores data server-side (e.g., in cookies or Redis). Cookies store small amounts of client-side data.
Example (Using Sessions):
session[:user_id] = @user.id
Example (Using Cookies):
cookies[:user_id] = { value: @user.id, expires: 1.week.from_now }
Used to generate HTML tags dynamically.
<%= link_to "Home", root_path %>
Follow-up: How can you make link_to submit a form?
<%= link_to "Delete", article_path(@article), method: :delete, data: { confirm: "Are you sure?" } %>
Used to generate forms dynamically in Rails.
<%= form_with model: @article, local: true do |f| %>
<%= f.text_field :title %>
<%= f.submit "Save" %>
<% end %>
A partial is a reusable view component.
Usage:
<%= render "shared/header" %>
Follow-up: How do you pass local variables to a partial?
<%= render "article", article: @article %>
Partial _article.html.erb:
<h2><%= article.title %></h2>
link_to is a Rails helper method that generates HTML anchor () tags dynamically.
Basic Usage:
<%= link_to "Home", root_path %>
Generates:
<a href="/">Home</a>
Follow-up: How can you make link_to submit a form?
link_to can be used to send DELETE/POST/PUT requests by specifying the method and adding data: { confirm: "Message" }.
<%= link_to "Delete", article_path(@article), method: :delete, data: { confirm: "Are you sure?" } %>
Generates:
<a href="/articles/1" data-method="delete" data-confirm="Are you sure?">Delete</a>
Rails uses JavaScript (UJS) to convert this into a DELETE request.
form_with is a Rails helper used to generate forms dynamically.
Basic Example:
<%= form_with model: @article, local: true do |f| %>
<%= f.text_field :title %>
<%= f.submit "Save" %>
<% end %>
This generates:
<form action="/articles" method="post">
<input type="text" name="article[title]">
<input type="submit" value="Save">
</form>
local: true prevents AJAX submissions.
A partial is a reusable view component that helps avoid duplication.
Example: Create _article.html.erb:
<div class="article">
<h2><%= article.title %></h2>
<p><%= article.content %></p>
</div>
Use it in another view:
<%= render "article", article: @article %>
Follow-up: How do you pass local variables to a partial?
<%= render "article", article: @article %>
Inside _article.html.erb:
<h2><%= article.title %></h2>
Layouts define a common structure for multiple views.
Example app/views/layouts/application.html.erb:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<%= yield %>
</body>
</html>
Each view injects its content into <%= yield %>.
yield acts as a placeholder for page-specific content inside a layout.
Example app/views/layouts/application.html.erb:
<html>
<head><title>My App</title></head>
<body>
<%= yield %> <!-- Page-specific content goes here -->
</body>
</html>
Usage in app/views/articles/index.html.erb:
<h1>All Articles</h1>
Final Rendered Output:
<html>
<head><title>My App</title></head>
<body>
<h1>All Articles</h1>
</body>
</html>
Helpers are Ruby methods used in views to keep logic out of templates.
Example Helper (app/helpers/articles_helper.rb):
module ArticlesHelper
def formatted_date(date)
date.strftime("%B %d, %Y")
end
end
Usage in a view:
<p>Published on <%= formatted_date(@article.created_at) %></p>
Follow-up: When should you use a helper vs. a partial?
Use Case Helper Partial Formatting data ✅ Yes ❌ No Repeating view elements ❌ No ✅ Yes
params stores request data (e.g., form inputs, URL parameters).
Example Controller:
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id]) # Fetch from URL
end
def create
@article = Article.new(params.require(:article).permit(:title, :content)) # Form Data
end
end
HTTP Method Purpose Example Usage GET Read data GET /articles/1 POST Create data POST /articles PUT Update entire record PUT /articles/1 DELETE Delete a record DELETE /articles/1
Lists all available routes in a Rails app.
rails routes
Example Output:
articles GET /articles(.:format) articles#index
It sets up a many-to-many relationship via a join table.
class User < ApplicationRecord
has_many :user_projects
has_many :projects, through: :user_projects
end
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
end
class Project < ApplicationRecord
has_many :user_projects
has_many :users, through: :user_projects
end
validates ensures data integrity before saving records.
class User < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
validates :email, uniqueness: true
end
Follow-up: What are some common validation options?
Validation Example
presence: true Ensures value is present
uniqueness: true Ensures unique values
length: { minimum: x } Min character length
format: { with: /regex/ } Custom regex validation
rescue_from handles exceptions globally in controllers.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def not_found
render plain: "404 Not Found", status: :not_found
end
end
Rake (Ruby Make) automates tasks like migrations and seeding.
rake db:migrate
rake db:seed
Follow-up: What is the difference between rake and rails commands?
Command Purpose
rake Runs tasks (e.g., migrations)
rails Runs commands (e.g., server, console)
❌ Problem:
users = User.all
users.each do |user|
puts user.posts.count
end
This results in one query per user, leading to an N+1 query issue.
✅ Solution: Use includes for Eager Loading
users = User.includes(:posts) # Fetch users & posts in 1 query
users.each do |user|
puts user.posts.count # Does not make extra queries
end
👉 includes loads the associated records efficiently.
🔹 Fragment Caching
<% cache @article do %>
<h1><%= @article.title %></h1>
<p><%= @article.content %></p>
<% end %>
👉 Caches the HTML output of the article block. 🔹 Low-Level Caching
Rails.cache.fetch("recent_articles", expires_in: 10.minutes) do
Article.order(created_at: :desc).limit(5)
end
👉 Caches query results for 10 minutes.
🚀 𝗟𝗲𝘃𝗲𝗹 𝗨𝗽 𝗬𝗼𝘂𝗿 𝗥𝗮𝗶𝗹𝘀 𝗞𝗻𝗼𝘄𝗹𝗲𝗱𝗴𝗲 𝘄𝗶𝘁𝗵 𝗧𝗵𝗲𝘀𝗲 𝗜𝗻𝘁𝗲𝗿𝘃𝗶𝗲𝘄 𝗤𝘂𝗲𝘀𝘁𝗶𝗼𝗻𝘀 🚀
Feature Proc Lambda Arity Check Allows missing arguments (defaults to nil). Strictly enforces the number of arguments. Return Behavior Exits the method immediately. Returns control to the method. Use Case Flexible argument handling, quick inline logic. Function-like behavior with strict argument handling.
Using Proc in Dynamic Method Execution
def perform_action(action)
actions = {
greet: Proc.new { puts "Hello!" },
farewell: Proc.new { puts "Goodbye!" }
}
actions[action].call if actions[action]
end
perform_action(:greet) # Output: "Hello!"
perform_action(:farewell) # Output: "Goodbye!"
Using Lambda for Strict Validations
validate_input = ->(name, age) {
raise "Invalid name" if name.nil? || name.empty?
raise "Invalid age" if age < 18
"Welcome, #{name}!"
}
puts validate_input.call("Alice", 20) # ✅ Output: "Welcome, Alice!"
puts validate_input.call("", 25) # ❌ Raises error "Invalid name"
Feature Proc (Proc.new) Lambda (-> {})
Arity (Arguments Handling) Doesn't enforce argument count (missing args = nil). Strict argument checking (errors if missing).
Return Behavior Exits the calling method immediately. Returns control to the calling method.
Use Case When flexibility is needed. When strict function-like behavior is required.
💡 TL;DR Use Procs when you want loose argument handling and early exit behavior.
Use Lambdas when you want strict function-like execution.
Sharding is a database partitioning technique that distributes data across multiple databases or servers to improve performance, scalability, and availability. Instead of storing all records in a single database, data is split (sharded) into multiple databases based on a specific key (e.g., user ID, region, tenant).
Scalability – Distributes load across multiple database instances.
Performance – Reduces query time by limiting the data each query scans.
Fault Tolerance – Failure in one shard doesn’t affect others.
Multi-tenancy Support – Different customers (tenants) can be stored in separate databases.
Rails 6+ provides built-in support for database sharding using the multiple databases feature (config/database.yml).
Example: User Data Sharded Across Multiple Databases
# config/database.yml
production:
primary:
adapter: postgresql
database: main_db
host: db1.example.com
shard_1:
adapter: postgresql
database: shard1_db
host: db2.example.com
shard_2:
adapter: postgresql
database: shard2_db
host: db3.example.com
Each database (shard_1, shard_2) contains a subset of the total records.
Step 1: Define Database Connections Modify database.yml to add shard configurations.
Step 2: Switch Between Shards in Models Use connects_to in the model:
class User < ApplicationRecord
connects_to database: { writing: :shard_1, reading: :shard_1 }
end
Step 3: Manually Switch Shards in Queries
ActiveRecord::Base.connected_to(role: :writing, shard: :shard_1) do
User.create(name: "Alice")
end
Step 4: Auto-Route Requests Based on Shard Key Example: Shard based on user region.
def set_shard
user_region = params[:region] # Example: "asia"
shard = "shard_#{user_region}"
ActiveRecord::Base.connected_to(role: :writing, shard: shard) do
yield
end
end
This method automatically connects to the appropriate shard.
Use Case Should You Use Sharding? Millions of rows per table ✅ Yes Multi-tenancy (separate databases per customer) ✅ Yes Geo-distributed applications ✅ Yes Simple CRUD apps with low traffic ❌ No Short-term performance issues (use indexing instead) ❌ No
Alternatives to Sharding Read Replicas – Separate read and write databases (primary, replica).
Partitioning – Store different data segments in the same database (PostgreSQL table partitioning).
TL;DR Sharding splits data across multiple databases to improve performance and scalability.
Rails 6+ supports multi-database configurations (connects_to, connected_to).
Use sharding when dealing with high-volume, multi-tenant, or geographically distributed applications.
What are different ways to manage configurations in Rails (secrets.yml, credentials.yml.enc, ENV variables)?
What is the difference between dependent: :destroy, dependent: :delete_all, and dependent: :nullify?
What’s the difference between synchronous and asynchronous processing in Rails?⚡ Performance & Scalability
def weird
begin
raise "Oops"
rescue
return "rescued"
ensure
return "ensured"
end
end
puts weird # => ???
Answer: "ensured" — ensure always runs last and overrides the return.
How would you implement your own version of each, map, or select without using Ruby's built-in methods?
What are some metaprogramming techniques in Ruby? Show an example using define_method or method_missing.
1. How do you handle exceptions and errors in Rails? What strategies do you use for logging and notification?
2. How do you approach database modeling in Rails? What considerations do you take for efficient data retrieval and storage?
3. How do you use Rails migrations to manage database schema changes? What are some best practices for writing migrations?
4. What caching mechanisms does Rails offer (fragment, action, and page caching), and when would you use each?
5. How do you design RESTful APIs in Rails? What considerations do you take for API versioning and security?
6. How do you use service objects to encapsulate business logic in Rails? What benefits do they offer?
7. How do you define nested routes in Rails? What are some advanced routing techniques such as shallow routes?
8. Explain polymorphic associations in Rails. When would you choose them over standard associations?
9. How do you tackle the N+1 query problem in Rails? What tools or techniques do you use to optimize queries?
10. How do you handle database transactions in Rails? What steps do you take to optimize database performance through indexing and query tuning?
11. How would you manage caching in a high-traffic, distributed Rails environment? Discuss the use of fragment, action, and page caching.
12. Discuss Rails’ built-in security features. How do you mitigate risks like CSRF, XSS, and SQL injection?
13. How do you design, secure, and version API endpoints in Rails? What are your thoughts on REST vs. GraphQL?
14. What architectural patterns have you implemented to ensure a Rails application scales? Discuss multi-tenancy or micro-services if applicable.
15. How do you address concurrency in Rails? Have you encountered and solved challenges with multi-threading?
16. What is sharding, and how can it be implemented in Rails to optimise data access for large datasets?
17. What testing frameworks and strategies (unit, integration, performance) do you rely on in Rails? How do you ensure comprehensive test coverage?
18. Describe your experience implementing real-time features using Action Cable. What challenges did you encounter?
19. How do you approach refactoring a large, legacy Rails codebase without causing disruptions? What tools or methodologies do you use?
20. Have you worked with Rails 6/7 features such as ActionMailbox, ActionText, or Hotwire? How have they influenced your development process?
21. How do you use service objects or decorators to encapsulate business logic? Share your experiences with Ruby’s meta-programming capabilities.
23. How do you analyze and optimize database queries in Rails? What tools do you use to identify performance bottlenecks?
What are different ways to manage configurations in Rails (secrets.yml, credentials.yml.enc, ENV variables)?
What is the difference between dependent: :destroy, dependent: :delete_all, and dependent: :nullify?
Challenge: Write a class CurrencyConverter that dynamically defines methods like to_usd, to_eur, to_gbp, and converts from a base amount in cents (as integer) to those currencies using given exchange rates.
Challenge: Write a method lazy_primes(n) that lazily generates the first n prime numbers using Ruby's enumerators.
Challenge: Create a counter that increments safely from multiple threads (simulate 100 threads incrementing a shared counter 1000 times each). Use a mutex to avoid race conditions.
Use rescue_from in controllers to handle specific exceptions:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from StandardError, with: :handle_error
private
def not_found
render json: { error: "Record not found" }, status: :not_found
end
def handle_error(exception)
Rails.logger.error(exception.message)
render json: { error: "Something went wrong" }, status: :internal_server_error
end
end
Use Rollbar, Sentry, or Bugsnag for error notifications.
Normalize data when needed (e.g., extract a tags table if storing repeated text). Use indexes for faster lookups (add_index :users, :email, unique: true). Optimize eager loading to avoid N+1 queries.
Keep migrations idempotent (avoid destructive changes in production).
Use change method over up and down unless a complex rollback is needed. Example:
class AddIndexToUsers < ActiveRecord::Migration[7.0]
def change
add_index :users, :email, unique: true
end
end
Page Caching: Stores full HTML response (caches_page :index).
Action Caching: Caches entire actions but checks authentication.
Fragment Caching: Caches parts of a view:
<% cache @user do %>
<%= render @user.profile %>
<% end %>
SQL Query Caching: Enabled by default in Rails.
Versioning: /api/v1/users Security Considerations: Use JWT or OAuth2 for authentication.
Add rate limiting with rack-attack.
Service objects keep controllers clean:
class UserCreator
def initialize(user_params)
@user_params = user_params
end
def call
User.create(@user_params)
end
end
Benefits: Reusable, testable, and scalable.
resources :authors do
resources :books, only: [:index, :show]
end
Use shallow routes to avoid deep nesting:
resources :books, shallow: true do
resources :reviews
end
- Polymorphic Associations Use when a model can belong to multiple models:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
Example:
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Video < ApplicationRecord
has_many :comments, as: :commentable
end
Use includes or preload:
Post.includes(:comments).each do |post|
puts post.comments.count
end
Use bullet gem to detect N+1.
- Database Transactions & Performance Wrap operations in transactions:
User.transaction do
user.update!(balance: user.balance - 100)
Payment.create!(user: user, amount: 100)
end
Indexing:
add_index :orders, :user_id
Use Redis as a caching layer.
Use low-level caching with Rails.cache.fetch.
CSRF Protection: protect_from_forgery with: :exception
XSS Protection: Use sanitize for user input.
SQL Injection Prevention: Always use ActiveRecord’s query methods:
User.where("email = ?", params[:email]) 13. REST vs. GraphQL REST: Good for simple CRUD APIs.
GraphQL: Good for complex frontends needing flexible data fetching.
Use read replicas and database sharding.
Move long tasks to background jobs.
Use Redis locks to prevent race conditions.
Use octopus gem or Rails 6 multiple databases feature.
Unit tests: RSpec or Minitest
Integration tests: Capybara
Performance testing: Benchmark
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
Challenges: Scaling with Redis, handling connections.
Use rubocop for code consistency.
Gradually introduce service objects.
Hotwire (Turbo & Stimulus)
ActionText (Rich Text ing)
ActionMailbox (Incoming Emails Handling)
Rack is a lightweight interface between web servers and Ruby frameworks.
Custom Middleware:
class LoggerMiddleware
def initialize(app)
@app = app
end
def call(env)
Rails.logger.info "Request received: #{env['PATH_INFO']}"
@app.call(env)
end
end
Solved using includes, preload, or eager_load.
Use concerns for shared logic:
module Taggable
extend ActiveSupport::Concern
included do
has_many :tags
end
end
HABTM: Direct many-to-many without extra fields.
HMT: Uses a join model for additional attributes.
class User
@@class_var = "class var"
@instance_var = "instance var"
end
Strings are mutable, Numbers & Booleans are immutable.
Ruby uses Mark-and-Sweep Garbage Collection.
include adds instance methods.
extend adds class methods.
Threads: Pre-emptive (run in parallel).
Fibers: Cooperative (manual yielding).
Lambdas check arity (number of arguments), Procs don’t.
params.require(:user).permit(:name, :email) CSRF Protection:
protect_from_forgery with: :exception ActiveRecord Callbacks: before_save, after_commit, etc.
Convention Over Configuration: Rails’ opinionated design.
Difference Between save and save!: save! raises an exception on failure.
Want Mock Interview Practice? 🔥 I can generate Rails coding challenges or mock system design questions! 🚀
📌 Associations in Rails and How They Are Formed Associations in Rails define relationships between models, allowing efficient data retrieval and manipulation.
- belongs_to Association
One-to-One or Many-to-One relationship
The child model holds the foreign key.
Example: A Book belongs to an Author
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
end
The books table must have an author_id column.
Foreign key: author_id in books.
class AddAuthorToBooks < ActiveRecord::Migration[7.0]
def change
add_reference :books, :author, foreign_key: true
end
end
One-to-One relationship
The parent model has one associated record.
Example: A User has one Profile
class User < ApplicationRecord
has_one :profile
end
class Profile < ApplicationRecord
belongs_to :user
end
Foreign key: user_id in profiles.
class AddUserToProfiles < ActiveRecord::Migration[7.0]
def change
add_reference :profiles, :user, foreign_key: true
end
end
One-to-Many relationship
A record has many related records.
Example: An Author has many Books
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :author
end
Foreign key: author_id in books.
Many-to-Many relationship with a join table containing additional attributes.
Example: Doctor has many Patients through Appointments
class Doctor < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Patient < ApplicationRecord
has_many :appointments
has_many :doctors, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :doctor
belongs_to :patient
end
Join table: appointments (with doctor_id and patient_id).
class CreateAppointments < ActiveRecord::Migration[7.0]
def change
create_table :appointments do |t|
t.references :doctor, foreign_key: true
t.references :patient, foreign_key: true
t.datetime :appointment_date
t.timestamps
end
end
end
Direct Many-to-Many relationship without additional attributes.
Example: Students and Courses
class Student < ApplicationRecord
has_and_belongs_to_many :courses
end
class Course < ApplicationRecord
has_and_belongs_to_many :students
end
Requires a join table (courses_students) with no extra columns.
class CreateCoursesStudentsJoinTable < ActiveRecord::Migration[7.0]
def change
create_join_table :courses, :students do |t|
t.index :course_id
t.index :student_id
end
end
end
📝 HABTM vs. has_many :through
Use has_many :through if the join table needs extra columns (e.g., appointments.date).
Use HABTM if the join table is purely relational.
A model can belong to multiple other models using a single association.
Example: A Comment can belong to both Post and Video
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Video < ApplicationRecord
has_many :comments, as: :commentable
end
The comments table has:
commentable_type (stores "Post" or "Video")
commentable_id (stores the related record’s ID)
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.text :body
t.references :commentable, polymorphic: true, index: true
t.timestamps
end
end
end
A model associates with itself.
Example: Employees reporting to a Manager
class Employee < ApplicationRecord
belongs_to :manager, class_name: "Employee", optional: true
has_many :subordinates, class_name: "Employee", foreign_key: "manager_id"
end
Foreign key: manager_id in employees.
class AddManagerToEmployees < ActiveRecord::Migration[7.0]
def change
add_reference :employees, :manager, foreign_key: { to_table: :employees }
end
end
Association Type Description Example
belongs_to Child model holds foreign key Book belongs_to Author
has_one One-to-one User has_one Profile
has_many One-to-many Author has_many Books
has_many :through Many-to-many with join model Doctor has_many Patients through Appointments
has_and_belongs_to_many (HABTM) Many-to-many without extra fields Students and Courses
polymorphic Model belongs to multiple types Comment belongs_to Post or Video
self-join Model relates to itself Employee belongs_to Manager