Toolmingo
Guides17 min read

Ruby on Rails Tutorial for Beginners (2025): Learn Rails Step by Step

Complete Ruby on Rails tutorial for beginners. Learn Rails from scratch: MVC, ActiveRecord, routes, controllers, views, authentication, REST APIs, and deploy a real app. Free guide with code examples.

Ruby on Rails is the web framework that made web development fast and enjoyable. Twitter, GitHub, Shopify, and Airbnb all started on Rails. If you want to build real web applications quickly without boilerplate, Rails is the right choice. This tutorial takes you from zero to a working Rails application — no prior Rails experience required.

What you'll learn

Topic What you'll be able to do
Setup Install Ruby and Rails, create your first app
MVC Understand Models, Views, Controllers
ActiveRecord Query and manage a database with zero SQL
Routes Map URLs to controller actions
Views Build dynamic HTML with ERB templates
Forms Create and validate user input
Authentication Add login/signup with Devise
REST API Build a JSON API
Testing Write RSpec unit and request specs
Deploy Ship to Fly.io or Render

Why Ruby on Rails?

Rails Django Laravel Express
Language Ruby Python PHP JavaScript
Philosophy Convention over Configuration Batteries included Elegant syntax Minimalist
Learning curve Low-Medium Medium Medium Low
Built-in ORM ActiveRecord Django ORM Eloquent None
Database migrations Yes Yes Yes Manual
Admin panel ActiveAdmin (gem) Built-in Nova (paid) None
Best for Full-stack CRUD apps, startups Data-heavy apps PHP teams Microservices
Job market ~80k+ jobs ~120k+ jobs ~100k+ jobs ~300k+ jobs

What Rails is great for

  • CRUD web applications (blogs, SaaS, marketplaces)
  • Rapid prototyping — idea to working app in hours
  • Admin dashboards and internal tools
  • REST APIs and JSON backends
  • Startups (speed of development is a superpower)

1. Installation and Setup

Install Ruby

macOS (recommended: rbenv):

brew install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0
ruby --version    # ruby 3.3.0

Ubuntu/Debian:

sudo apt install rbenv
rbenv install 3.3.0
rbenv global 3.3.0

Windows: Use WSL2 + Ubuntu, then follow the Linux steps.

Install Rails

gem install rails
rails --version    # Rails 7.2.x (or similar)

Create your first Rails app

rails new myapp --database=postgresql
cd myapp

What rails new generates:

Directory/File Purpose
app/ Your application code (MVC)
app/models/ ActiveRecord models
app/controllers/ Controllers
app/views/ ERB templates
config/routes.rb URL routing
db/ Database schema and migrations
Gemfile Ruby gem dependencies
test/ or spec/ Tests

Start the server

bin/rails db:create
bin/rails server    # or: rails s

Open http://localhost:3000 — you'll see the Rails welcome page.


2. MVC Architecture

Rails is built on the Model-View-Controller pattern.

Browser → Routes → Controller → Model → Database
                       ↓
                      View → HTML → Browser
Layer File location Responsibility
Model app/models/ Business logic, database queries
View app/views/ HTML templates (ERB)
Controller app/controllers/ Handle requests, coordinate model+view
Routes config/routes.rb Map URLs to controller actions

Request lifecycle

  1. Browser sends GET /posts
  2. Router matches route → PostsController#index
  3. Controller calls Post.all (Model)
  4. Model queries database, returns records
  5. Controller passes data to view (@posts)
  6. View renders HTML with @posts
  7. Controller sends HTML back to browser

3. Your First Resource: Posts

Generate a scaffold

Rails can generate a complete CRUD resource in one command:

bin/rails generate scaffold Post title:string body:text published:boolean

This generates:

  • Model (app/models/post.rb)
  • Controller (app/controllers/posts_controller.rb)
  • Views (app/views/posts/)
  • Migration (db/migrate/xxx_create_posts.rb)
  • Routes (added to config/routes.rb)

Run the migration

bin/rails db:migrate

Now visit http://localhost:3000/posts — full CRUD app, no code written.

What the scaffold created

Controller (app/controllers/posts_controller.rb):

