Ruby on rails ActiveRecord::RecordInvalid(验证失败:Uid不能为空)Omniauth LinkedIn设备

Ruby on rails ActiveRecord::RecordInvalid(验证失败:Uid不能为空)Omniauth LinkedIn设备,ruby-on-rails,ruby,omniauth,omniauth-linkedin,Ruby On Rails,Ruby,Omniauth,Omniauth Linkedin,保存用户模型时,我收到(验证失败:Uid不能为空)。Uid本身属于Identity对象,它充当多个标识类型的单个标识访问点。换句话说,您将能够在应用程序中使用facebook、twitter或linkedin omniauth策略,而无需创建多个帐户。它似乎不像广告宣传的那样有效。考虑到我从应用程序登录LinkedIn后无法创建用户 我试图将env引用替换为request.env。因为,它不存在于其当前范围内。我试图找到错误,但找不到身份模型在创建用户之前没有存储身份的确切原因。它在创建用户对象

保存用户模型时,我收到(验证失败:Uid不能为空)。Uid本身属于Identity对象,它充当多个标识类型的单个标识访问点。换句话说,您将能够在应用程序中使用facebook、twitter或linkedin omniauth策略,而无需创建多个帐户。它似乎不像广告宣传的那样有效。考虑到我从应用程序登录LinkedIn后无法创建用户

我试图将env引用替换为request.env。因为,它不存在于其当前范围内。我试图找到错误,但找不到身份模型在创建用户之前没有存储身份的确切原因。它在创建用户对象后立即失败,而不是检查用户是否存在并附加了标识或是否应该创建标识

服务器开发日志

