如何拆分Devise注册编辑页为多表单并隔离current_password错误?
问题
Devise默认的registrations/edit.html.erb允许用户编辑邮箱、密码(已设置时),且操作需填写current_password。现在要把这个表单拆成更新邮箱、更新密码、删除账户三个独立表单,但拆分后发现两个表单都依赖同一个resource(即current_user),且都包含current_password字段,同时用Devise的update_with_password方法验证身份。当current_password验证失败时,错误会在两个表单的该字段上同时显示,误导用户。请问如何让错误仅在对应的表单中展示?
解决方案
核心思路是区分不同表单的提交动作,把错误信息绑定到对应表单的专属标识上,避免全局共享错误。下面提供两种实用实现方式:
方法一:给字段加前缀区分,控制器定向处理错误
1. 修改视图表单
- 更新邮箱的表单里,给
current_password字段加专属前缀,同时只显示当前表单对应的错误:<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= f.email_field :email, placeholder: "新邮箱" %> <%= password_field_tag "email_update[current_password]", nil, placeholder: "请输入当前密码" %> <%= f.submit "更新邮箱" %> <!-- 只显示邮箱表单的current_password错误 --> <% if resource.errors[:email_update_current_password].present? %> <div class="error"><%= resource.errors[:email_update_current_password].first %></div> <% end %> <% end %> - 更新密码的表单同理,给
current_password加另一个前缀:<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= f.password_field :password, placeholder: "新密码" %> <%= f.password_field :password_confirmation, placeholder: "确认新密码" %> <%= password_field_tag "password_update[current_password]", nil, placeholder: "请输入当前密码" %> <%= f.submit "更新密码" %> <!-- 只显示密码表单的current_password错误 --> <% if resource.errors[:password_update_current_password].present? %> <div class="error"><%= resource.errors[:password_update_current_password].first %></div> <% end %> <% end %>
2. 重写Devise的Registrations控制器
在app/controllers/users/registrations_controller.rb里重写update方法,区分不同表单的提交,把错误信息转移到对应的自定义字段:
class Users::RegistrationsController < Devise::RegistrationsController def update self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key) # 判断是更新邮箱还是密码的请求 if params[:email_update].present? email_params = params.require(:user).permit(:email) current_password = params[:email_update][:current_password] if resource.update_with_password(email_params.merge(current_password: current_password)) set_flash_message :notice, :updated if is_flashing_format? bypass_sign_in resource, scope: resource_name redirect_to after_update_path_for(resource) else # 把current_password的错误转移到自定义字段 resource.errors.add(:email_update_current_password, resource.errors.delete(:current_password).first) if resource.errors[:current_password].present? clean_up_passwords resource set_minimum_password_length render :edit end elsif params[:password_update].present? password_params = params.require(:user).permit(:password, :password_confirmation) current_password = params[:password_update][:current_password] if resource.update_with_password(password_params.merge(current_password: current_password)) set_flash_message :notice, :updated if is_flashing_format? bypass_sign_in resource, scope: resource_name redirect_to after_update_path_for(resource) else resource.errors.add(:password_update_current_password, resource.errors.delete(:current_password).first) if resource.errors[:current_password].present? clean_up_passwords resource set_minimum_password_length render :edit end else super end end end
方法二:用独立路由区分动作,绑定错误标识
1. 添加自定义路由
在config/routes.rb里给Registrations控制器新增两个专属路由:
devise_for :users, controllers: { registrations: "users/registrations" } do put 'update_email', to: 'users/registrations#update_email' put 'update_password', to: 'users/registrations#update_password' end
2. 拆分控制器动作
在app/controllers/users/registrations_controller.rb里新增两个独立的处理方法,给不同表单的错误加专属标识:
class Users::RegistrationsController < Devise::RegistrationsController # 处理更新邮箱请求 def update_email self.resource = current_user email_params = params.require(:user).permit(:email) current_password = params[:current_password] if resource.update_with_password(email_params.merge(current_password: current_password)) set_flash_message :notice, :updated if is_flashing_format? bypass_sign_in resource redirect_to edit_user_registration_path else # 标记是邮箱表单的错误 @email_update_error = true clean_up_passwords resource render :edit end end # 处理更新密码请求 def update_password self.resource = current_user password_params = params.require(:user).permit(:password, :password_confirmation) current_password = params[:current_password] if resource.update_with_password(password_params.merge(current_password: current_password)) set_flash_message :notice, :updated if is_flashing_format? bypass_sign_in resource redirect_to edit_user_registration_path else # 标记是密码表单的错误 @password_update_error = true clean_up_passwords resource render :edit end end end
3. 修改视图表单
- 更新邮箱表单指向专属路由,只在邮箱错误标识存在时显示错误:
<%= form_for(resource, as: resource_name, url: update_email_path, html: { method: :put }) do |f| %> <%= f.email_field :email, placeholder: "新邮箱" %> <%= f.password_field :current_password, placeholder: "请输入当前密码" %> <%= f.submit "更新邮箱" %> <% if @email_update_error && resource.errors[:current_password].present? %> <div class="error"><%= resource.errors[:current_password].first %></div> <% end %> <% end %> - 更新密码表单同理,指向密码专属路由:
<%= form_for(resource, as: resource_name, url: update_password_path, html: { method: :put }) do |f| %> <%= f.password_field :password, placeholder: "新密码" %> <%= f.password_field :password_confirmation, placeholder: "确认新密码" %> <%= f.password_field :current_password, placeholder: "请输入当前密码" %> <%= f.submit "更新密码" %> <% if @password_update_error && resource.errors[:current_password].present? %> <div class="error"><%= resource.errors[:current_password].first %></div> <% end %> <% end %>
内容的提问来源于stack exchange,提问作者Sumak
相关产品推荐
相关产品推荐