class PostsController < ApplicationController
  before_action :set_post, only: %i[show edit update destroy]

  def index
    @posts = Post.all
  end

  def show
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post, notice: "Post was successfully created."
    else
      render :new, status: :unprocessable_entity
    end
  end

  def update
    if @post.update(post_params)
      redirect_to @post, notice: "Post was successfully updated."
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @post.destroy
    redirect_to posts_url, notice: "Post was successfully destroyed."
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end

  def post_params
    params.require(:post).permit(:title, :body, :published)
  end
end

Index view (app/views/posts/index.html.erb):

<h1>Posts</h1>

<% @posts.each do |post| %>
  <div>
    <h2><%= post.title %></h2>
    <p><%= post.body %></p>
    <%= link_to "Show", post %>
    <%= link_to "Edit", edit_post_path(post) %>
    <%= link_to "Delete", post, data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>
  </div>
<% end %>

<%= link_to "New Post", new_post_path %>

4. Routes

Routes map URLs and HTTP methods to controller actions.

config/routes.rb:

Rails.application.routes.draw do
  resources :posts          # generates 7 RESTful routes
  root "posts#index"        # homepage
end

RESTful routes table

HTTP Method URL Controller action Purpose
GET /posts index List all posts
GET /posts/new new Form to create
POST /posts create Save new post
GET /posts/:id show Show one post
GET /posts/:id/edit edit Form to edit
PATCH/PUT /posts/:id update Save edits
DELETE /posts/:id destroy Delete post

View routes

bin/rails routes

Named route helpers

posts_path          # => "/posts"
new_post_path       # => "/posts/new"
post_path(@post)    # => "/posts/1"
edit_post_path(@post) # => "/posts/1/edit"

Nested routes

resources :posts do
  resources :comments, only: [:create, :destroy]
end
# generates: /posts/:post_id/comments

5. ActiveRecord — Database Without SQL

ActiveRecord is Rails' ORM. It maps Ruby classes to database tables.

Creating a model

bin/rails generate model Comment post:references body:text author:string
bin/rails db:migrate

CRUD operations

# CREATE
post = Post.create(title: "Hello Rails", body: "My first post", published: true)
# or:
post = Post.new(title: "Hello")
post.save

# READ
Post.all                          # all records
Post.find(1)                      # find by id (raises error if not found)
Post.find_by(title: "Hello")      # find by attribute (returns nil if not found)
Post.where(published: true)       # returns array-like relation
Post.order(created_at: :desc)     # ordering
Post.limit(10).offset(20)         # pagination

# UPDATE
post.update(title: "Updated Title")
# or:
post.title = "New Title"
post.save

# DELETE
post.destroy
Post.where(published: false).destroy_all

Querying

# Conditions
Post.where("created_at > ?", 1.week.ago)
Post.where(published: true).order(:created_at)

# Counting
Post.count
Post.where(published: true).count

# Aggregates
Post.average(:views)
Post.sum(:views)
Post.maximum(:created_at)

# Joining
Post.joins(:comments).where(comments: { author: "Alice" })

# Eager loading (avoids N+1 queries)
Post.includes(:comments).all

Scopes

class Post < ApplicationRecord
  scope :published, -> { where(published: true) }
  scope :recent,    -> { order(created_at: :desc).limit(10) }
  scope :by_author, ->(name) { where(author: name) }
end

Post.published.recent
Post.by_author("Alice").published

Associations

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
  has_many :tags, through: :post_tags
  has_one :featured_image, dependent: :destroy
end

class Comment < ApplicationRecord
  belongs_to :post
  belongs_to :user
end

# Usage
post.comments          # all comments for this post
post.comments.create(body: "Great post!")
comment.post           # the post this comment belongs to

Association types

Association Method Example
One-to-Many has_many / belongs_to Post has_many Comments
One-to-One has_one / belongs_to User has_one Profile
Many-to-Many has_many :through Post has_many Tags through PostTags
Many-to-Many (simple) has_and_belongs_to_many Post has_and_belongs_to_many Categories

6. Validations

Validations run before saving to the database.

class Post < ApplicationRecord
  validates :title, presence: true, length: { minimum: 5, maximum: 100 }
  validates :body, presence: true
  validates :slug, uniqueness: true, format: { with: /\A[a-z0-9-]+\z/ }
  validates :status, inclusion: { in: %w[draft published archived] }

  validate :title_must_not_contain_profanity

  private

  def title_must_not_contain_profanity
    if title&.include?("badword")
      errors.add(:title, "must not contain inappropriate language")
    end
  end
