Ruby on Rails - 脚手架
Ruby on Rails - 现代脚手架 (Scaffolding)
Section titled “Ruby on Rails - 现代脚手架 (Scaffolding)”在开发 Rails 应用时,尤其是需要简单界面来管理数据库记录(CRUD 操作 - 创建、读取、更新、删除)的应用,Scaffolding 可以是快速原型开发和理解 Rails 约定的强大工具。
Scaffolding 除了快速演示之外,还提供了显著的优势:
- 快速生成一个功能性原型以收集早期用户反馈。
- 加速开发并更快地实现切实的成果,提升积极性。
- 通过检查生成的代码,提供了一种理解 Rails 约定(MVC, RESTful 设计)的绝佳方式。
- 作为一个坚实的基础,你可以在其上进行定制和构建,以满足你的特定应用需求。
Scaffolding 示例:构建一个食谱应用
Section titled “Scaffolding 示例:构建一个食谱应用”为了说明 Scaffolding,我们将构建一个简单的“食谱”应用来管理“食谱记录”。我们将使用 Rails 生成器为我们的 Recipe 资源创建必要的文件和数据库结构。
创建一个新的 Rails 应用
Section titled “创建一个新的 Rails 应用”打开你的终端或命令行,导航到你想要的工程目录,然后运行以下命令创建一个名为 ‘cookbook’ 的新 Rails 应用:
rails new cookbook此命令会创建一个名为 cookbook 的新 Rails 应用目录,其中包含标准的工程结构。默认情况下,新的 Rails 应用(版本 5.0 及更高版本)配置为使用 SQLite3 进行开发和测试,它是一个轻量级、基于文件的数据库,只需最少的设置。
数据库配置与设置
Section titled “数据库配置与设置”Rails 可以高效地处理数据库的创建和配置。数据库设置位于 config/database.yml 中。对于我们默认的 SQLite 设置,它通常看起来像这样:
default: &default adapter: sqlite3 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000
development: <<: *default database: db/development.sqlite3
test: <<: *default database: db/test.sqlite3
production: <<: *default database: db/production.sqlite3如果你更喜欢使用 PostgreSQL 或 MySQL 等其他数据库,可以在创建新应用时指定(例如,rails new cookbook --database=postgresql)。然后,你需要确保数据库服务器正在运行,并在必要时使用适当的凭据更新 config/database.yml。
配置好 config/database.yml 后(或者如果你使用默认的 SQLite 设置),通过运行以下命令创建开发和测试数据库:
rails db:create此命令读取 config/database.yml 并为你当前环境创建指定的数据库。
生成 Scaffold 代码
Section titled “生成 Scaffold 代码”rails generate scaffold 命令会为指定的资源创建一整套 Model、View、Controller (MVC) 组件,以及路由和数据库迁移文件。这提供了一个完整的 CRUD(创建、读取、更新、删除)界面。
导航到你的应用目录(cd cookbook)并运行以下命令,为 Recipe 资源生成 Scaffold。我们将为其指定一个 title(string 类型)和一个 instructions(text 类型):
rails generate scaffold Recipe title:string instructions:text此命令将生成多个文件并输出类似以下的列表(时间戳会有所不同):
invoke active_record create db/migrate/YYYYMMDDHHMMSS_create_recipes.rb create app/models/recipe.rb invoke test_unit create test/models/recipe_test.rb create test/fixtures/recipes.yml invoke resource_route route resources :recipes invoke scaffold_controller create app/controllers/recipes_controller.rb invoke erb create app/views/recipes create app/views/recipes/index.html.erb create app/views/recipes/edit.html.erb create app/views/recipes/show.html.erb create app/views/recipes/new.html.erb create app/views/recipes/_form.html.erb create app/views/recipes/_recipe.html.erb invoke test_unit create test/controllers/recipes_controller_test.rb create test/system/recipes_test.rb invoke helper create app/helpers/recipes_helper.rb invoke test_unit invoke jbuilder create app/views/recipes/index.json.jbuilder create app/views/recipes/show.json.jbuilder create app/views/recipes/_recipe.json.jbuilder生成的关键组件包括:
- Migration 文件:
db/migrate/..._create_recipes.rb- 定义数据库中recipes表的 schema(模式)。 - Model:
app/models/recipe.rb- 表示一个食谱。它继承自ApplicationRecord并用于与recipes表交互。 - Controller:
app/controllers/recipes_controller.rb- 处理 Web 请求,包含对食谱执行 CRUD 操作的逻辑。 - Views:
app/views/recipes/目录中的文件 - 用于向用户显示食谱数据和表单的 ERB 模板。 - Routes: 将
resources :recipes添加到config/routes.rb中 - 根据 RESTful 约定将 URL(例如/recipes、/recipes/new)映射到 controller actions。 - Helpers:
app/helpers/recipes_helper.rb- 包含可供食谱 views 使用的 helper 方法。 - Tests: 用于 model、controller 和系统交互的各种测试文件。
- Jbuilder views:
app/views/recipes/目录中以.json.jbuilder结尾的文件,用于构建 JSON 响应,对 APIs 非常有用。
应用数据库迁移
Section titled “应用数据库迁移”在使用新的 Recipe 资源之前,我们需要运行数据库迁移,以便根据迁移文件中定义的 schema 在数据库中创建 recipes 表:
rails db:migrate生成的 Controller:recipes_controller.rb
Section titled “生成的 Controller:recipes_controller.rb”让我们看看在 app/controllers/recipes_controller.rb 中生成的 controller。它实现了标准的 RESTful actions:
class RecipesController < ApplicationController before_action :set_recipe, only: [:show, :edit, :update, :destroy]
# GET /recipes # GET /recipes.json def index @recipes = Recipe.all end
# GET /recipes/1 # GET /recipes/1.json def show # @recipe 由 set_recipe before_action 设置 end
# GET /recipes/new def new @recipe = Recipe.new end
# GET /recipes/1/edit def edit # @recipe 由 set_recipe before_action 设置 end
# POST /recipes # POST /recipes.json def create @recipe = Recipe.new(recipe_params)
respond_to do |format| if @recipe.save format.html { redirect_to @recipe, notice: 'Recipe was successfully created.' } format.json { render :show, status: :created, location: @recipe } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @recipe.errors, status: :unprocessable_entity } end end end
# PATCH/PUT /recipes/1 # PATCH/PUT /recipes/1.json def update respond_to do |format| if @recipe.update(recipe_params) format.html { redirect_to @recipe, notice: 'Recipe was successfully updated.' } format.json { render :show, status: :ok, location: @recipe } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @recipe.errors, status: :unprocessable_entity } end end end
# DELETE /recipes/1 # DELETE /recipes/1.json def destroy @recipe.destroy respond_to do |format| format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' } format.json { head :no_content } end end
private # 使用 callbacks 分享 actions 之间的通用设置或约束。 def set_recipe @recipe = Recipe.find(params[:id]) end
# 只允许通过受信任的参数列表。 # 这使用 Strong Parameters 来防止 mass assignment 漏洞。 def recipe_params params.require(:recipe).permit(:title, :instructions) endendController 的关键方面:
before_action :set_recipe: 这个 callback 会在show、edit、update和destroyactions 之前调用set_recipe方法,以加载相关的@recipe实例变量。- RESTful Actions:
index、show、new、edit、create、update、destroy对应标准的 CRUD 操作和 HTTP verbs。 respond_to: 这个块允许 controller 根据请求的 format(例如,浏览器请求 HTML,APIs 请求 JSON)做出不同的响应。recipe_params: 这个私有方法实现了 Strong Parameters,这是一项安全功能,要求你显式允许哪些 attributes 可以通过 mass assignment 进行更新。这对于保护你的应用至关重要。
当用户导航到 /recipes/1 这样的 URL 时,Rails 将请求路由到 RecipesController 的 show action。controller(通过 set_recipe)查找食谱,然后根据约定,Rails 会渲染 app/views/recipes/show.html.erb 模板。这种“约定优于配置”(Convention over Configuration)原则简化了开发。
controller 使用 Active Record 方法,如 all、find、new、save、update 和 destroy 来与数据库交互。Active Record 是一个 ORM(对象关系映射器),它抽象了大部分 SQL,让你能够将数据库记录当作 Ruby 对象来处理。
rails generate scaffold 命令,接着是 rails db:migrate,可以快速为你的 Recipe 资源建立一个完全功能性的界面,支持:
- 创建新食谱
- 编辑现有食谱
- 查看特定食谱的详细信息
- 列出所有食谱
- 删除食谱
Scaffolding 会使用 Rails 表单 helpers(例如 form_with)自动生成用于创建和编辑记录的表单。这些 helpers 会根据模型的属性类型智能地创建适当的输入字段,例如:
- 简单的文本字符串(
<input type='text'>) - 用于大块文本的文本区域(
<textarea>) - 日期选择器
- 日期时间选择器
- 数字字段
- 用于布尔值的复选框
Rails 表单 helpers 简化了表单创建并安全地处理数据提交,包括 CSRF 保护。
既然我们已经生成了 scaffold 并迁移了数据库,现在来运行 Rails 开发服务器。在你的 cookbook 目录中,执行:
rails server在较新的 Rails 应用中,你可能还会看到使用 bin/dev 来启动 Web 服务器和其他进程,这些应用使用了 Procfile.dev(例如,配合 jsbundling-rails 或 cssbundling-rails)。rails server(或 rails s)是启动 Puma Web 服务器的直接命令。
打开你的 Web 浏览器并导航到 http://localhost:3000/recipes。你应该会看到一个列出食谱的页面(最初为空)。点击“New Recipe”(或前往 http://localhost:3000/recipes/new)来查看创建新食谱的表单。
填写表单(例如,Title: ‘Chocolate Cake’,Instructions: ‘Mix ingredients and bake.’)并点击“Create Recipe”后,数据将被保存到数据库。你将被重定向到该食谱的 show 页面,通常是 http://localhost:3000/recipes/1(如果是第一个食谱)。
在此页面以及主要的食谱列表页面(http://localhost:3000/recipes)上,你会找到链接来“Show”、“Edit”和“Destroy”每个食谱。通过这些 CRUD 操作进行实验,看看 scaffold 的实际效果。
通过 Validations 增强 Model
Section titled “通过 Validations 增强 Model”Scaffolding 提供了一个基本的 model。我们可以通过添加 validations 来增强它。Active Record validations 有助于确保数据在保存到数据库之前保持完整性。让我们为 Recipe model 添加一些规则。
打开 app/models/recipe.rb。默认情况下,新版本 Rails 中的 models 继承自 ApplicationRecord。按如下方式修改它:
class Recipe < ApplicationRecord validates :title, presence: true, length: { minimum: 3, maximum: 100 } validates :title, uniqueness: { message: "already exists. Please choose a different title." } validates :instructions, presence: true, length: { minimum: 10 }end这些 validations 的解释:
validates :title, presence: true确保 title 字段不为空。validates :title, length: { minimum: 3, maximum: 100 }确保 title 长度在 3 到 100 个字符之间。validates :title, uniqueness: { message: "..." }确保每个食谱都有一个唯一的 title,如果尝试创建重复的,会提供自定义错误消息。validates :instructions, presence: true, length: { minimum: 10 }确保提供了 instructions,并且长度至少为 10 个字符。
现在,如果你尝试创建或更新一个违反这些规则的食谱(例如,空白的 title,或 instructions 太短),Rails 会阻止保存,并且 scaffold 生成的表单会显示用户友好的错误消息。
生成的 Views
Section titled “生成的 Views”Scaffolding 还会生成一整套 views(ERB 模板),位于 app/views/recipes/ 目录中。其中包括:
index.html.erb: 显示所有食谱的列表。show.html.erb: 显示单个食谱的详细信息。new.html.erb: 渲染用于创建新食谱的表单(通常使用_form.html.erbpartial)。edit.html.erb: 渲染用于编辑现有食谱的表单(也通常使用_form.html.erbpartial)。_form.html.erb: 一个 partial view,包含用于 new 和 edit actions 的通用表单字段。这遵循了 DRY(Don’t Repeat Yourself,不要重复自己)原则。_recipe.html.erb: 一个用于渲染单个 recipe 对象的 partial view。这通常被index.html.erb用于在列表中渲染每个食谱,也可能被show.html.erb使用。
我们鼓励你探索这些文件,看看 Rails helpers(例如 form_with、link_to、button_to)和 ERB 模板如何用于动态构建 HTML。这些文件是你的起点,可以完全自定义。
Scaffolding 与手动创建的对比
Section titled “Scaffolding 与手动创建的对比”如果你要手动构建这些 CRUD 功能,你需要一步步创建 controller actions、routes、model(包含 attributes)、migrations 和 view 模板。Scaffolding 自动化了整个过程,立即提供了一个功能齐全的起点。这对于以下方面非常宝贵:
- 快速原型开发 (Rapid Prototyping): 快速构建功能用于演示或初步用户测试。
- 学习 Rails: 理解 MVC 的不同组件如何交互以及 Rails 约定如何应用。
- 简单资源管理 (Simple Resource Management): 对于简单的数据模型,Scaffolding 可以直接提供所需的大部分功能。
然而,scaffold 生成的代码仅仅是一个起点。对于复杂的应用或具有独特逻辑的功能,你通常需要显著自定义甚至替换生成的代码部分以满足你的特定要求。不要害怕修改它!
实际应用场景
Section titled “实际应用场景”想象你正在构建一个简单的项目管理工具。你可以 scaffolding 一个 Task 资源,包含 name:string description:text due_date:date completed:boolean 等 attributes。这将立即为你提供一个 Web 界面来创建、查看、更新和删除 tasks,使你能够接下来专注于添加更高级的功能。