首先得了解 MVC 是什么东西,即 M(Model,模型)V(View,视图)C(Controller,控制器)。接下来从一个完整的 Ruby on Rails(ror) 项目流程来了解三者之间的关系。
请求到达控制器:用户发送 HTTP 请求,请求被路由系统分发给相应的控制器动作。
控制器处理请求:控制器接收请求,可能会从模型中获取数据,或者对模型进行操作。
数据传递给视图:控制器通过实例变量将获取到的数据传递给视图。
视图渲染响应:视图使用嵌入 Ruby 代码的 ERB 模板,将控制器传递的数据渲染成最终的 HTML 响应。
响应返回给用户:最终生成的 HTML 响应由 Rails 框架发送给用户的浏览器,用户在浏览器中看到相应的页面或者数据。
由上面可知,模型负责和数据库做对接操作,控制器则负责从模型当中获取需要数据,视图则是负责对拿到的数据进行渲染。
在 Ruby on Rails 中,console(控制台)指的是一个命令行工具,通常称为 Rails 控制台或 Rails 命令行控制台。它提供了一个交互式的环境,允许开发者在不启动完整的 Rails 应用程序的情况下,执行 Ruby 代码和与应用程序进行交互。
rails new <PROJECT-NAME>
bin/rails server
rails server
bin/rails generate controller <CONTROLLER-NAME> index --skip-routes
bin/rails generate model <MODEL-NAME> <DB-FIELD>
bin/rails console
首先运行
rails new blog
来创建一个名为 blog
的 ror 项目,接下去进入到 blog 目录 cd blog
。如果成功的话,运行 bin/rails server
之后,就可以访问到 http://localhost:3000/
。
博客文章至少需要两个部分的内容一个是标题 title
还有内容主题 body
。我们来创建有这两个部分的 model
, 运行
bin/rails generate model Article title:string body:text
将创建一个 migrate文件
,在 /db/migrate
下面。这个文件里的内容为
# 定义了一个名为 CreateArticles 的 Ruby 类,它继承自 ActiveRecord::Migration[7.1]。在 Rails 中,迁移类用于修改数据库结构,继承自 ActiveRecord::Migration 提供了一系列用于定义数据库迁移的方法和规范
class CreateArticles < ActiveRecord::Migration[7.1]
# change 方法是 ActiveRecord::Migration 提供的一个特殊方法,用于定义要执行的数据库操作。在这个迁移中,change 方法内部包含了创建名为 articles 的新表格的指令
def change
# create_table 是 ActiveRecord::Migration 提供的方法之一,用于在数据库中创建新的表格。在这里,我们创建了一个名为 articles 的表格,do |t| 部分是一个代码块,用于定义表格的各个列和其属性
create_table :articles do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
这段迁移代码的作用是创建一个名为 articles
的数据库表格,该表格具有 title
(字符串类型)和 body
(文本类型)两列,以及自动生成的 timestamps
列(用于记录创建和更新时间)。
博客项目肯定需要有增删改查操作,即增加文章,删除文章,修改文章,查找文章。将这四个功能写入到 app/controllers/articles_controller.rb
当中,就是
# 这行代码的核心作用是定义一个名为 ArticlesController 的控制器类,并使其继承自 ApplicationController,以便在 Rails 应用中处理和管理与文章相关的逻辑和请求
class ArticlesController < ApplicationController
# 获取所有文章并将它们存储在实例变量 @articles 中,以便在视图中显示所有文章列表
def index
@articles = Article.all
end
# 根据传入的 params[:id] 查找特定 ID 的文章,并将其存储在实例变量 `@article` 中,以便在显示单个文章的视图中使用
def show
@article = Article.find(params[:id])
end
# 创建一个新的空白文章对象 `@article`,用于在创建新文章的表单页面中使用
def new
@article = Article.new
end
# 根据从表单收集的 article_params 创建新的文章对象,并尝试保存到数据库。如果保存成功,重定向到新创建的文章页面;如果保存失败,则重新渲染创建文章的表单页面,并返回状态码 422 Unprocessable Entity
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
# 根据传入的 params[:id] 查找要编辑的文章,并将其存储在实例变量 `@article` 中,以便在编辑文章的表单页面中使用
def edit
@article = Article.find(params[:id])
end
# 根据传入的 params[:id] 查找要更新的文章,并根据从表单收集的 article_params 尝试更新。如果更新成功,则重定向到更新后的文章页面;如果更新失败,则重新渲染编辑文章的表单页面,并返回状态码 422 Unprocessable Entity
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render :edit, status: :unprocessable_entity
end
end
# 根据传入的 params[:id] 查找要删除的文章,并将其从数据库中删除。完成后重定向到根路径 (root_path),并返回状态码 303 See Other
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to root_path, status: :see_other
end
# 定义了一个私有方法 article_params,用于过滤和允许 params 中的 article 参数,防止不必要的数据传递和安全问题
private
def article_params
params.require(:article).permit(:title, :body)
end
end
接下来就是视图部分,视图有四个文件 edit.html.erb
, index.html.erb
, new.html.erb
, show.html.erb
, 分别对应着编辑页面,首页,新建页面,展示页面。
edit.html.erb
用于编辑现有的文章。它预填充了现有文章的标题和正文内容,允许用户对这些内容进行修改,并提交表单以更新文章信息。index.html.erb
用于展示文章列表和提供创建新文章的入口。new.html.erb
用于创建新的文章。它包含了一个空的文章表单,用户可以填写标题和正文内容,并提交表单以创建新的文章。show.html.erb
用于展示单篇文章的详细内容。它显示了文章的标题和正文,并提供了编辑和删除操作的链接。接下来我将展示这四个部分的代码
# blog/models/views/articles/edit.html.erb
<h1>Edit Article</h1>
# 使用 form_with 方法生成一个表单,该表单与 @article 实例相关联,表示该表单用于编辑现有的文章对象
<%= form_with model: @article do |form| %>
# 在表单中显示文章标题的输入字段。使用 form.label 和 form.text_field 方法分别生成标题字段的标签和文本输入框。如果 @article 的标题验证失败(例如长度限制等),则会显示相应的错误消息(通过@article.errors.full_messages_for(:title) 迭代输出每个错误消息)
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
<% @article.errors.full_messages_for(:title).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
# 在表单中显示文章正文的文本区域字段。类似于标题字段,使用 form.label 和 form.text_area 方法生成正文字段的标签和文本区域。如果 @article 的正文验证失败,将会显示相应的错误消息
<div>
<%= form.label :body %><br>
<%= form.text_area :body %><br>
<% @article.errors.full_messages_for(:body).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
# 显示一个提交按钮,允许用户提交表单数据以更新文章的标题和正文内容
<div>
<%= form.submit %>
</div>
<% end %>
# blog/models/views/articles/index.html.erb
<h1>Articles</h1>
# 使用 <ul> 和 <li> 标签显示文章的列表。通过 <% @articles.each do |article| %> 循环遍历 @articles 实例变量中的每篇文章对象,对于每篇文章,使用 link_to 方法生成一个链接,链接到文章的详情页面(article_path(article)),显示文章的标题 article.title
<ul>
<% @articles.each do |article| %>
<li>
<%= link_to article.title, article %>
</li>
<% end %>
</ul>
<%= link_to "New Article", new_article_path %>
# blog/models/views/articles/new.html.erb
<h1>New Article</h1>
# 使用 `form_with` 方法生成一个表单,该表单与 `@article` 实例相关联,表示该表单用于创建一个新的文章对象
<%= form_with model: @article do |form| %>
# 在表单中显示文章标题的输入字段。使用 form.label 和 form.text_field 方法分别生成标题字段的标签和文本输入框。如果 @article 的标题验证失败(例如长度限制等),则会显示相应的错误消息(通过 @article.errors.full_messages_for(:title) 迭代输出每个错误消息)
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
<% @article.errors.full_messages_for(:title).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
# 在表单中显示文章正文的文本区域字段。类似于标题字段,使用 form.label 和 form.text_area 方法生成正文字段的标签和文本区域。如果 @article 的正文验证失败,将会显示相应的错误消息
<div>
<%= form.label :body %><br>
<%= form.text_area :body %><br>
<% @article.errors.full_messages_for(:body).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
# 表单提交
<div>
<%= form.submit %>
</div>
<% end %>
# blog/models/views/articles/show.html.erb
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
# 显示两个操作链接,一个是 "Edit" 编辑链接,另一个是 "Destroy" 删除链接
<ul>
# 使用 link_to 方法生成一个编辑链接,链接到编辑文章页面 (edit_article_path(@article))。用户点击这个链接可以进入编辑该文章的页面
<li><%= link_to "Edit", edit_article_path(@article) %></li>
# 使用 link_to 方法生成一个删除链接,链接到删除文章的路由路径 (article_path(@article))。同时,使用 data 选项传递了 turbo_method: :delete 表示这是一个通过 Turbo 框架发送的删除请求,并提供了 turbo_confirm 提示用户确认删除操作
<li><%= link_to "Destroy", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "Are you sure?"
} %></li>
</ul>
当然这些代码还有很多不足,包括但不限于对于 Controller
的 destroy
部分,并没有添加如果操作失败的的错误打印等。