要求
使用的工具:Rails 6.1.3, Ruby 3
什么是表单?
网络表单不是Rails的概念,表单是一种向服务器发送数据的方式。有许多其他的方式来实现这一点,但使用<form> 标签意味着使用标准的方式来发送数据。它意味着出色的设备支持、出色的可访问性、浏览器支持等等。
这很有效。没有Rails。不需要任何JavaScript。浏览器本身就能够向一个(其他)URL提交数据。
那么,为什么还需要Rails呢?
乍一看,我们不需要Rails来向服务器发送任何数据。
但是,为了避免多余的、容易出错的复制/粘贴,Rails自带的帮助器会自动生成上述表单,再加上.NET技术,就可以把数据发送到服务器。
- 为了安全起见,还要加上 authenticity_token。
- 一致的类和ID。
- 一致的 "名称 "标签
- 一旦按下提交按钮,就可以禁用它(通过名为Rails-ujs的JavaScript库)。
- 其他好东西。
Rails有一些帮助工具来构建表单。
- form_for (softly deprecated)
- form_tag (已被弃用)
- form_with (新标准)
你可能会在旧的 gem 或 Rails 项目中遇到form_for和form_tag,但form_with现在是新的标准。因此,对于任何新项目,你唯一需要关心的助手就是form_with。
现在让我们看看Rails是如何处理上面这个简单的表单的。
<# This is what you write in your Rails template file #>
<%= form_with scope: "book", url: "/books" do |form| %>
<%= form.text_field :title %>
<%= form.submit "Create" %>
<% end %>
<# This is the generated HTML you can view in your browser #>
<form class="new_book" id="new_book" action="/books" method="post">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="…">
<input type="text" name="book[title]" id="book_title">
<input type="submit" name="commit" value="Create" data-disable-with="Create">
</form>
从这里你可以注意到。
- Rails "强制 "使用标准的utf8编码,以便在服务器端正确解码你的表单字段。
- 默认使用的REST方法是POST,(表单标签中的属性
method="post")。 - 为了安全起见,有一个隐藏的认证令牌字段。
- 文本字段的 "name "和 "id "已经为你写好了。
- 提交字段有一个名字("commit"),以便处理有多个提交按钮的情况。
- 提交字段已经有一个 "data-disable-with",它将被Rails-ujs使用,一旦按下提交按钮就会失效。
- 避免多次提交是必要的,想想你的客户按了 "pay "按钮的情况,这个用户可能不想多次付款......
在没有Rails助手的情况下使用表单是很乏味和危险的。
教程从头开始
$> rails new myform --minimal
$> cd myform
在app/controllers/welcome_controller.rb内部
class WelcomeController < ApplicationController
# welcome_path GET /welcome
# root_path GET /
def index
end
# update_book_path POST /welcome/update_book
def update_book
end
end
在app/views/welcome/index.html.erb里面
<h1>Welcome ! This is a tutorial about Rails forms</h1>
<%= form_with scope: "book", url: update_book_path, method: :put do |form| %>
<%= form.text_field :title %>
<%= form.submit "Create" %>
<% end %>
在config/routes.rb里面
Rails.application.routes.draw do
get "/welcome", to: "welcome#index"
put "/welcome/update_book", to: "welcome#update_book", as: 'update_book'
root "welcome#index"
end
现在运行
$> bin/rails server
并打开你的浏览器,http://localhost:3000

渲染的表单
当你在Rails环境中处理表单时,我建议总是打开Chrome开发工具控制台,以查看DOM树中发生的事情。
也许你会看到一些惊喜。在这里,我们可以看到的是 。
-
与本教程的第一部分相比,这个Rails版本以另一种方式处理UTF8。我不知道这是否与Rails或Rails-ujs版本有关,但注意到这一点是有点好笑的。对于开发者来说,这并没有改变什么,但同样,在没有Rails助手的情况下管理表单是一种负担。
-
请注意
app/views/welcome/index.html.erb里面的method: :put。为了配合routes.rb里面的VERB。最后,这个put方法是在表单的一个隐藏字段里面。可能是因为大多数浏览器只知道GET数据和POST数据,但无法使用其他动词,如DELETE或PUT或PATCH。 -
其他一切行为都如上所述。
向服务器发送数据
在Chrome开发工具中,打开网络标签。然后,在表单里面输入任何书的名字,并点击按钮提交表单。

Chrome开发工具的表单提交
我们可以看到,有4个参数被发送到了服务器:_method, authenticity_token, book[title], commit
现在打开你的终端。

终端参数
只有3个参数,_method已经消失了,因为Rails用它来计算哪个控制器的方法是目标。
所以Rails已经为我们调用了正确的方法:WelcomeController#update_book。
在 "params "对象中传递3个参数(可以被控制器的任何方法使用)。
- authenticity_token (我从来没有用过这种情况)
- commit (如果我们有多个提交按钮,但这不是我们的情况)
- book (有用的有效载荷)
用默认值预先填入表单
用 "Rails方式 "来预填充表单的值是使用一个Model,它是一个与数据库混合的对象。
如果你已经有一些编码经验,这听起来是错误的。事实上,许多教程并不建议这样做。相反,你可以使用一个普通的Model,没有映射到数据库,名为 "表单对象"。我很快会就这个问题单独写一篇博客文章,因为这个教程已经够厚了:)
现在你只需要知道,在Rails中这样做是可能的,而且它为Rails表单带来了更大的魔力(即更短的代码)。
解压缩发送的数据
在app/controllers/welcome_controller.rb里面。
class WelcomeController < ApplicationController
# welcome_path GET /welcome
# root_path GET /
def index
end
# update_book_path POST /welcome/update_book
def update_book
p ''
p '--- extracted params are ---'
p book_params # will output {"title" => "gatsby"}
p ''
end
def book_params
params.require(:book).permit(:title).to_h
end
end
现在把 "gatsby "放到表单中,然后提交按钮。
下面是控制台中打印的内容。

解压后的表单
正如你所看到的,这次并没有什么神奇的地方。更糟的是,你必须处理 "强参数",这是为了安全起见,但却很烦人,因为它们增加了很多冗长的语言。
结论
-
虽然 "web表单 "是一个众所周知的、古老的、成熟的web标准,与Rails无关,但在Rails环境中不建议在没有任何辅助工具的情况下使用它。视图和控制器一起工作以确保安全、编码和路由。
-
"form_with "是新的统一标准。"form_tag "和 "form_for "已被废弃--但出于兼容性的考虑而保留。
-
Rails根据 "scope: "键自动映射字段名,另一种方法是用 "model: "键注入一个 "表单对象",我们将在后面看到如何操作。
-
另一种方法没有什么神奇之处:一旦表单被提交,在处理提交的数据之前,你必须在控制器中逐一提取、授权和读取参数。