Started GET "/users/auth/linkedin" for 127.0.0.1 at 2019-08-03 22:56:03 -0400
I, [2019-08-03T22:56:04.789247 #23148]  INFO -- omniauth: (linkedin) Request phase initiated.
Started GET "/users/auth/linkedin/callback?oauth_token=77--2555555-5555-4444-ssfs-dcsfsfsfsfsf93&oauth_verifier=28090" for 127.0.0.1 at 2019-08-03 22:56:31 -0400
I, [2019-08-03T22:56:32.422234 #23148]  INFO -- omniauth: (linkedin) Callback phase initiated.
Processing by OmniauthCallbacksController#linkedin as HTML
  Parameters: {"oauth_token"=>"77--2555555-5555-4444-ssfs-dcsfsfsfsfsf93", "oauth_verifier"=>"28090"}
  Identity Load (1.4ms)  SELECT  "identities".* FROM "identities" WHERE "identities"."uid" IS NULL AND "identities"."provider" = ? LIMIT ?  [["provider", "linkedin"], ["LIMIT", 1]]
  ↳ app/models/identity.rb:7
   (0.2ms)  begin transaction
  ↳ app/models/identity.rb:7
  Identity Exists (1.1ms)  SELECT  1 AS one FROM "identities" WHERE "identities"."uid" IS NULL AND "identities"."provider" = ? LIMIT ?  [["provider", "linkedin"], ["LIMIT", 1]]
  ↳ app/models/identity.rb:7
   (0.2ms)  rollback transaction
  ↳ app/models/identity.rb:7
   (0.2ms)  begin transaction
  ↳ app/models/user.rb:47
  User Exists (2.0ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "change@me--linkedin.com"], ["LIMIT", 1]]
  ↳ app/models/user.rb:47
  User Create (44.3ms)  INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at", "confirmed_at") VALUES (?, ?, ?, ?, ?)  [["email", "change@me--linkedin.com"], ["encrypted_password", "$2a"], ["created_at", "2019-08-04 02:56:33.928307"], ["updated_at", "2019-08-04 02:56:33.928307"], ["confirmed_at", "2019-08-04 02:56:33.921311"]]
  ↳ app/models/user.rb:47
   (134.0ms)  commit transaction
  ↳ app/models/user.rb:47
   (0.1ms)  begin transaction
  ↳ app/models/user.rb:54
  Identity Exists (2.1ms)  SELECT  1 AS one FROM "identities" WHERE "identities"."uid" IS NULL AND "identities"."provider" = ? LIMIT ?  [["provider", "linkedin"], ["LIMIT", 1]]
  ↳ app/models/user.rb:54
   (0.2ms)  rollback transaction
  ↳ app/models/user.rb:54
Completed 422 Unprocessable Entity in 879ms (ActiveRecord: 191.3ms)



ActiveRecord::RecordInvalid (Validation failed: Uid can't be blank):

app/models/user.rb:54:in `find_for_oauth'
(eval):3:in `linkedin'
omniauth\u回调\u控制器.rb

class OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def self.provides_callback_for(provider)
    class_eval %Q{
      def #{provider}
        @user = User.find_for_oauth(request.env["omniauth.auth"], current_user)

        if @user.persisted?
          sign_in_and_redirect @user, event: :authentication
          set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
        else
          session["devise.#{provider}_data"] = env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    }
  end

  [:facebook, :linkedin].each do |provider|
    provides_callback_for provider
  end

  def after_sign_in_path_for(resource)
    if resource.email_verified?
      super resource
    else
      finish_signup_path(resource)
    end
  end

  def failure
    redirect_to root_path
  end
end
class OmniAuthCallbackController
identity.rb

class Identity < ApplicationRecord
  belongs_to :user
  validates_presence_of :uid, :provider
  validates_uniqueness_of :uid, :scope => :provider

  def self.find_for_oauth(auth)
    find_or_create_by(uid: auth.uid, provider: auth.provider)
  end
end
类标识:provider的唯一性
定义self.find_for_oauth(auth)
查找或创建用户(uid:auth.uid,提供程序:auth.provider)
结束
结束
user.rb

class User < ApplicationRecord
  TEMP_EMAIL_PREFIX = 'change@me'
  TEMP_EMAIL_REGEX = /\Achange@me/

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, :trackable, :confirmable

  validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update

  def self.find_for_oauth(auth, signed_in_resource = nil)

    # Get the identity and user if they exist
    identity = Identity.find_for_oauth(auth)

    # If a signed_in_resource is provided it always overrides the existing user
    # to prevent the identity being locked with accidentally created accounts.
    # Note that this may leave zombie accounts (with no associated identity) which
    # can be cleaned up at a later date.
    user = if signed_in_resource then
             signed_in_resource
           else
             identity.user
           end

    # Create the user if needed
    if user.nil?

      # Get the existing user by email if the provider gives us a verified email.
      # If no verified email was provided we assign a temporary email and ask the
      # user to verify it on the next step via UsersController.finish_signup
      email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
      email = auth.info.email if email_is_verified
      user = User.where(:email => email).first if email

      # Create the user if it's a new registration
      if user.nil?
        user = User.new(
            name: auth.extra.raw_info.name,
            #username: auth.info.nickname || auth.uid,
            email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
            password: Devise.friendly_token[0,20]
        )
        user.skip_confirmation!
        user.save!
      end
    end

    # Associate the identity with the user if needed
    if identity.user != user
      identity.user = user  
      identity.save!     # Where error occurs
    end
    user
  end

  def email_verified?
    self.email && self.email !~ TEMP_EMAIL_REGEX
  end

  protected
  def confirmation_required?
    false
  end
end
class用户TEMP\u email\u REGEX,on::update
def self.find_for_oauth(auth,signed_in_resource=nil)
#获取标识和用户(如果存在)
identity=identity.find_for_oauth(auth)
#如果提供了已签名的\u-in\u资源,它将始终覆盖现有用户
#防止身份被意外创建的帐户锁定。
#请注意,这可能会留下僵尸帐户(没有关联的身份)
#可以以后再清理。
用户=如果已在资源中签名,则
已签名的\u-in\u资源
其他的
identity.user
结束
#如果需要,创建用户
如果user.nil?
#如果提供商向我们发送验证电子邮件,则通过电子邮件获取现有用户。
#如果未提供验证电子邮件,我们将分配一封临时电子邮件,并询问
#用户在下一步通过UsersController.finish\u注册进行验证
email_is_verified=auth.info.email&(auth.info.verified | | auth.info.verified_email)
电子邮件=auth.info.email(如果电子邮件已验证)
user=user.where(:email=>email)。第一个if email
#如果是新注册,则创建用户
如果user.nil?
user=user.new(
名称:auth.extra.raw_info.name,
#用户名:auth.info.昵称| | auth.uid,
电子邮件:email?email:“#{TEMP_email_PREFIX}-#{auth.uid}-#{auth.provider}.com”,
密码:design.friendly_令牌[0,20]
)
user.skip\u确认!
user.save!
结束
结束
#如果需要,将标识与用户关联
如果identity.user!=用户
identity.user=用户
identity.save!#发生错误的地方
结束
用户
结束
def电子邮件是否已验证?
self.email&&self.email!~临时电子邮件
结束
受保护的
是否需要def确认?
假的
结束
结束
预期行为:用户将成功登录其Linkedin帐户,并被重定向到应用程序以完成注册


实际结果:用户对象创建后,无法保存标识并将Uid留空。

错误是验证错误,Uid为null,这就是问题所在。因此,您似乎已经在数据库中保留了一个用户记录,其UID为nil。您的验证不允许添加另一个UID为nil的用户。因此,您的UID无法正确通过,因此验证将失败。我将首先编写一个测试,并使用OmniAuths测试模式和模拟的auth哈希,以确保您的代码实际按预期工作。然后,您应该添加一些NOTNULL数据库约束、复合索引和错误处理,以便不会在标识表中获得无效行和没有标识的用户记录。然后,如果您获得了预期的身份验证哈希,您可以尝试通过实际调用提供程序和byebug进行开发。您还有另一个潜在的问题,那就是
会话[“designe.#{provider}\u data”]=env[“omniauth.auth”]
-omniauth自述文件警告不要将整个身份验证哈希复制到会话中,因为它可能会超过cookies施加的大小限制。使用切片,只取你需要的。我实际上用一种完全不同的方法解决了这个问题。为便于将来参考,请关注此项目。它100%有效&别忘了在googledev上设置回调重定向url,等等