end

Common validators

Validator Example
presence validates :name, presence: true
uniqueness validates :email, uniqueness: true
length validates :bio, length: { maximum: 500 }
numericality validates :age, numericality: { greater_than: 0 }
format validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
inclusion validates :role, inclusion: { in: %w[admin user guest] }
confirmation validates :password, confirmation: true

Checking validity

post = Post.new(title: "Hi")  # too short
post.valid?          # => false
post.errors.full_messages  # => ["Title is too short (minimum is 5 characters)"]
post.save            # => false (does not save)

7. Views and ERB Templates

ERB (Embedded Ruby) mixes Ruby into HTML.

<%  %>   <!-- Execute Ruby, no output -->
<%= %>   <!-- Execute Ruby AND output result -->
<%# %>   <!-- Comment (not rendered) -->

Example view (app/views/posts/show.html.erb):

<article>
  <h1><%= @post.title %></h1>
  <p class="meta">
    Published <%= time_ago_in_words(@post.created_at) %> ago
    <% if @post.published? %>
      <span class="badge">Published</span>
    <% else %>
      <span class="badge draft">Draft</span>
    <% end %>
  </p>

  <div class="body">
    <%= simple_format(@post.body) %>
  </div>

  <h2>Comments (<%= @post.comments.count %>)</h2>
  <% @post.comments.each do |comment| %>
    <div class="comment">
      <strong><%= comment.author %></strong>
      <p><%= comment.body %></p>
    </div>
  <% end %>
</article>

<%= link_to "Edit", edit_post_path(@post) %>
<%= link_to "Back", posts_path %>

Layouts

The application layout wraps all views:

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
  <head>
    <title>MyApp</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag "application", data: { turbo_track: "reload" } %>
    <%= javascript_importmap_tags %>
  </head>
  <body>
    <nav>
      <%= link_to "Home", root_path %>
      <%= link_to "Posts", posts_path %>
    </nav>

    <% if notice %>
      <div class="notice"><%= notice %></div>
    <% end %>

    <main>
      <%= yield %>   <!-- view content goes here -->
    </main>
  </body>
</html>

Partials

Reusable view fragments start with _:

<!-- app/views/posts/_post.html.erb -->
<div class="post">
  <h2><%= post.title %></h2>
  <p><%= truncate(post.body, length: 150) %></p>
  <%= link_to "Read more", post %>
</div>

Render in a view:

<!-- renders _post.html.erb for each post -->
<%= render @posts %>

<!-- or explicitly: -->
<%= render partial: "post", locals: { post: @post } %>

View helpers

link_to "Click me", posts_path                    # <a href="/posts">Click me</a>
image_tag "logo.png", alt: "Logo"                 # <img src="..." alt="Logo">
time_ago_in_words(post.created_at)                # "3 minutes"
number_to_currency(49.99)                         # "$49.99"
truncate("Long text...", length: 50)              # "Long tex..."
simple_format("Line 1\nLine 2")                  # wraps in <p> tags
pluralize(post.comments.count, "comment")         # "3 comments"

8. Forms

Rails forms use form_with helper.

New post form (app/views/posts/new.html.erb):

<h1>New Post</h1>

<%= form_with model: @post do |form| %>
  <% if @post.errors.any? %>
    <div class="errors">
      <h2><%= pluralize(@post.errors.count, "error") %> prevented saving:</h2>
      <ul>
        <% @post.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div>
    <%= form.label :title %>
    <%= form.text_field :title, placeholder: "Enter title..." %>
  </div>

  <div>
    <%= form.label :body %>
    <%= form.text_area :body, rows: 10 %>
  </div>

  <div>
    <%= form.label :published %>
    <%= form.check_box :published %>
  </div>

  <%= form.submit "Save Post" %>
<% end %>

<%= link_to "Cancel", posts_path %>

Form field helpers

Helper HTML output
form.text_field :name <input type="text">
form.text_area :body <textarea>
form.check_box :active <input type="checkbox">
form.select :role, ["admin","user"] <select>
form.email_field :email <input type="email">
form.password_field :password <input type="password">
form.number_field :age <input type="number">
form.file_field :avatar <input type="file">
form.hidden_field :token <input type="hidden">
form.submit "Save" <input type="submit">

