Skip to content

Ruby on Rails - 视图

在 Rails 中,视图(Views)是 MVC 中的 ‘V’。它们负责生成用户界面,通常是 HTML。Rails 视图最常使用 ERB (Embedded Ruby) 编写,它允许您在 HTML 文档中嵌入 Ruby 代码。数据通过实例变量(instance variables)从控制器(controllers)传递到视图(views)。

当您生成一个控制器时,Rails 通常会在 app/views/ 中创建一个相应的子目录。例如,如果您有一个 BooksController,其视图文件将位于 app/views/books/。每个公共的控制器动作(action)通常都有一个对应的视图模板文件(例如,index 动作对应 index.html.erb)。

让我们为 BooksController 中定义的动作创建视图文件。在构建这些视图时,请思考数据如何从控制器流向视图,以及 Rails 助手方法(helpers)如何简化 HTML 生成。

为 index 动作创建视图(图书列表)

Section titled “为 index 动作创建视图(图书列表)”

在 app/views/books/ 中创建一个名为 index.html.erb 的文件。这个视图将显示所有图书的列表。如果目前没有图书,它将显示一条消息。

将以下 ERB 代码添加到 app/views/books/index.html.erb 文件中:

<h1>Available Books</h1>
<% if @books.empty? %>
<p>There are no books currently in the system.</p>
<% else %>
<p>These are the current books in our system:</p>
<ul id="books">
<% @books.each do |book| %>
<li id="book_<%= book.id %>">
<%= link_to book.title, book_path(book) %>
<%= link_to 'Edit', edit_book_path(book) %>
<%= link_to 'Delete', book_path(book), data: { turbo_method: :delete, turbo_confirm: 'Are you sure you want to delete this item?' } %>
</li>
<% end %>
</ul>
<% end %>
<p><%= link_to "Add new Book", new_book_path %></p>

解释:

  • @books.empty? 检查实例变量 @books(在控制器的 index 动作中设置)是否为空。
  • @books.each do |book| ... end 遍历每本图书。
  • link_to book.title, book_path(book) 创建一个指向图书详情页的链接。book_path(book) 是一个路由助手方法(route helper),它为给定的 book 对象生成 books#show 对应的 URL。
  • edit_book_path(book) 链接到图书的编辑页。
  • link_to 'Delete', book_path(book), data: { turbo_method: :delete, turbo_confirm: 'Are you sure you want to delete this item?' } 创建一个删除链接。现代 Rails 使用 Turbo 来处理此类动作,需要设置 turbo_method: :delete。turbo_confirm 提供一个 JavaScript 确认对话框。
  • new_book_path 链接到创建新图书的页面。

如果您在浏览器中访问 /books 并且尚未添加任何图书,您将看到消息 “There are no books currently in the system.” 以及一个添加新图书的链接。

为 new 动作创建视图(新图书表单)

Section titled “为 new 动作创建视图(新图书表单)”

要添加图书,我们需要一个表单(form)。在 app/views/books/ 中创建 new.html.erb 文件。

将以下代码添加到 app/views/books/new.html.erb 文件中:

<h1>Add New Book</h1>
<%= form_with(model: @book, local: true) do |form| %>
<% if @book.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@book.errors.count, "error") %> prohibited this book from being saved:</h2>
<ul>
<% @book.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :price %><br>
<%= form.number_field :price, step: 0.01 %>
</p>
<p>
<%= form.label :subject_id %><br>
<%= form.collection_select :subject_id, @subjects, :id, :name, prompt: 'Select a Subject' %>
</p>
<p>
<%= form.label :description %><br>
<%= form.text_area :description %>
</p>
<p>
<%= form.submit 'Create Book' %>
</p>
<% end %>
<%= link_to 'Back to List', books_path %>

解释:

  • form_with(model: @book, local: true) do |form| ... end 是现代 Rails 的表单构建器(form builder)。model: @book 告诉它为 @book 对象(在控制器的 new 动作中实例化)构建一个表单。local: true 确保默认情况下执行标准的 HTML 表单提交,而不是 AJAX 提交。
  • 错误显示块会在保存失败时显示验证错误(validation errors)。
  • form.label、form.text_field、form.number_field、form.collection_select、form.text_area 和 form.submit 都是表单助手方法(form helper methods),用于生成相应的 HTML 表单元素。
  • collection_select 用于 ‘Subject’ 下拉列表,由 @subjects(从控制器传递过来)填充数据。
  • 导航到 /books/new 将显示这个表单。

填写完表单并点击 “Create Book” 后,数据将提交到 create 动作。如果成功,您将被重定向到图书列表页,现在列表将显示新添加的图书。如果存在验证错误,new 页面将重新渲染并显示错误消息。

