【问题标题】:Rails validation is still firing despite unless option evaluating to true尽管选项评估为 true,但 Rails 验证仍在触发
【发布时间】:2014-02-10 23:52:37
【问题描述】:

我在我的应用中使用devise_invitable 来允许用户发送邀请。我意识到一个糟糕的情况,用户被邀请但忽略了邀请,然后返回应用程序自行注册。因为devise_invitable 通过使用为邀请提供的电子邮件地址创建一个新的user 来处理邀请,所以我对电子邮件字段的唯一性验证将导致 Rails 抱怨,告诉用户该电子邮件地址已被占用。

我正在尝试编写一些逻辑来处理这种情况。我看到了两条路径——要么想办法检测到这一点并销毁先前创建的用户并允许创建新用户,要么检测到用户被邀请并执行另一个流程。我决定实施第二个选项,因为如果可能的话,我仍想使用邀请。

我有限的经验让我质疑我所写的内容是否有效,但我实际上无法完全测试它,因为触发了电子邮件上的 Rails 验证。我已经确定 Devise 的 :validatable 模块处于非活动状态。我创建了一个(我认为)可以检测用户是否被邀请的方法,在这种情况下应该跳过唯一性验证。

#user.rb
...
validates :email, uniqueness: true, unless: :was_invited?

...
def was_invited?
  if self.invitation_sent_at.present? && self.sign_in_count == 0
    true
  else
    false
  end
end

FWIW,我最初是用速记写的,而不是打破 if/else,但我想非常明确地找出错误/失败。

希望一旦表单通过验证,create 操作将对用户的邀请状态进行一些检测,如果他们被邀请,则将他们重定向到accept_user_invitation_path。同样,我还无法实际测试它,因为我无法绕过验证。

#registrations_controller.rb
def create
  if User.find_by_email(params[:email])
    @existing_user = User.find_by_email(params[:email])
    @existing_user.save(validate: false)
    if @existing_user.was_invited?
      redirect_to accept_user_invitation_path(:invitation_token => @existing_user.invitation_token)
    end
  else
    super
  end
end

在绝望的努力中,您会看到我还添加了.save(validate: false) 以尝试将其短路,但它甚至没有走那么远。

如果我完全注释掉电子邮件验证,只是为了测试逻辑/流程的其余部分,我会收到一个 PG 错误,因为电子邮件地址上的索引而抱怨唯一性 - 我不想把这一切拆开只是为了测试这个方法。

我已经尝试了几个小时来解决这个问题,但我很茫然 - 感谢任何帮助。如果您想查看其他代码,请告诉我。

【问题讨论】:

  • 你确定if User.find_by_email(params[:email]) 是真的吗?似乎永远无法到达if 中的代码。您可以从您的日志中发布您的表单和提交的params 吗? (不应该是params[:user][:email]吗??)您在创建操作中所做的事情很奇怪:您正在获取现有用户,然后对其调用save,这是无用的,因为您没有更改任何内容.
  • @Mischa - 是的,这就是问题所在!我(愚蠢地)通过错过[:user] 部分来执行正确构造的查询,因此找不到用户,因此跳过了其余的逻辑。你是对的,保存几乎没有用 - 我正在尝试使用validate: false 来通过电子邮件验证,但这是不必要的,因为在修复查询后一切都已到位。感谢您的评论!

标签: ruby-on-rails ruby-on-rails-3 validation devise devise-invitable


【解决方案1】:

查看重定向:

redirect_to accept_user_invitation_path(:invitation_token => @existing_user.invitation_token)

我可以看到没有return,这应该意味着如果调用该重定向,您应该得到一个AbstractController::DoubleRenderError 错误,因为父控制器的create 方法应该尝试呈现new 视图.

据此,我猜您用于查找现有用户的查询实际上并未返回结果,可能是因为您使用的是params[:email],而如果您使用的是默认视图或格式正确的表单,则应该是params[:user][:email].

【讨论】:

  • 您完全正确-我没有正确查询哈希以获取user,因此它正在搜索NULL 电子邮件,因此无法正确捕获其余逻辑。现在一切正常,谢谢!
【解决方案2】:

也许你应该给你的控制器更多的责任......

如果找到用户,请使用该用户,否则创建一个新用户。假设您的表单显示为 http://yourapp/users/new,请将其在您的路由中更改为 http://yourapp/users/new/:email,让用户在进入表单之前输入他们的电子邮件。

def new
    @existing_user = User.find_by_email("#{params[:email]}.#{params[:format]}") || User.new
    if @existing_user.was_invited? # will only work for existing user
        redirect_to accept_user_invitation_path(:invitation_token => @existing_user.invitation_token)
    else
        render 'new'
    end
end

def create
    # do maybe something before saving
    if @existing_user.save(user_params)
        # do your magic
    else
        render 'new', notice: "Oops, I didn't save"
    end
end

【讨论】:

  • 我确实尝试过这个以防万一,但这并没有解决我的问题。这里有一些有用的东西可用于重构 create 方法,但首先是问题中的问题 - 应用程序无法访问此代码,因为验证正在针对表单运行并且唯一性验证失败。我必须先克服它。
  • 在这种情况下,您应该将第一行带到控制器中的“新”操作并运行 form_for @existing_user。那应该可以完全解决您的问题。查看修改后的版本
猜你喜欢
  • 1970-01-01
  • 2017-01-04
  • 1970-01-01
  • 2012-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多