Rails Intro
Rails is the convention-over-configuration web framework that taught the industry MVC, migrations, REST, and asset pipelines. Modern Rails is still the most productive option for full-stack apps when youre comfortable with its opinions. This lesson sketches the parts you touch on day one — generate, model, route, controller, view.
Generate a resource end-to-end with Rails 7
EXAMPLE
# 1) Generate a new app (modern defaults — esbuild + Tailwind + Hotwire)
# rails new shop --css=tailwind --javascript=esbuild --database=postgresql
# 2) Scaffold a resource — model, migration, controller, views, routes, tests
# bin/rails g scaffold Product name:string price_cents:integer slug:string:index
# bin/rails db:migrate
# config/routes.rb
Rails.application.routes.draw do
resources :products
root "products#index"
end
# app/models/product.rb
class Product < ApplicationRecord
validates :name, presence: true, length: { maximum: 120 }
validates :slug, presence: true, uniqueness: true
validates :price_cents, numericality: { greater_than_or_equal_to: 0 }
before_validation :generate_slug, on: :create
scope :active, -> { where(archived_at: nil) }
def price_dollars = (price_cents / 100.0).round(2)
private
def generate_slug
self.slug ||= name.to_s.parameterize
end
end
# app/controllers/products_controller.rb
class ProductsController < ApplicationController
before_action :set_product, only: %i[show edit update destroy]
def index
@products = Product.active.order(created_at: :desc).page(params[:page])
end
def show; end
def new; @product = Product.new end
def edit; end
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product, notice: "Product created"
else
render :new, status: :unprocessable_entity
end
end
def update
if @product.update(product_params)
redirect_to @product, notice: "Updated"
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@product.update!(archived_at: Time.current)
redirect_to products_path, notice: "Archived"
end
private
def set_product; @product = Product.find_by!(slug: params[:id]) end
def product_params; params.require(:product).permit(:name, :price_cents) end
end
# app/views/products/index.html.erb
<h1>Products</h1>
<%= link_to "New product", new_product_path, class: "btn" %>
<ul>
<% @products.each do |p| %>
<li>
<%= link_to p.name, p %> — \$<%= p.price_dollars %>
<%= button_to "Archive", p, method: :delete, data: { turbo_confirm: "Sure?" } %>
</li>
<% end %>
</ul>
# spec/models/product_spec.rb (RSpec) — or use Minitest under test/
require "rails_helper"
RSpec.describe Product, type: :model do
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:slug) }
it "auto-generates a slug on create" do
p = Product.create!(name: "Wool Jacket", price_cents: 19900)
expect(p.slug).to eq("wool-jacket")
end
end
# Run it
# bin/dev # esbuild + Tailwind + Puma together
# bin/rails test # or bin/rspec
# bin/rails routes -g products
Why it matters
Rails 7s Hotwire (Turbo + Stimulus) brings SPA-feel responsiveness without ever shipping a JSON API or a heavy front-end framework. For most CRUD apps, Hotwire + Tailwind is faster to build, lighter to ship, and easier to maintain than React/Vue + a separate Rails API.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Exercise
Create a new Rails app.
rails
blog
Three letters.
Discussion
Loading…