9. Authentication with Devise

Devise is the standard Rails authentication gem.

Install

# Gemfile
gem "devise"
bundle install
bin/rails generate devise:install
bin/rails generate devise User
bin/rails db:migrate

What Devise adds

  • User model with email/password/confirmable/recoverable
  • Routes: sign_in, sign_up, sign_out, password reset, confirmation
  • Controllers and views for all auth flows
  • current_user helper in controllers and views
  • authenticate_user! before action

Protect routes

class PostsController < ApplicationController
  before_action :authenticate_user!, except: [:index, :show]

  # now only logged-in users can create/edit/delete
end

In views

<% if user_signed_in? %>
  Logged in as <%= current_user.email %>
  <%= link_to "Sign out", destroy_user_session_path, data: { turbo_method: :delete } %>
<% else %>
  <%= link_to "Sign in", new_user_session_path %>
  <%= link_to "Sign up", new_user_registration_path %>
<% end %>

Add user to posts

bin/rails generate migration AddUserToPosts user:references
bin/rails db:migrate
class Post < ApplicationRecord
  belongs_to :user
end

class User < ApplicationRecord
  has_many :posts, dependent: :destroy
end
# PostsController
def create
  @post = current_user.posts.build(post_params)
  # ...
end

10. Building a REST API

Rails can serve JSON APIs without any views.

API-only app

rails new myapi --api --database=postgresql

JSON responses

class Api::V1::PostsController < ApplicationController
  def index
    posts = Post.published.order(created_at: :desc)
    render json: posts, status: :ok
  end

  def show
    post = Post.find(params[:id])
    render json: post, status: :ok
  rescue ActiveRecord::RecordNotFound
    render json: { error: "Post not found" }, status: :not_found
  end

  def create
    post = Post.new(post_params)
    if post.save
      render json: post, status: :created
    else
      render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :body, :published)
  end
end

API routes

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :posts, only: [:index, :show, :create, :update, :destroy]
    end
  end
end

Custom serialization with jbuilder

# app/views/api/v1/posts/show.json.jbuilder
json.id @post.id
json.title @post.title
json.body @post.body
json.published_at @post.created_at.iso8601
json.author do
  json.id @post.user.id
  json.name @post.user.name
end
json.comments @post.comments do |comment|
  json.id comment.id
  json.body comment.body
  json.author comment.user.name
end

JWT authentication for APIs

# Gemfile
gem "jwt"

# Generate token
def encode_token(payload)
  JWT.encode(payload, Rails.application.secret_key_base, "HS256")
end

# Decode token
def decode_token(token)
  JWT.decode(token, Rails.application.secret_key_base, true, algorithm: "HS256")
rescue JWT::DecodeError
  nil
end

11. Background Jobs with Sidekiq

For long-running tasks (emails, image processing, data import).

# Gemfile
gem "sidekiq"

# app/jobs/notification_job.rb
class NotificationJob < ApplicationJob
  queue_as :default

  def perform(user_id, message)
    user = User.find(user_id)
    UserMailer.notification(user, message).deliver_now
  end
end

# Enqueue from anywhere
NotificationJob.perform_later(current_user.id, "Your post was published!")
NotificationJob.set(wait: 5.minutes).perform_later(user.id, "Reminder!")

Start Sidekiq:

bundle exec sidekiq

12. Testing with RSpec

# Gemfile (development, test group)
group :development, :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
  gem "faker"
end
bin/rails generate rspec:install

Model spec

# spec/models/post_spec.rb
require "rails_helper"

RSpec.describe Post, type: :model do
  describe "validations" do
    it "is valid with valid attributes" do
      post = build(:post)
      expect(post).to be_valid
    end

    it "requires a title" do
      post = build(:post, title: nil)
      expect(post).not_to be_valid
      expect(post.errors[:title]).to include("can't be blank")
    end

    it "requires title to be at least 5 characters" do
      post = build(:post, title: "Hi")
      expect(post).not_to be_valid
    end
  end

  describe ".published" do
    it "returns only published posts" do
      published = create(:post, published: true)
      draft = create(:post, published: false)
      expect(Post.published).to include(published)
      expect(Post.published).not_to include(draft)
    end
  end
