Ruby on Rails is a high-level web application framework written in the Ruby programming language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It follows the Model-View-Controller (MVC) pattern and emphasizes two key philosophies:
- (Don't Repeat Yourself): Encouraging the reduction of information repetition.
- over Configuration (CoC): Rails has a set of conventions that allow you to write less code by following established "best practices" rather than configuring every detail.
The key features of Rails include:
- Active Record (ORM): Maps Ruby objects to database tables, allowing you to manipulate data without writing SQL.
- Action View: A system for managing the "View" layer, using Embedded Ruby (ERB) to generate HTML.
- Action Dispatch: Handles routing by directing incoming web requests to the correct controller.
- Active Job: A framework for declaring jobs and making them run on a variety of queuing backends.
- Scaffolding: A tool that automatically generates the major pieces of an application (Models, Views, and Controllers) for a specific resource.
- Asset Pipeline: Manages the processing and compression of JavaScript, CSS, and images.
- Development Speed: Thanks to scaffolding and CoC, Rails is famous for moving from "idea" to "MVP" faster than almost any other framework.
- Strong Community: A massive ecosystem of "Gems" (libraries) means you can plug in functionality (like authentication) in minutes.
- Standards-Based: Because Rails is opinionated, it’s easy for a new developer to jump into a Rails project and understand where everything is.
- Built-in Testing: Rails creates skeleton test code while you’re writing your application, making it easier to ensure your app works as expected.
To install Rails, you generally follow these steps:
- Install Ruby: Ensure Ruby is installed on your system
(usually managed via tools like
rbenvorrvm). - Install SQLite3: (Or your preferred database) as Rails requires a database to function.
- Install Rails using RubyGems: Open your terminal and
run:
gem install rails - Verify the installation: Check the version by running:
rails -v
As of early 2026, the latest stable version of Rails is 8.0.x.strong (Note: Rails 8 introduced significant improvements in "No-PaaS" deployment tools like Kamal and removed the mandatory need for a JavaScript build tool for simpler apps).
A Rails project is a directory structure containing all the files needed for a
web application. When you create a project, Rails sets up a standardized folder
hierarchy (e.g., app/, config/, db/).
- To create a project: Use the
rails newcommand followed by the project name.rails new my_awesome_app - This command generates the entire application skeleton, including the Gemfile (for dependencies) and the database configuration.
In the MVC pattern, the Controller is the middleman. It receives a request from the router, gathers data from the Model, and passes it to the View.
- To create a controller: Use the
command:
bin/rails generate controller Posts - Example: A
PostsControllermight have anindexmethod that fetches all blog posts from the database to show them to the user.
Active Record is the layer responsible for representing business data and logic. It allows you to interact with your database using Ruby syntax.
- Example Usage: To find a user with the email "dev@example.com":
- Active Record automatically translates that Ruby code into the appropriate
SQL
SELECTstatement.
ERB (Embedded Ruby) is the default templating engine for Rails. It looks like standard HTML but allows you to execute Ruby code inside special tags.
- Template Example (
index.html.erb): <% %>executes code (like a loop), while<%= %>outputs the result into the HTML.
A Gem is a packaged Ruby library or application. Rails itself is a Gem. Developers use Gems to add specific functionality to their apps without writing it from scratch (e.g., Devise for authentication or Pundit for authorization). All Gems for a project are listed in a file called the Gemfile.
Convention over Configuration (CoC) is a software design paradigm used by frameworks like Ruby on Rails to decrease the number of decisions a developer must make without losing flexibility.
The core idea is that the framework assumes "sensible defaults" for how things should work. If you follow these established conventions, you don't need to write configuration files (like XML or YAML) to specify how your application parts connect. You only write configuration when you need to deviate from the standard.
- Database Naming: If you have a class (Model) named
Post, Rails automatically assumes the database table is namedposts. No manual mapping is required. - Primary Keys: Rails assumes every table has a primary key
column named
id. - Foreign Keys: Rails assumes foreign keys follow the pattern
singular_model_name_id(e.g.,user_idto link to auserstable). - File Placement: Controllers must be in
app/controllersand Models inapp/models. By placing them there, Rails automatically loads and associates them. - View Mapping: A method called
indexin thePostsControllerwill automatically look for a template file located atapp/views/posts/index.html.without any explicit code telling it to do so. - Reduced Boilerplate: Eliminates hundreds of lines of configuration code.
- Project Consistency: Since most Rails apps follow the same conventions, any Rails developer can navigate a new project instantly.
- Faster Prototyping: Developers focus on unique business logic rather than the plumbing of the framework.
How CoC Functions in Rails
Core Benefits
DRY (Don't Repeat Yourself) is a software development principle aimed at reducing the repetition of information of all kinds. In Rails, this means that every piece of knowledge or logic must have a single, unambiguous, authoritative representation within the system.
- Active Record: You define database columns in the database schema, and Rails automatically makes them available as attributes in your Ruby models. You do not need to redefine those fields in the class file.
- (Don't Repeat Yourself): Encouraging the reduction of information repetition.
- over Configuration (CoC): Rails has a set of conventions that allow you to write less code by following established "best practices" rather than configuring every detail.
How DRY is Implemented in Rails
Partials: For the View layer, Rails uses
Partials (_filename.html.erb) to share
HTML snippets (like a navigation bar or a search form) across multiple
pages, preventing the need to copy-paste code.
Concerns: When multiple models or controllers share the same logic, Rails uses Concerns (modules) to store that code in one place and include it where needed.
Filters (Callbacks): Instead of repeating authentication
or data-loading logic in every controller action, you
usebefore_action to run a single method before specific
tasks.
Scopes: Common database queries are defined once in the
model as a scope, allowing them to be reused throughout the
application.
| Feature | DRY Approach (Best Practice) | WET Approach ("Write Everything Twice") |
|---|---|---|
| Maintenance | Update logic in one file; changes propagate everywhere. | Must find and update every instance of the code manually. |
| Readability | High; code is modular and concise. | Low; cluttered with redundant logic. |
| Bugs | Lower risk of inconsistency. | High risk of missing one instance during an update. |
In Rails, Resourceful Routing allows you to declare all common
routes for a given controller in a single line of code. Instead of defining
individual paths for every action, you use the resources keyword to
map a conventional set of HTTP verbs to controller actions.
To define routes for a "Post" resource, you add the following to
config/routes.rb:
The Standard 7 Routes Created
By declaring resources :posts, Rails automatically generates these seven routes, which map to the CRUD (Create, Read, Update, Delete) operations:
- Limiting Routes: Use
onlyorexceptto restrict which routes are generated. resources :posts, only: [:index, :show]- Singular Resources: Use
resource(singular) for resources that can be looked up without an ID (e.g., a user's/profile). - Nested Resources: Capture relationships by nesting routes.
resources :posts do resources :comments; end(Generates routes like/posts/:post_id/comments).
| HTTP Verb | Path | Controller#Action | Purpose |
|---|---|---|---|
| GET | /posts | posts#index | Display a list of all posts |
| GET | /posts/new | posts#new | Return an HTML form for creating a new post |
| POST | /posts | posts#create | Create a new post in the database |
| GET | /posts/:id | posts#show | Display a specific post |
| GET | /posts/:id/edit | posts#edit | Return an HTML form for editing a post |
| PATCH / PUT | /posts/:id | posts#update | Update a specific post |
| DELETE | /posts/:id | posts#destroy | Delete a specific post |
-
Key Customizations
In Rails routing, both methods define how a URL is mapped to a controller and an action, but they serve different functional purposes within the application structure.
root MethodThe root method defines the homepage of your
application. It maps the empty path (/) to a specific controller and action.
- Syntax:
root 'pages#home' - Purpose: Sets the entry point of the website.
- Helper Generated: It automatically creates
the
root_pathandroot_urlhelpers. - Requirement: It should only be used once per application (or once per specific namespace).
The get Method
- Syntax:
get '/about', to: 'pages#about' - Purpose: Maps a custom URL path (e.g.,
/about) to a specific controller action. - Helper Generated: By default, it creates a helper based on
the path (e.g.,
about_path), but you can customize it usingas:.
| Feature | root 'controller#action' | get 'path', to: 'controller#action' |
|---|---|---|
| URL Path | Always / (the base domain) |
A specific named path (e.g., /contact) |
| Common Use Case | Landing pages or dashboards | Content pages, search pages, or index views |
| Named Helper | root_path |
path_name_path (e.g., about_path)
|
| Verb Constraint | Hardcoded to GET |
Can be swapped for POST, PATCH,
DELETE, etc.
|
Example in config/routes.rb:
Nested Resources are a routing technique in Rails used to reflect a
logical hierarchy between models in your URL structure. By nesting one resource
inside another, you make the relationship explicit in the request path.
If a Post has many Comments, you define the
relationship in config/routes.rb:
Resulting URL Structure
Nesting generates URLs that include the ID of the parent resource, allowing the controller to know which parent the child belongs to.
| Action | URL Pattern | Named Helper |
|---|---|---|
| index | /posts/:post_id/comments |
post_comments_path(@post) |
| new | /posts/:post_id/comments/new |
new_post_comment_path(@post) |
| show | /posts/:post_id/comments/:id |
post_comment_path(@post, @comment) |
When to Use Nested Resources
Use nested resources when a child object cannot logically exist or be identified without its parent.
- Logical Hierarchy: When a model is "owned" by another
(e.g.,
Chaptersin aBook, LineItemsanOrder). - Contextual Data: When you need the parent's ID to scope the
database query (e.g.,
Post.find(params[:post_id]).comments). - SEO & UX: When you want the URL to clearly describe the site structure to the user.
The "Rule of Thumb"
According to Rails best practices (popularized by Jamis Buck), you should never nest more than one level deep.
- Good:
/posts/1/comments - Bad:
/publishers/1/magazines/5/articles/10/comments(This becomes difficult to manage and creates overly complex helpers).
For actions like show, edit, update, anddestroy, it is
often better to use Shallow Nesting
(resources :posts do resources :comments, shallow: true end)
because the child’s unique ID is enough to identify it without the parent ID in
the URL.
Both Namespaces and Scopes allow you to group related routes, but they differ in how they affect the URL, the controller file structure, and the named helpers.
A Namespace is used to group a set of controllers under a specific prefix. It assumes that the controllers will be located in a sub-folder and that the URL and helpers will all share that prefix.
- URL Prefix: Added (e.g.,
/admin/posts). - Controller Subdirectory: Required (e.g.,
app/controllers/admin/posts_controller.rb). - Helper Prefix: Added (e.g.,
admin_posts_path). - Common Use Case: Admin dashboards or API versioning (e.g.,
/api/v1/).
The Scope method is more flexible. It allows you to change specific parts of the routing (the URL, the module, or the helper prefix) without forcing all three to change simultaneously.
- URL Prefix: Optional.
- Controller Subdirectory: Optional.
- Helper Prefix: Optional.
- Common Use Case: Localizing URLs (e.g., prefixing with
/en/or/es/) while keeping controllers in the main directory.
| Feature | namespace :admin | scope :admin (Default) |
|---|---|---|
| URL Path | /admin/posts | /admin/posts |
| Controller Class | Admin::PostsController | PostsController |
| File Location | app/controllers/admin/ | app/controllers/ |
| Named Helper | admin_posts_path | posts_path |
If you want to mimic a namespace but only change specific attributes, you can
pass options to scope
scope module: 'admin'(Changes the controller location but not the URL).scope path: 'admin'(Changes the URL but not the controller location).scope as: 'admin'(Changes the helper name prefix).
In Rails, the params hash is a dictionary-like object that collects
all parameters sent with an HTTP request. It acts as the primary bridge between
the user's input (from the browser) and the application's logic (in the
controller).
Sources of Data in params
The params hash aggregates data from three main sources:
- Query String: Data sent in the URL after the
?(e.g.,/search?query=ruby). - Route Parameters: Dynamic segments defined in
config/routes.rb(e.g., in/posts/:id, the:idis captured). - POST/PATCH Data: Information sent via HTML forms or JSON API payloads.
Usage Example
If a user visits /posts/5?display=full, the params hash
will look like this:
You can access this data in your controller action:
| Method | Description |
|---|---|
| params[:key] | Retrieves the value associated with the key (returns a string or nested hash). |
| params.require(:key) | Ensures a specific parameter is present; throws an error if missing. |
| params.permit(:col1, :col2) | Strong Parameters: Explicitly allows only specific fields to be used for mass-assignment, preventing unauthorized database updates. |
Important Note on Data Types
All values in theparams hash are initially treated as
strings (or nested hashes/arrays). If you need to perform math
or boolean logic, you must convert them manually (e.g.,
params[:count].to_i).
How Migrations Manage Schema
- Version Control for Databases: Migrations are timestamped
files stored in
db/migrate/. This allows a team of developers to share the same database structure by running the same files. - Database Agnostic: You write migrations in Ruby, and Active Record generates the specific SQL needed for your database engine (PostgreSQL, MySQL, SQLite, etc.).
- Reversibility: Most migrations are reversible. If you make a mistake, you can "roll back" the database to its previous state using a single command.
- The
schema.rbFile: After a migration runs, Rails updatesdb/schema.rb.This file is the authoritative source for the current state of your database and is used to initialize new environments.
Common Migration Commands
| Command | Action |
|---|---|
bin/rails generate migration Name |
Creates a new migration file. |
bin/rails db:migrate |
Runs all pending migrations. |
bin/rails db:rollback |
Reverts the last migration performed. |
bin/rails db:status |
Shows which migrations have been applied. |
Code Example
A migration to create a products table:
While both commands interact with your migration files in
db/migrate/, they move your database schema in opposite directions
along your application's timeline.
Key Differences
| Feature | bin/rails db:migrate |
bin/rails db:rollback |
|---|---|---|
| Direction | Forward (Up) | Backward (Down) |
| Action | Runs all migration files that have not yet been applied to the database. | Reverts the very last migration (or set of migrations) that was applied. |
| Common Use | Updating your local or production database after pulling new code or creating a migration. | Undoing a mistake in the most recent migration to fix a column name or type. |
| Effect on Schema | Adds tables, columns, or indexes. | Removes tables, columns, or indexes. |
The Rollback "STEP" Option
By default, rollback only undoes the single most recent migration
file. If you need to go back multiple versions, you can use theSTEP
parameter:
bin/rails db:rollback STEP=3(Undoes the last three migrations).
The "Change" Method vs. "Up/Down"
- Modern Rails (
def change): Rails is smart enough to know that the opposite ofadd_columnis remove_column. When you rundb:rollback, it simply performs the inverse of whatever is inside thechangemethod. - Complex Migrations (
def up/def down): If a migration involves complex data transformation that cannot be automatically reversed, developers define anupmethod fordb:migrateand adownmethod fordb:rollback.
When to Use Which?
- Use
db:migratewhenever you have created a new migration file using a generator or have pulled down new migrations from a git repository. - Use
db:rollbackif you realize you made a syntax error or forgot a field in the migration you just ran. Once you rollback, you can edit the file and rundb:migrateagain.
In Rails, Associations are declarations that tell Active Record there is a relationship between two models. They simplify data manipulation by allowing you to navigate between related objects without writing complex join queries.
The Three Primary Associations
- 1.One-to-Many Relationship
post.userreturns the user object associated with that post.- 2.One-to-One Relationship
- user.profile returns the unique profile record.
-
Method Generation: Rails automatically creates methods to find, create, and build related records (e.g.,user.posts.build). - Data Integrity: You can add options like
dependent: :destroy. If aUseris deleted, all theirpostsare automatically deleted to prevent orphaned records in the database. - Readability: Code becomes more expressive:
Comment.first.post.author.nameinstead of multiple SQL queries.
| Association | Direction | Database Requirement | Logic |
|---|---|---|---|
belongs_to |
Child to Parent |
The table must contain the foreign key
(e.g., user_id).
|
Each instance of this model belongs to exactly one instance of another. |
has_many |
Parent to Children | The other table must contain the foreign key. | A single instance of this model can be associated with zero or more instances of another. |
has_one |
Parent to Child | The other table must contain the foreign key. | A single instance of this model is associated with exactly one instance of another. |
Implementation Examples
This is the most common association. One user can have many posts.
Functionality Gained: * user.posts returns a
list of all posts by that user.
One user has exactly one profile.
Functionality Gained:
Key Benefits of Associations
We have completed the 20 questions! Would you like me to summarize the key differences between Django and Rails, or perhaps explore a specific advanced Rails topic like Active Job or Action Cable?
A Polymorphic Association allows a model to belong to more than
one other model on a single association. This is useful when several models have
a common relationship with another model (e.g., both Post and
Product can have many Comments).
How it Works
Instead of creating a post_id and a product_id column
on the comments table, you create two special columns:
- [Type]_id: An integer to store the ID of the parent record.
- [Type]_type: A string to store the class name of the parent record (e.g., "Post" or "Product").
Implementation Example
1. The Child Model (Comment) You define the association as
polymorphic. Conventionally, the name ends in -able.
2. The Parent Models (Post & Product) Both parents reference the name used in the child model.
Database Migration
The migration for the child table uses the references helper with
the polymorphic: true option.
Advantages vs. Regular Associations
| Feature | Regular Association | Polymorphic Association |
|---|---|---|
| Scalability | Requires a new foreign key column for every new parent type. | One set of columns handles infinite parent types. |
| Code DRYness | Logic for "Comments" must be duplicated or heavily branched. | A single Comment model handles logic for all
parents. |
| Complexity | Low; easy to enforce database-level foreign key constraints. | Medium; database-level integrity is harder to enforce (requires custom logic). |
When to Use
Use Polymorphic Associations when multiple models need the same functionality
(Comments, Photos, Tags, Addresses) and you want to avoid creating separate
tables like post_comments and product_comments.
Active Record Validations are rules defined at the model level to ensure that only valid data is saved to your database. They are the first line of defense against corrupted, incomplete, or malicious data entry.
Validations are triggered automatically when you attempt to save an object. If the data violates any defined rules, Rails will:
- Prevent the save: The
INSERTorUPDATESQL command is never sent to the database. - Populate errors: It adds error messages to the object’s
errorscollection. - Return false: Methods like
.saveor.updatereturnfalseinstead oftrue.
Common Validation Helpers
| Helper | Purpose | Example |
|---|---|---|
presence |
Ensures the field is not empty. | validates :name, presence: true |
uniqueness |
Ensures no other record has the same value. | validates :email, uniqueness: true |
length |
Constraints the character count. | validates :bio, length: { maximum: 500 } |
numericality |
Ensures the value is a number (or integer). | validates :age, numericality: { only_integer: true }
|
format |
Uses Regex to match specific patterns. | validates :zip, format: { with: /\d{5}/ } |
inclusion |
Ensures value is within a specific list. | validates :size, inclusion: { in: %w(S M L) }
|
Why Validate in the Model (vs. Client-side or Database)?
Rails encourages model-level validations because they provide the best balance of flexibility and security:
- Database Agnostic: Unlike database constraints (e.g.,
NOT NULL), model validations work the same regardless of whether you use PostgreSQL or MySQL. - User Experience: They allow Rails to easily pass specific error messages back to the View (e.g., "Email is already taken"), which is harder to do with database-level errors.
- Decoupled from UI: Unlike JavaScript (client-side) validations, model validations cannot be bypassed by turning off JS or using an API tool like Postman.
Example Usage
In Rails, these three methods are used to retrieve records from the database, but they differ in their return types, how they handle missing records, and the arguments they accept.
Key Differences
| Method | Return Type | If No Record Found | Common Argument |
|---|---|---|---|
find |
A single object | Raises ActiveRecord::RecordNotFound |
Primary Key (id) |
find_by |
A single object | Returns nil |
Hash (e.g., email: '...') |
where |
ActiveRecord::Relation (Collection) |
Returns an empty collection ([]) |
Hash, String, or Array |
1.find(*args)
Used specifically to retrieve a record by its primary key (usually id).
- Usage:
User.find(1) - Behavior: If the ID does not exist in the database, it
throws a 404-style error. This is preferred when you expect the record to
exist (e.g., from a URL parameter /
users/1).
2.find_by(args)
Used to find the first record that matches the given criteria.
- Usage:
User.find_by(email: 'test@example.com')orUser.find_by(name: 'John', status: 'active') - Behavior: It returns
nilif no match is found. This is safer for logic where a record might be missing (e.g., a login attempt).
3.where(args)
Used to find all records matching certain criteria.
- Usage:
User.where(active: true) - Behavior: It returns a Relation, which
acts like an array but allows for Lazy Loading. The actual
SQL query isn't run until you try to use the data (e.g., calling
.each). This allows you to chain more methods, likeUser.where(active: true).order(:name). - Use
findwhen searching by ID and you want the app to crash/show 404 if the ID is invalid. - Use
find_bywhen searching by any column other than ID and you only need one result. - Use
wherewhen you expect a list of results or want to chain further queries (like sorting or limiting).
When to use which?
Active Record Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic at specific moments—such as before a record is saved, after it is created, or before it is deleted. They are used to automate tasks related to the object's state changes.
The Object Lifecycle
When an object is manipulated in Rails, it passes through a sequence of events. Callbacks allow you to "intercept" these events.
Commonly Used Callbacks
| Callback | Timing | Typical Use Case |
|---|---|---|
before_validation |
Runs before validation rules are checked. | Normalizing input (e.g., downcasing an email or stripping whitespace). |
before_save |
Runs before both create and update. | Calculating a value based on other fields (e.g., generating a URL slug). |
after_create |
Runs after a record is first saved to the DB. | Sending a “Welcome” email or initializing related profile data. |
after_commit |
Runs after the DB transaction is finalized. | Triggering background jobs or clearing an external cache. |
before_destroy |
Runs before a record is deleted. | Checking conditions to prevent deletion (e.g., “cannot delete admin”). |
Implementation Example
Best Practices & Risks
- Avoid "Fat" Callbacks: Don't put complex business logic in callbacks. They should ideally handle internal object state. For complex operations (like payment processing), use Service Objects.
- Side Effects: Be careful with
after_save.If you modify the object inside anafter_savecallback, you might trigger an infinite loop of saves. - Skipping Callbacks: Be aware that certain methods (like
update_column,delete, orinsert_all) bypass callbacks entirely for performance reasons.
The N+1 Query Problem is a performance issue that occurs when an application executes one query to fetch a parent record (the "1"), followed by a separate query for every associated child record (the "N"). This results in excessive database hits, which significantly slows down applications as data grows.
The Problem Example
Imagine you want to display a list of 10 Posts and the Author of each post.
Database logs would show:
SELECT * FROM posts LIMIT 10(1 query)SELECT * FROM users WHERE id = 1SELECT * FROM users WHERE id = 2 ...(10 separate queries) Total: 11 queries (N+1)
The Solution: Eager Loading with includes
Rails solves this using Eager Loading. The includes
method tells Rails to fetch the associated records in as few queries as
possible.
Database logs now show:
SELECT * FROM posts LIMIT 10SELECT * FROM users WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)Total: 2 queries
Comparison: N+1 vs. Eager Loading
| Feature | N+1 Query (Lazy Loading) | includes (Eager Loading) |
|---|---|---|
| Database Hits | 1 + N queries | Usually 2 queries |
| Performance | Slow; degrades as N increases | Fast; constant query count |
| Memory Usage | Lower (loads data only when needed) | Higher (loads all data into memory at once) |
| Best Use Case | When you only need the parent record | When you know you will access associations |
Advanced Usage
- Multiple Associations:
Post.includes(:user, :comments) - Nested
Associations:
Post.includes(comments: :author)(Fetches posts, their comments, and the authors of those comments).