Rails 7 注册表单不显示错误消息

use*_*916 4 ruby ruby-on-rails

我正在遵循Rails 教程,在注册表单中,如果提交了无效的用户信息,则应该重新呈现注册页面并显示错误消息,但实际上并没有。看来即使注册页面是由 渲染的"render \'new\'",传递给它的@user 也是空的。如何解决这个问题?

\n

请注意,本教程使用 Rails 6,但我实际上使用的是 Rails 7.0.2.3 和 Ruby 3.1.1。不确定这是否是原因。

\n

应用程序/控制器/user_controller.rb

\n
class UsersController < ApplicationController\n  def new\n    @user = User.new\n  end\n\n  def create\n    @user = User.new(user_params)\n    if @user.save\n    else\n      render \'new\'\n    end\n  end\n\n  def show\n    @user = User.find(params[:id])\n  end\n\n  private\n\n  def user_params\n    params.require(:user).permit(:name, :email, :password, :password_confirmation)\n  end\nend\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序/视图/用户/new.html.erb

\n
<% provide(:title, \'Sign up\') %>\n<h1>Sign up</h1>\n\n<div class="row">\n  <div class="col-md-6 col-md-offset-3">\n    <%= form_with(model: @user, local: true) do |f| %>\n      <%= render \'shared/error_messages\' %>\n      <%= f.label :name %>\n      <%= f.text_field :name, class: \'form-control\' %>\n      <%= f.label :email %>\n      <%= f.email_field :email, class: \'form-control\' %>\n      <%= f.label :password %>\n      <%= f.password_field :password, class: \'form-control\' %>\n      <%= f.label :password_confirmation, "Confirmation" %>\n      <%= f.password_field :password_confirmation, class: \'form-control\' %>\n      <%= f.submit "Create my account", class: "btn btn-primary" %>\n    <% end %>\n  </div>\n</div>\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序/视图/共享/_error_messages.html.erb

\n
<% if @user.errors.any? %>\n  <div id="error_explanation">\n    <div class="alert alert-danger">\n      The form contains <%= pluralize(@user.errors.count, "error") %>.\n    </div>\n    <ul>\n      <% @user.errors.full_messages.each do |msg| %>\n        <li><%= msg %></li>\n      <% end %>\n    </ul>\n  </div>\n<% end %>\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序/资产/样式表/custom.scss

\n
@import "bootstrap-sprockets";\n@import "bootstrap";\n\n/* variables */\n\n$gray-medium-light: #eaeaea;\n\n/* mixins */\n\n@mixin box_sizing {\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\n/* miscellaneous */\n\n.debug_dump {\n  clear: both;\n  float: left;\n  width: 100%;\n  margin-top: 45px;\n  @include box_sizing;\n}\n\n/* universal */\n\nbody {\n  padding-top: 60px;\n}\n\nsection {\n  overflow: auto;\n}\n\ntextarea {\n  resize: vertical;\n}\n\n.center {\n  text-align: center;\n\n  h1 {\n    margin-bottom: 10px;\n  }\n}\n\n/* typography */\n\nh1, h2, h3, h4, h5, h6 {\n  line-height: 1;\n}\n\nh1 {\n  font-size: 3em;\n  letter-spacing: -2px;\n  margin-bottom: 30px;\n  text-align: center;\n}\n\nh2 {\n  font-size: 1.2em;\n  letter-spacing: -1px;\n  margin-bottom: 30px;\n  text-align: center;\n  font-weight: normal;\n  color: $gray-light;\n}\n\np {\n  font-size: 1.1em;\n  line-height: 1.7em;\n}\n\n/* header */\n\n#logo {\n  float: left;\n  margin-right: 10px;\n  font-size: 1.7em;\n  color: white;\n  text-transform: uppercase;\n  letter-spacing: -1px;\n  padding-top: 9px;\n  font-weight: bold;\n\n  &:hover {\n    color: white;\n    text-decoration: none;\n  }\n}\n\n/* footer */\n\nfooter {\n  margin-top: 45px;\n  padding-top: 5px;\n  border-top: 1px solid $gray-medium-light;\n  color: $gray-light;\n\n  a {\n    color: $gray;\n\n    &:hover {\n      color: $gray-darker;\n    }\n  }\n\n  small {\n    float: left;\n  }\n\n  ul {\n    float: right;\n    list-style: none;\n\n    li {\n      float: left;\n      margin-left: 15px;\n    }\n  }\n}\n\n/* sidebar */\n\naside {\n  section.user_info {\n    margin-top: 20px;\n  }\n\n  section {\n    padding: 10px 0;\n    margin-top: 20px;\n\n    &:first-child {\n      border: 0;\n      padding-top: 0;\n    }\n\n    span {\n      display: block;\n      margin-bottom: 3px;\n      line-height: 1;\n    }\n\n    h1 {\n      font-size: 1.4em;\n      text-align: left;\n      letter-spacing: -1px;\n      margin-bottom: 3px;\n      margin-top: 0px;\n    }\n  }\n}\n\n.gravatar {\n  float: left;\n  margin-right: 10px;\n}\n\n.gravatar_edit {\n  margin-top: 15px;\n}\n\n/* forms */\n\ninput, textarea, select, .uneditable-input {\n  border: 1px solid #bbb;\n  width: 100%;\n  margin-bottom: 15px;\n  @include box_sizing;\n}\n\ninput {\n  height: auto !important;\n}\n\n#error_explanation {\n  color: red;\n  ul {\n    color: red;\n    margin: 0 0 30px 0;\n  }\n}\n\n.field_with_errors {\n  @extend .has-error;\n  .form-control {\n    color: $state-danger-text;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

单击表单提交按钮无效时 Rails 服务器的调试信息

\n
Started POST "/users" for ::1 at 2022-04-06 00:55:04\nProcessing by UsersController#create as TURBO_STREAM\n  Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"name"=>"foo123", "email"=>"foo123@asdf", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}\n  TRANSACTION (0.0ms)  begin transaction\n  \xe2\x86\xb3 app/controllers/users_controller.rb:8:in `create\'\n  User Exists? (0.1ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "foo123@asdf"], ["LIMIT", 1]]\n  \xe2\x86\xb3 app/controllers/users_controller.rb:8:in `create\'\n  TRANSACTION (0.0ms)  rollback transaction\n  \xe2\x86\xb3 app/controllers/users_controller.rb:8:in `create\'\n  Rendering layout layouts/application.html.erb\n  Rendering users/new.html.erb within layouts/application\n  Rendered shared/_error_messages.html.erb (Duration: 0.4ms | Allocations: 403)\n  Rendered users/new.html.erb within layouts/application (Duration: 1.9ms | Allocations: 1891)\n  Rendered layouts/_shim.html.erb (Duration: 0.0ms | Allocations: 15)\n  Rendered layouts/_header.html.erb (Duration: 0.1ms | Allocations: 78)\n  Rendered layouts/_footer.html.erb (Duration: 0.1ms | Allocations: 51)\n  Rendered layout layouts/application.html.erb (Duration: 9.5ms | Allocations: 8695)\nCompleted 200 OK in 213ms (Views: 9.8ms | ActiveRecord: 0.2ms | Allocations: 11628)\n
Run Code Online (Sandbox Code Playgroud)\n

Ale*_*lex 13

TURBO_STREAM在 Rails 7 中,表单默认提交。提交表单后,Turbo 预计会重定向,除非响应状态在 400-599 范围内。

render :new  # default status is 200
Run Code Online (Sandbox Code Playgroud)

状态代码 200 Turbo 在浏览器控制台中显示错误,并且页面不会重新呈现。

要使 Turbo 接受渲染的 html,请更改响应状态。默认值似乎是:unprocessable_entity(状态代码 422)

render :new, status: :unprocessable_entity
Run Code Online (Sandbox Code Playgroud)

https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission

https://github.com/hotwired/turbo/commit/4670f2b57c5d0246dfc0f6d10ff7d9a52a63fdca


更新:关于“内容类型”的注释。这适用于使用 Turbo 的默认表单提交。

在此设置中,Turbo期待 html 响应Content-Type: text/html;。@puerile 指出,在视图中省略.html扩展名也会破坏响应。

Rails 使用.html扩展名将响应内容类型设置为text/html. 当省略扩展名时,内容类型设置为,因为表单以TURBO_STREAMtext/vnd.turbo-stream.html形式提交,因为我们的响应没有 a它是错误的内容类型。<turbo-stream>

>> Mime[:turbo_stream].to_str
=> "text/vnd.turbo-stream.html"
Run Code Online (Sandbox Code Playgroud)

如果我们有一个 view views/users/new.erb,这将不起作用:

if @user.save
  redirect_to @user
else
  # NOTE: this will render `new.erb` and set 
  #       `Content-Type: text/vnd.turbo-stream.html` header;
  #       turbo is not happy.
  render :new, status: :unprocessable_entity
end
Run Code Online (Sandbox Code Playgroud)

要修复它,请使用respond_to方法:

respond_to do |format|
  if @user.save
    format.html { redirect_to @user }
  else
    # NOTE: this will render `new.erb` and set 
    #       `Content-Type: text/html` header;
    #       turbo is happy.
    format.html { render(:new, status: :unprocessable_entity) }
  end
end
Run Code Online (Sandbox Code Playgroud)

或手动设置内容类型:

if @user.save
  redirect_to @user
else
  render :new, status: :unprocessable_entity, content_type: "text/html"

  # NOTE: you can also set headers like this
  headers["Content-Type"] = "text/html"
end
Run Code Online (Sandbox Code Playgroud)

最后设置的一个警告是,布局也必须没有.html扩展名,否则,将在没有布局的情况下render :new渲染new.erb ,并且Turbo将不再满意。使用方法时这不是问题respond_to


https://api.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_to

  • 还通过 **Turbo** 发现,如果您生成视图并将其更改为“new.erb”(因为其他人这样做!),那么它也会崩溃,甚至不会显示错误消息如果你有`render :new, status: :unprocessable_entity`。因此最好在视图中保留“.html”,例如(“new.html.erb”) (2认同)

max*_*max 5

如果您查看日志,您可以看到 Rails 正在以 Turbo 流的形式获取 AJAX 请求:

Processing by UsersController#create as TURBO_STREAM
Run Code Online (Sandbox Code Playgroud)

应该在哪里读:

Processing by UsersController#create as HTML
Run Code Online (Sandbox Code Playgroud)

要禁用涡轮增压,您需要data-turbo="false"在表单上设置一个属性:

Processing by UsersController#create as TURBO_STREAM
Run Code Online (Sandbox Code Playgroud)

local: false选项仅适用于旧的 Rails UJS javascript 库,这是 Rails 7 之前的默认库。您还可以通过以下方式默认禁用 Turbo:

Processing by UsersController#create as HTML
Run Code Online (Sandbox Code Playgroud)

看:

https://turbo.hotwired.dev/handbook/drive#disabling-turbo-drive-on-specific-links-or-forms