简体   繁体   中英

Devise after_sign_in_path_for not working; being ignored when model has validations on: :update

My method is executing, but Devise is not using the return value at all. On the sign in page, it just reloads the page with a 'Signed in successfully' notice. It doesn't redirect to the value returned from the method.

Log

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)

Notice it is rendering users/sessions/new.haml instead of the edit page?

Code

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

I completely commented out the method and it still reloaded the login page.

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

Devise 4.4.0

Documentation:

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


I added

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

And did discover validation errors like

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

But it does show the notice

Signed in successfully.

And I do have a session and can navigate elsewhere. My validations are on: :update .

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

This should not cause log in behavior errors.


I commented all validations on the model and it does work, but this is highly unusual! Validations should not affect login behavior. There has to be a workaround.

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)

As you only want your validations on update, I guess that you only need them for a specific form, since your users are still valid even without this validations. In that case I would use a so called form object, that does the on update validations for you and remove the on update validations on your user model. In that case your validations don't affect other parts of your app.

Here is a good guide on how to do that with just using ActiveModel.

Example:

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 %>

Alternative

An alternative could be to add a virtual attribute to the model and run your validations conditionally in the user controller.

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

Note: Instead of using a virtual attribute (attr_accessor) you could also use a real DB attribute, so you can also actually know which users filled out their profile completely.

Alternative 2

In some other projects I also used state machine gems (there are a couple eg aasm or statemachines-activerecord ) to do somehing similar. Some of the state machine gems even support having validations only for certain states or transisions.

Check this documentation https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in . They have clearly mentioned when you will go in loop and solution for it. Check Preventing redirect loops section in above doc.

You might need conditional validation on your model. Something like this:

 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 will update sign_in_at whenever sign_in happens. Which will trigger update action and related validations.

Also Documentation said the allow_nil: true instruct the model to validate the fields ONLY if it exists on the submitted form.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM