RoR自定义Devise会话控制器 渲染登录页触发表单nil值报错咨询
报错核心原因
这个报错的触发逻辑完全和你代码中的执行路径对应:
- Devise登录页的表单使用
form_for(resource, ...)渲染,要求第一个参数必须是有效的模型实例,不能为nil。正常通过GET请求访问登录页时,Devise会在newaction中自动通过build_resource({})初始化一个空的User对象赋值给resource变量,供视图渲染表单使用。 - 你在
createaction中实现了自定义的用户邮箱查询逻辑:self.resource = User.find_by(email: params[:user][:email]),当查询不到对应用户时,resource变量会被直接赋值为nil。 - 结合Rails的render机制特性:执行
render 'new'时仅会加载new对应的视图模板,不会触发newaction的代码执行流程,所有视图可访问的变量都来自当前create action的上下文。此时视图拿到nil作为表单参数,直接触发“First argument in form cannot contain nil or be empty”报错。
修复方案
在用户不存在的分支中,手动对齐Devise的资源初始化逻辑,给视图传入有效的resource对象,修改app/controllers/users/sessions_controller.rb中create方法的空用户分支:
def create self.resource = User.find_by(email: params[:user][:email]) if resource.nil? # 调用Devise内置方法初始化空的认证模型实例 self.resource = build_resource({}) # 回填用户已输入的邮箱值,避免用户重复输入 resource.email = params[:user][:email] # 给模型添加校验错误,页面可自动同步展示提示 resource.errors.add(:email, "未查询到匹配的注册用户,请核对邮箱后重试") render 'new' else # 原有登录、OTP发送逻辑保持不变 set_flash_message!(:notice, :signed_in) sign_in(resource_name, resource) yield resource if block_given? resource.update(language: Constant::SUPPORTED_LANGUAGES.key(params['locale'])) if params['locale'].present? resource.send_otp(force_expire: true) if resource.email_verified? respond_with resource, location: after_sign_in_path_for(resource) flash.delete(:notice) end end
注意事项
- 优先使用Devise提供的
build_resource方法初始化资源,不要直接写User.new,前者会自动适配你当前配置的认证模型,后续调整认证逻辑时不需要同步修改控制器代码。 - 你引入的
ExpireExistingToken关注点模块的before_action仅挂载在newaction上,render时不会触发该前置逻辑,不会造成token清理的逻辑冲突。 - 因为你是实现仅邮箱登录的流程,原warden带密码校验的逻辑注释是合理的,不需要额外恢复。
内容的提问来源于stack exchange,提问作者Anisha
相关产品推荐
相关产品推荐