end

Factory

# spec/factories/posts.rb
FactoryBot.define do
  factory :post do
    title { Faker::Lorem.sentence(word_count: 5) }
    body { Faker::Lorem.paragraphs(number: 3).join("\n") }
    published { true }
    association :user
  end
end

Request spec

# spec/requests/posts_spec.rb
require "rails_helper"

RSpec.describe "Posts API", type: :request do
  let(:user) { create(:user) }
  let(:headers) { { "Authorization" => "Bearer #{auth_token(user)}" } }

  describe "GET /api/v1/posts" do
    before { create_list(:post, 3, user: user) }

    it "returns all posts" do
      get "/api/v1/posts", headers: headers
      expect(response).to have_http_status(:ok)
      expect(JSON.parse(response.body).length).to eq(3)
    end
  end

  describe "POST /api/v1/posts" do
    let(:valid_params) { { post: { title: "New Post Title", body: "Body content here", published: true } } }

    it "creates a post" do
      expect {
        post "/api/v1/posts", params: valid_params, headers: headers
      }.to change(Post, :count).by(1)
      expect(response).to have_http_status(:created)
    end

    it "returns errors for invalid params" do
      post "/api/v1/posts", params: { post: { title: "" } }, headers: headers
      expect(response).to have_http_status(:unprocessable_entity)
    end
  end
end

13. Migrations

Migrations version-control your database schema.

# Generate migrations
bin/rails generate migration AddSlugToPosts slug:string:uniq
bin/rails generate migration AddIndexToPostsTitle
bin/rails generate migration RemovePublishedFromPosts published:boolean

Example migration:

class AddSlugAndIndexToPosts < ActiveRecord::Migration[7.2]
  def change
    add_column :posts, :slug, :string
    add_column :posts, :views_count, :integer, default: 0, null: false
    add_index :posts, :slug, unique: true
    add_index :posts, :published
    add_index :posts, [:user_id, :created_at]
  end
end

Migration commands

Command What it does
rails db:migrate Run pending migrations
rails db:rollback Undo last migration
rails db:rollback STEP=3 Undo last 3 migrations
rails db:migrate:status Show migration status
rails db:schema:load Load schema fresh
rails db:seed Run db/seeds.rb
rails db:reset Drop + create + migrate + seed

14. Performance Tips

Avoid N+1 queries

# BAD — N+1 (1 query for posts + 1 per post for user)
@posts = Post.all
# In view: post.user.name  # runs query for EACH post

# GOOD — eager load
@posts = Post.includes(:user).all
# In view: post.user.name  # no extra queries

# Also: includes with conditions
@posts = Post.includes(:comments).where(published: true)

Detect N+1 with Bullet gem

# Gemfile (development)
gem "bullet"

Pagination

# Gemfile
gem "pagy"

# Controller
include Pagy::Backend
@pagy, @posts = pagy(Post.published.order(created_at: :desc), items: 20)

# View
include Pagy::Frontend
<%= pagy_nav(@pagy) %>

Caching

# Cache a fragment
<% cache @post do %>
  <%= render @post %>
<% end %>

# Cache a value
Rails.cache.fetch("popular_posts", expires_in: 1.hour) do
  Post.published.order(views_count: :desc).limit(10).to_a
end

15. Deploy to Fly.io

# Install flyctl
curl -L https://fly.io/install.sh | sh

# Login
fly auth login

# Launch app
fly launch

# Set environment variables
fly secrets set RAILS_MASTER_KEY=$(cat config/master.key)

# Deploy
fly deploy

# Run migrations
fly ssh console -C "bin/rails db:migrate"

fly.toml (auto-generated, key sections):

[build]

[env]
  PORT = "8080"
  RAILS_ENV = "production"

[http_service]
  internal_port = 8080
  force_https = true

16. Build This: Blog App

Putting it all together — a blog with users, posts, and comments.

# Create app
rails new blog --database=postgresql
cd blog

# Add gems
bundle add devise pagy rspec-rails factory_bot_rails

# Generate models
bin/rails generate devise:install
bin/rails generate devise User name:string
bin/rails generate scaffold Post title:string body:text slug:string published:boolean user:references
bin/rails generate model Comment body:text user:references post:references
bin/rails db:migrate

