Ruby 入门指南 / 第 17 章:Rails 入门
第 17 章:Rails 入门
“Rails 让 Web 开发变得简单而愉快。” —— DHH
17.1 Rails 概述
17.1.1 Rails 是什么
Ruby on Rails(简称 Rails)是一个全栈 Web 框架,由 David Heinemeier Hansson(DHH)于 2004 年创建。
| 核心原则 | 说明 |
|---|---|
| 约定优于配置 | 合理的默认值,减少配置 |
| DRY | Don’t Repeat Yourself |
| REST | 资源导向的架构 |
| 约定路由 | resources 自动生成 RESTful 路由 |
17.1.2 MVC 架构
┌─────────────────────────────────────────┐
│ Rails │
├─────────────────────────────────────────┤
│ 浏览器请求 → 路由 → 控制器 → 模型 → 视图 │
│ │
│ Model(模型) - ActiveRecord │
│ View(视图) - ERB / Haml │
│ Controller(控制器)- Action Controller │
└─────────────────────────────────────────┘
17.2 安装和创建
17.2.1 安装 Rails
# 安装 Rails
gem install rails
# 验证
rails --version
# 创建新项目
rails new myapp
rails new myapp --database=postgresql
rails new myapp --api # API 模式
rails new myapp --skip-test --skip-system-test
17.2.2 项目结构
myapp/
├── app/ # 应用代码
│ ├── controllers/ # 控制器
│ ├── models/ # 模型
│ ├── views/ # 视图
│ ├── helpers/ # 辅助方法
│ ├── javascript/ # 前端代码
│ └── assets/ # 静态资源
├── config/ # 配置文件
│ ├── routes.rb # 路由
│ ├── database.yml # 数据库配置
│ └── environments/ # 环境配置
├── db/ # 数据库
│ └── migrate/ # 迁移文件
├── spec/ 或 test/ # 测试
├── Gemfile # 依赖
└── bin/ # 脚本
└── rails # Rails 命令
17.2.3 常用命令
# 启动服务器
rails server
rails s
# 控制台
rails console
rails c
# 数据库
rails db:create
rails db:migrate
rails db:seed
rails db:rollback
# 生成器
rails generate model User name:string email:string
rails generate controller Users index show
rails generate migration AddAgeToUsers age:integer
rails generate scaffold Post title:string body:text
# 路由
rails routes
rails routes | grep users
# 测试
rails test
rails test:system
17.3 路由
17.3.1 基本路由
# config/routes.rb
Rails.application.routes.draw do
# 基本路由
get "/about", to: "pages#about"
post "/contact", to: "pages#contact"
# 多个 HTTP 方法
match "/path", to: "controller#action", via: [:get, :post]
# 资源路由(RESTful)
resources :users
# 有限资源
resources :posts, only: [:index, :show, :create]
resources :comments, except: [:destroy]
# 嵌套资源
resources :users do
resources :posts
end
# 命名空间
namespace :admin do
resources :users
resources :posts
end
# 自定义路由
get "search", to: "search#index"
post "login", to: "sessions#create"
delete "logout", to: "sessions#destroy"
# 根路由
root "pages#home"
end
17.3.2 RESTful 路由
resources :users
# 生成以下路由:
# 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 /users/:id → users#update
# DELETE /users/:id → users#destroy
# 辅助方法
# users_path → /users
# user_path(@user) → /users/1
# new_user_path → /users/new
# edit_user_path(@user) → /users/1/edit
17.4 控制器
17.4.1 基本控制器
# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
@users = User.all
@users = @users.where(active: true) if params[:active]
@users = @users.order(created_at: :desc).page(params[:page])
end
def show
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: "User created successfully."
else
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
if @user.update(user_params)
redirect_to @user, notice: "User updated successfully."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@user.destroy
redirect_to users_path, notice: "User deleted."
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :email, :age, :active)
end
end
17.4.2 渲染和重定向
class PagesController < ApplicationController
def home
# 默认渲染 app/views/pages/home.html.erb
end
def about
render :about # 显式渲染
end
def search
@results = Search.perform(params[:q])
respond_to do |format|
format.html # 渲染 HTML 模板
format.json { render json: @results }
format.xml { render xml: @results }
end
end
def redirect_example
redirect_to root_path
redirect_to user_path(@user), status: :moved_permanently
redirect_back(fallback_location: root_path)
end
end
17.5 ActiveRecord
17.5.1 模型基础
# app/models/user.rb
class User < ApplicationRecord
# 关联
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
belongs_to :organization, optional: true
# 验证
validates :name, presence: true, length: { minimum: 2, maximum: 50 }
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :age, numericality: { greater_than: 0, less_than: 150 }, allow_nil: true
# 回调
before_save :normalize_email
after_create :send_welcome_email
# 作用域
scope :active, -> { where(active: true) }
scope :adults, -> { where("age >= ?", 18) }
scope :recent, -> { where("created_at > ?", 1.week.ago) }
# 类方法
def self.search(query)
where("name LIKE ? OR email LIKE ?", "%#{query}%", "%#{query}%")
end
# 实例方法
def full_name
"#{first_name} #{last_name}"
end
private
def normalize_email
self.email = email.downcase.strip
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
17.5.2 查询
# 基本查询
User.all
User.find(1)
User.find_by(email: "[email protected]")
User.where(active: true)
User.where("age > ?", 18)
# 链式查询
User.active.adults.recent.order(name: :asc).limit(10)
# 聚合
User.count
User.average(:age)
User.maximum(:age)
User.minimum(:age)
User.group(:role).count
# 复杂查询
User.includes(:posts).where(posts: { published: true })
User.joins(:posts).where("posts.created_at > ?", 1.month.ago)
User.left_joins(:comments).group("users.id").having("COUNT(comments.id) > ?", 5)
# 原始 SQL
User.find_by_sql("SELECT * FROM users WHERE age > 18")
# 分页
User.page(1).per(25) # Kaminari
User.where(active: true).page(params[:page])
17.5.3 CRUD 操作
# 创建
user = User.new(name: "Alice", email: "[email protected]")
user.save
User.create(name: "Alice", email: "[email protected]")
User.create!(name: "Alice", email: "[email protected]") # 失败时抛异常
# 读取
User.find(1)
User.find_by(email: "[email protected]")
User.first
User.last
User.where(active: true)
# 更新
user.update(name: "Bob")
user.update!(name: "Bob")
user.update_columns(name: "Bob") # 跳过验证和回调
User.update_all(active: true)
# 删除
user.destroy
user.destroy!
User.destroy_all
User.delete_all # 跳过回调
17.6 数据库迁移
17.6.1 生成迁移
# 生成迁移
rails generate migration CreateUsers name:string email:string age:integer
rails generate migration AddRoleToUsers role:string
rails generate migration AddIndexToUsersEmail
17.6.2 编写迁移
# db/migrate/20240115120000_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false
t.integer :age
t.string :role, default: "user"
t.boolean :active, default: true
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :role
end
end
# db/migrate/20240116120000_add_posts_table.rb
class AddPostsTable < ActiveRecord::Migration[7.1]
def change
create_table :posts do |t|
t.references :user, null: false, foreign_key: true
t.string :title, null: false
t.text :body
t.boolean :published, default: false
t.datetime :published_at
t.timestamps
end
add_index :posts, :published
add_index :posts, [:user_id, :published]
end
end
# 复杂迁移
class MigrateUserData < ActiveRecord::Migration[7.1]
def up
User.find_each do |user|
user.update_columns(
first_name: user.name.split(" ").first,
last_name: user.name.split(" ").last
)
end
end
def down
User.find_each do |user|
user.update_columns(name: "#{user.first_name} #{user.last_name}")
end
end
end
17.6.3 迁移命令
# 运行迁移
rails db:migrate
# 回滚
rails db:rollback
rails db:rollback STEP=3 # 回滚 3 步
# 重置数据库
rails db:reset # drop + create + migrate + seed
# 迁移状态
rails db:migrate:status
# 生成迁移时指定类型
rails generate migration AddIndex \
field:type{options} \
--no-test-framework
17.7 视图
17.7.1 ERB 模板
<!-- app/views/users/index.html.erb -->
<h1>Users</h1>
<%= link_to "New User", new_user_path, class: "btn btn-primary" %>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td>
<%= link_to "Show", user_path(user) %>
<%= link_to "Edit", edit_user_path(user) %>
<%= button_to "Delete", user_path(user), method: :delete,
data: { confirm: "Are you sure?" } %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= paginate @users %>
<!-- app/views/users/_form.html.erb -->
<%= form_with(model: user) do |form| %>
<% if user.errors.any? %>
<div class="errors">
<h2><%= pluralize(user.errors.count, "error") %>:</h2>
<ul>
<% user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, class: "form-control" %>
</div>
<div class="field">
<%= form.label :email %>
<%= form.email_field :email, class: "form-control" %>
</div>
<div class="field">
<%= form.label :age %>
<%= form.number_field :age, class: "form-control" %>
</div>
<%= form.submit class: "btn btn-primary" %>
<% end %>
17.8 动手练习
- 创建博客系统
rails new blog
cd blog
rails generate scaffold Post title:string body:text
rails db:migrate
rails server
- 添加评论功能
rails generate model Comment post:references body:text
rails db:migrate
# 实现嵌套路由和表单
- 添加验证和测试
# 为 Post 模型添加验证
# 编写集成测试
17.9 本章小结
| 要点 | 说明 |
|---|---|
| MVC | Model-View-Controller 架构模式 |
| 路由 | resources 生成 RESTful 路由 |
| 控制器 | 处理请求、协调模型和视图 |
| ActiveRecord | ORM 框架,映射数据库表 |
| 迁移 | 版本控制数据库结构 |
| 视图 | ERB 模板渲染 HTML |