为 show 动作创建视图(显示图书详情)

Section titled “为 show 动作创建视图(显示图书详情)”

此视图显示特定图书的详细信息。在 app/views/books/ 中创建 show.html.erb 文件。

添加以下代码:

<h1><%= @book.title %></h1>
<p>
**Price:** <%= number_to_currency(@book.price) %><br>
**Subject:** <%= @book.subject&.name || 'N/A' %><br>
**Created Date:** <%= @book.created_at.strftime("%B %d, %Y") %><br>
</p>
<p>
**Description:**<br>
<%= @book.description %>
</p>
<hr>
<%= link_to 'Edit this Book', edit_book_path(@book) %> |
<%= link_to 'Back to List', books_path %>

解释:

  • @book.title、@book.price 等显示控制器 show 动作找到的图书的属性。
  • number_to_currency 是一个 Rails 助手方法(helper),用于格式化价格。
  • @book.subject&.name 使用 Ruby 的安全导航操作符(safe navigation operator &.) 来防止图书没有关联主题时发生错误。然后它会显示主题名称或 ‘N/A’。
  • strftime 格式化 created_at 时间戳。
  • 提供了编辑图书或返回列表的链接。

为 edit 动作创建视图(编辑图书表单)

Section titled “为 edit 动作创建视图(编辑图书表单)”

这个表单类似于 new 表单,但会预填充图书的现有数据,并提交到 update 动作。在 app/views/books/ 中创建 edit.html.erb 文件。

添加以下代码:

<h1>Edit Book Details</h1>
<%= form_with(model: @book, local: true) do |form| %>
<% if @book.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@book.errors.count, "error") %> prohibited this book from being saved:</h2>
<ul>
<% @book.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :price %><br>
<%= form.number_field :price, step: 0.01 %>
</p>
<p>
<%= form.label :subject_id %><br>
<%= form.collection_select :subject_id, @subjects, :id, :name, prompt: 'Select a Subject' %>
</p>
<p>
<%= form.label :description %><br>
<%= form.text_area :description %>
</p>
<p>
<%= form.submit 'Update Book' %>
</p>
<% end %>
<%= link_to 'Show this Book', book_path(@book) %> |
<%= link_to 'Back to List', books_path %>

这个表单与 new 表单几乎完全相同。form_with(model: @book) 会自动设置表单的提交动作到 PATCH /books/:id(即 update 动作),并且因为 @book 是一个已存在的记录,会自动预填充字段。

delete(或 destroy)动作通常没有自己的视图。删除记录后,控制器通常会重定向到 index 页面。删除功能是通过 index 页面(或 show 页面)上的链接触发的,如前面所示。

为 show_subjects 动作创建视图(自定义动作示例)

Section titled “为 show_subjects 动作创建视图(自定义动作示例)”

如果您在 BooksController 中有一个像 show_subjects 这样的自定义动作(也许是为了按特定主题列出图书,尽管这种逻辑通常应放在 SubjectsController 中),您需要创建 app/views/books/show_subjects.html.erb 文件。

假设 @subject 及其关联的 @books 已在控制器中设置:

<h1>Books in Subject: <%= @subject.name %></h1>
<% if @subject.books.empty? %>
<p>No books found for this subject.</p>
<% else %>
<ul>
<% @subject.books.each do |book| %>
<li><%= link_to book.title, book_path(book) %></li>
<% end %>
</ul>
<% end %>
<%= link_to 'Back to All Books', books_path %>

这演示了如何遍历与特定主题关联的图书。您还需要为此设置相应的路由(route)和控制器动作(controller action)。例如,在 show.html.erb 中,您可以在主题名称处链接到此处: **Subject:** <%= link_to @book.subject&.name, subject_books_path(@book.subject) if @book.subject %>(假设存在一个路由助手方法 subject_books_path)。

要在主图书列表页(app/views/books/index.html.erb)上列出所有主题,您可以添加(假设 @all_subjects 从控制器传递过来):

<h2>Browse by Subject</h2>
<ul id="subjects">
<% @all_subjects.each do |subject| %>
<li><%= link_to subject.name, books_path(subject_id: subject.id) %></li>
<%# Or link_to subject.name, subject_path(subject) if you have a subjects#show page %>
<% end %>
</ul>

这个例子展示了一种使用查询参数(query parameters)按主题过滤图书列表的方法。然后控制器的 index 动作需要处理这个参数。

有了处理逻辑的控制器和展示数据的视图,您就拥有了一个 Web 应用的核心。下一章将探讨布局(Layouts),它为多个视图提供了统一的结构和外观。