【问题标题】:Devise after_sign_in_path_for not working; being ignored when model has validations on: :update设计 after_sign_in_path_for 不工作;当模型对: :update 进行验证时被忽略
【发布时间】:2018-02-21 19:13:40
【问题描述】:

我的方法正在执行,但 Devise 根本没有使用返回值。在登录页面上,它只是重新加载带有“登录成功”通知的页面。它不会重定向到从方法返回的值。

日志

Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:19:50 -0500
Processing by Users::SessionsController#create as HTML
  Parameters: {"utf8"=>"√", "authenticity_token"=>"tQd5a43StP85oyyCpEmFU8cAkFXdJL2OLpuAK1+sqQC6/rIqcd+fB2iE4RT0RoPKPCqreNBYlv2bxjl9gZFrWg==", "user"=>{"email"=>"test11@example.com", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
  User Load (2.0ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["email", "test11@example.com"], ["LIMIT", 1]]
   (5.0ms)  BEGIN
  User Exists (3.0ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = $1 AND ("users"."id" != $2) LIMIT $3  [["email", "test11@example.com"], ["id", 23], ["LIMIT", 1]]
  Sector Load (0.0ms)  SELECT "sectors".* FROM "sectors" INNER JOIN "sectors_users" ON "sectors"."id" = "sectors_users"."sector_id" WHERE "sectors_users"."user_id" = $1  [["user_id", 23]]
  Region Load (0.0ms)  SELECT "regions".* FROM "regions" INNER JOIN "regions_users" ON "regions"."id" = "regions_users"."region_id" WHERE "regions_users"."user_id" = $1  [["user_id", 23]]
  Criterium Load (0.0ms)  SELECT "criteria".* FROM "criteria" INNER JOIN "criteria_users" ON "criteria"."id" = "criteria_users"."criterium_id" WHERE "criteria_users"."user_id" = $1  [["user_id", 23]]
  AssetType Load (0.0ms)  SELECT "asset_types".* FROM "asset_types" INNER JOIN "asset_types_users" ON "asset_types"."id" = "asset_types_users"."asset_type_id" WHERE "asset_types_users"."user_id" = $1  [["user_id", 23]]
  Company Load (1.0ms)  SELECT  "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2  [["id", 42], ["LIMIT", 1]]
   (5.0ms)  ROLLBACK
############### /users/23/edit
  Rendering users/sessions/new.haml within layouts/application
  Rendered users/shared/_links.html.erb (3.0ms)
  Rendered users/sessions/new.haml within layouts/application (251.2ms)
  Rendered layouts/_footer.haml (15.0ms)
Completed 200 OK in 6554ms (Views: 3364.9ms | ActiveRecord: 86.1ms)

注意它正在渲染users/sessions/new.haml 而不是编辑页面?

代码

class ApplicationController < ActionController::Base
...
  def after_sign_in_path_for(resource)
    logger.debug '############### ' + edit_user_path(resource) if resource.is_a?(User) && resource.signature.blank?
    return edit_user_path resource if resource.is_a?(User) && resource.signature.blank?
    stored_location_for(resource) ||
      if resource.is_a?(User)
        dashboard_path
      elsif resource.is_a?(Facilitator) && resource.name.nil?
        edit_facilitator_path resource
      elsif resource.is_a?(Facilitator)
        facilitator_path resource
      else
        super
      end
  end

我完全注释掉了这个方法,它仍然重新加载了登录页面。

Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:25:21 -0500
...
  Rendering users/sessions/new.haml within layouts/application

设计 4.4.0

文档:

https://github.com/plataformatec/devise/wiki/How-To%3A-Redirect-to-a-specific-page-on-successful-sign-in-and-sign-out

http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers:after_sign_in_path_for


我加了

  def after_sign_in_path_for(resource)
    logger.debug '############# ' + resource.errors.full_messages.join(', ')

确实发现了类似的验证错误

 ############# Title can't be blank, Country can't be blank, Signature can't be blank, ...

但它确实显示了通知

Signed in successfully.

我确实有一个会话,可以在其他地方导航。我的验证是on: :update

  validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update

这不会导致登录行为错误。


我评论了模型上的所有验证,它确实有效,但这非常不寻常!验证不应影响登录行为。必须有一个解决方法。

Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 23:11:43 -0500
  SQL (15.0ms)  UPDATE "users" SET "current_sign_in_at" = $1, "last_sign_in_at" = $2, "current_sign_in_ip" = $3, "sign_in_count" = $4, "updated_at" = $5 WHERE "users"."id" = $6  [["current_sign_in_at", "2018-03-06 04:11:44.225501"], ["last_sign_in_at", "2017-11-09 01:22:28.245231"], ["current_sign_in_ip", "127.0.0.1/32"], ["sign_in_count", 6], ["updated_at", "2018-03-06 04:11:44.230506"], ["id", 23]]
Redirected to http://localhost:3000/users/23/edit
Completed 302 Found in 2183ms (ActiveRecord: 48.0ms)

【问题讨论】:

  • 也许在条件块中抛出一些logger.debug 语句,以便您知道代码的去向?
  • 注意:我不知道这是否是您的问题,但我注意到 ruby​​ 有时会对括号感到困惑。我会使用edit_user_path(resource),以防它对放置if 的位置感到困惑。例如,也许它正在做类似的事情:edit_user_path(resource if resource.is_a?(User) &amp;&amp; resource.signature.blank?) 其次:我觉得ROLLBACK 有点奇怪......为什么它似乎无法从数据库加载用户?
  • @CD-RUM 如果你看到调试语句的结果,你可以看到条件结果为真,所以它在最后一个调试语句之后立即返回。
  • @TarynEast 如果括号是这样的话,那是无效的 Ruby。无论如何,我将它们添加到论点中,但这没有帮助。我从日志中删除了一些 SQL 语句。它执行用户加载、BEGIN 事务、用户存在,然后加载几个关系,然后 ROLLBACK。我也不知道为什么。可能是因为 Devise 正在设置上次登录时间或其他内容并保存记录并进行验证?
  • 回滚会取消保存。它可以被解释为有效的 ruby​​,如果条件:resource.is_a?(User) &amp;&amp; resource.signature.blank? 失败,则返回 nil。它绝对不是 good ruby​​...绝对不会是 clear ruby​​,也绝对不是预期的 ruby​​...因此为什么具体会有所帮助。很高兴知道您实际尝试过,但这不是问题 - 我们可以排除这种极端情况。调试包括一一排除故障的可能原因,直到找到实际问题:)

标签: ruby-on-rails devise ruby-on-rails-5


【解决方案1】:

由于您只需要更新时的验证,我猜您只需要它们用于特定表单,因为即使没有此验证,您的用户仍然有效。 在这种情况下,我会使用一个所谓的表单对象,它会为您执行更新验证并删除您的用户模型上的更新验证。在这种情况下,您的验证不会影响应用的其他部分。

Here 是关于如何仅使用 ActiveModel 来做到这一点的一个很好的指南。

例子:

app/models/user.rb

class User < ApplicationRecord
  # remove the validations here
end

app/forms/user_edit_form.rb

class UserEditForm
    include ActiveModel::Model

    ATTRIBUTES = :email, :name, :title, :phone, 
                 :address1, :city, :state, :zip, 
                 :country, :type, :signature
    attr_accessor *ATTRIBUTES

    validates *ATTRIBUTES, presence: true

    def update(user)
      if valid?
        user.update(self.attributes)
      end
    end

    def self.for_user(user)
      new(user.slice(*ATTRIBUTES)
    end
  end

users_controller.rb

class UsersController
  def edit
    @user = User.find(params[:id])
    @user_edit_form = UserEditForm.for_user(@user)
  end

  def update
    @user = User.find(params[:id])
    @user_edit_form = UserEditForm.new(user_update_params).update(@user)
    if @user_edit_form.errors?
      render :edit
    else 
      redirect_to user_path(@user)
    end
  end

 def user_update_params
    # ...
 end
end

edit.html.erb

<%= form_for @user_edit_form, url: user_path(@user), method: :patch do |f| %>
  # ...

  <%= f.submit %>
<% end %>

替代方案

另一种方法是向模型添加虚拟属性并在用户控制器中有条件地运行验证。

class User < ApplicationRecord
  attr_accessor :profile_complete

  with_options if: -> { profile_complete } do
    validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true
  end
end

users_controller.rb

class UsersController < ApplicationController
  def update
    @user = User.find(params[:id])
    @user.profile_complete = true 
    if @user.update(user_update_params)
      redirect_to @user
    else 
      render :edit
    end

    # ...
  end
end

注意:除了使用虚拟属性 (attr_accessor),您还可以使用真实的 DB 属性,这样您实际上也可以知道哪些用户完全填写了他们的个人资料。

备选方案 2

在其他一些项目中,我也使用状态机 gem(有几个,例如 aasmstatemachines-activerecord)来做类似的事情。一些状态机 gem 甚至支持仅对某些状态或转换进行验证。

【讨论】:

  • 好的,但该示例并未显示如何仅运行验证 on: :update 并且仅适用于单个表单。您说“删除模型上的验证”,但示例确实在模型上使用了验证。让用户使用简单的名称/电子邮件/密码进行注册,然后填写 100 个问题的个人资料是网络上的标准做法。如果他们必须填写一份巨大的问卷,那么注册是没有用的。
  • 该示例没有对模型使用验证,而是使用了一个额外的 ActiveModel 类,该类包装了用户模型,并且仅在该控制器中使用。我会用一个例子更新我的帖子。
  • 如果您愿意,我也可以举一个其他方法的示例。
  • 哇,这当然很聪明,我想它会起作用,但它似乎完全没有必要,过于复杂,而且抗 DRY。我有 50 个关于用户、自定义设置器和大约十几个验证的属性/关系。这与设计中使用#update_columns 而不是save(validate: true) 的单行修复相比。 (更不用说 Devise :trackable 现在已损坏,因为它使用户能够在不保存 IP/时间戳的情况下使用会话登录。更不用说它留下损坏的 Rails on: :update。)
  • 为替代方法添加了另一个示例,它更简单,也许更多您正在寻找的东西。但请记住,当 Rails 应用程序变大时,第一种方法可能是正确的方法,因为您希望使模型不受表单逻辑的影响(实际上属于 MVC 中的视图而不是模型。使用表单对象您可以将 View Logic 封装在模型之外。首先可能看起来不那么 DRY,但有一些完整的宝石,如开拓者改革 (trailblazer.to/gems/reform) 可以帮助您采用这种方法。
【解决方案2】:

查看此文档https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in。他们已经清楚地提到了您何时会进入循环和解决方案。检查上述文档中的防止重定向循环部分。

【讨论】:

  • 没有循环。无论如何,那是针对 PasswordController 和 RegistrationController 的。这是设计中的一个错误。请参阅问题中的最后一条评论。
【解决方案3】:

您可能需要对模型进行条件验证。像这样:

 validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update, unless: Proc.new {|user| user.current_sign_in_at.present? }

每当登录发生时,Devise 都会更新sign_in_at。这将触发更新操作和相关验证。

文档还说allow_nil: true 指示模型仅在提交的表单中存在字段时才对其进行验证。

【讨论】:

  • NoMethodError (undefined method 'sign_in_at' for #&lt;User:0x000000125f19e8&gt; Did you mean? sign_in_count)
  • 设计登录时更新的任何属性。编辑为 current_sign_in_at
  • current_sign_in_at | timestamp without time zone 是一个数据库字段。它始终存在且从不为空白(未经确认的新用户除外)。这将使验证成为 NO-OP(无操作;就像注释掉一样)。这将允许用户编辑他们的个人资料并省略所需的信息。
  • 我的意思是如果current_sign_in_at 只出现在更新属性中。不是整个对象。
猜你喜欢
  • 2013-05-27
  • 1970-01-01
  • 1970-01-01
  • 2013-04-13
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多