Rails表单验证失败后显示无效URL的原因及标准解决方案
根本原因
这是Rails的标准设计行为,不存在配置遗漏:
/admin/users/20/edit页面的默认表单提交地址为/admin/users/20,提交方法为PATCH,符合REST路由规范- 验证失败后执行
render "edit"仅会将edit模板的HTML内容返回给当前的PATCH请求,不会触发浏览器跳转,因此浏览器地址栏会停留在表单提交的目标地址/admin/users/20,不会回退到之前的edit页面地址。
业界通用解决方案
按改动成本从低到高排序,可按需选择:
- 方案1:使用Rails7+默认Turbo机制
只要项目没有全局关闭Turbo,表单提交默认走异步请求,验证失败后只会局部更新页面内容,不会修改浏览器地址栏,不需要额外改代码就能规避该问题。 - 方案2:修改表单提交地址+补充路由规则
该实现非常简单,不属于拙劣方案:- 路由补充PATCH规则:
namespace :admin do resources :users do member do patch :edit # 给edit路径新增PATCH动词支持 end end end- 修改edit页面的表单,指定提交地址:
改动后表单提交目标就是带<%# 原有form_with仅需新增url参数 %> <%= form_with model: @user, url: edit_admin_user_path(@user) do |form| %>/edit后缀的地址,验证失败后地址栏保持不变,不需要修改其他业务逻辑。 - 方案3:兼容PRG(Post/Redirect/Get)模式
验证失败时不直接渲染模板,而是重定向回edit页面,同时用flash暂存用户输入和错误信息:
然后在edit动作里恢复暂存的内容:def update if @user.update(user_params) redirect_to(some_path, notice: 'Record updated') else flash[:user_params] = user_params flash[:errors] = @user.errors.full_messages redirect_to edit_admin_user_path(@user) end end
该方案完全符合HTTP规范,地址栏全程都是合法的GET路径,唯一缺点是flash存储容量有限,不适合带大量附件的表单。def edit # 原有逻辑不变,补充下面的内容 if flash[:user_params] @user.assign_attributes(flash[:user_params]) flash[:errors]&.each { |msg| @user.errors.add(:base, msg) } end end - 方案4:修改内部gem逻辑
仅需要给gem加一层路由判断:如果读取到的HTTP_REFERER对应的控制器动作是admin/users#update,就自动跳转对应资源的edit页面即可,改动量并不大。
内容的提问来源于stack exchange,提问作者iconoclast
相关产品推荐
相关产品推荐