# Add to routes
# resources :posts do
#   resources :comments, only: [:create, :destroy]
# end
# root "posts#index"

Seed data (db/seeds.rb):

user = User.create!(
  name: "Alice",
  email: "alice@example.com",
  password: "password123"
)

5.times do |i|
  Post.create!(
    title: "Post #{i + 1}: Getting Started with Rails",
    body: "Rails is a web application framework...",
    published: true,
    user: user
  )
end

puts "Created #{User.count} users and #{Post.count} posts"
bin/rails db:seed
bin/rails server

Common Mistakes

Mistake Problem Fix
N+1 queries Slow pages Use includes() to eager load
No strong_params Mass assignment vulnerability Always use params.permit()
Logic in views Unmaintainable code Move to models or helpers
Fat controllers Hard to test Move business logic to models/services
Missing indexes Slow queries Index foreign keys and frequently queried columns
Storing secrets in code Security breach Use credentials.yml.enc or ENV vars
Skipping validations with save! Uncaught errors Handle ActiveRecord::RecordInvalid
Forgetting dependent: :destroy Orphaned records Add to has_many associations

Rails vs related technologies

Purpose When to use
Rails Full-stack web framework CRUD apps, SaaS, admin panels
Sinatra Minimal Ruby framework Simple APIs, microservices
Hanami Modern Ruby framework Clean architecture, performance
Devise Authentication gem Login/signup in any Rails app
ActiveAdmin Admin panel gem Internal dashboards fast
Sidekiq Background jobs Async tasks, email, processing
Hotwire (Turbo+Stimulus) SPA-like UX without JS framework Dynamic Rails pages
Pagy Pagination gem Paginating large collections
RSpec Testing framework Unit, integration, request specs
Fly.io / Render Hosting platforms Deploy Rails apps easily

Ruby on Rails developer roles and salary

Role Experience Salary (US)
Junior Rails Developer 0–2 years $65k–$90k
Mid-level Rails Developer 2–5 years $90k–$130k
Senior Rails Developer 5+ years $130k–$175k
Rails Tech Lead 7+ years $150k–$200k
Fullstack (Rails + React) 3+ years $110k–$160k

Learning path

Stage Topics Time
1. Ruby basics Variables, methods, classes, blocks, modules 2–3 weeks
2. Rails fundamentals MVC, routes, CRUD, ActiveRecord 3–4 weeks
3. Authentication Devise, sessions, authorization (Pundit/CanCanCan) 1–2 weeks
4. Testing RSpec, FactoryBot, Capybara 2 weeks
5. Frontend Hotwire Turbo+Stimulus, or React/Vue with Rails API 2–4 weeks
6. Background jobs Sidekiq, Action Mailer, Active Storage 1–2 weeks
7. Deploy & ops Fly.io/Render, environment config, caching 1 week
8. Portfolio Build 2–3 real apps, push to GitHub Ongoing

FAQ

Q: Do I need to learn Ruby before Rails? A: Yes — learn Ruby basics first (1–2 weeks). You don't need to be an expert, but you need to understand classes, blocks, and modules. Try Ruby in 20 Minutes is a good starting point.

Q: Is Rails still relevant in 2025? A: Yes. Rails powers major companies (GitHub, Shopify, Basecamp, Airbnb). Rails 7 added Hotwire for reactive UIs without heavy JavaScript. The ecosystem is active and thriving.

Q: Rails vs Django — which should I learn? A: If you know Python or plan to work in data science, choose Django. If you prefer Ruby's expressiveness and want the fastest path to building full-stack apps, choose Rails. Both are excellent choices.

Q: How long to get a Rails job? A: With dedicated study (8–12 hours/week), most people are job-ready in 4–6 months. Build real projects, contribute to open-source Rails apps, and deploy something publicly.

Q: What's Hotwire? A: Hotwire (Turbo + Stimulus) ships with Rails 7 and gives you SPA-like interactivity — partial page updates, live search, real-time feeds — without writing a JavaScript framework. It's Rails' answer to React for most use cases.

Q: PostgreSQL vs MySQL for Rails? A: PostgreSQL is recommended for Rails apps. It supports more advanced features (JSONB, full-text search, array columns) and is the default on most Rails hosting platforms. Use --database=postgresql when creating your app.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools