使用Devise和Omniauth编辑用户

13

我正在学习实现Devise和OmniAuth(以及Devise的文档),目前,我已经建立了一个网站,访客可以使用他们的Facebook帐号注册或填写表格注册。

但是,当通过OmniAuth注册的用户尝试编辑其个人资料时,我遇到了麻烦。当他们提交更改请求时,Devise会要求输入当前密码,但那些使用Facebook登录的用户不知道自己的密码(它们在用户模型中自动生成):

  def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(first_name:auth.extra.raw_info.first_name,
                         last_name:auth.extra.raw_info.last_name,
                           provider:auth.provider,
                           uid:auth.uid,
                           email:auth.info.email,
                           password:Devise.friendly_token[0,20]
                           )
    end
    user
  end

如果用户通过OmniAuth设置了账户,则当用户编辑个人信息时,应用程序不应要求密码确认。该教程建议使用方便的password_required?方法来实现此目标。具体而言,将此方法添加到用户模型中意味着它只有在用户没有通过OmniAuth注册时才返回true(在这种情况下,提供者属性将为空):

def password_required?
  super && provider.blank?
end

因此,像下面这样的一段代码:

<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
  <%= devise_error_messages! %>
    <%= render :partial => "essential_user_info_inputs", :locals => { :f => f } %>
    <%= render :partial => "inessential_user_info_inputs", :locals => { :f => f } %>
    <% if f.object.password_required? %> 
        <%= render :partial => "password_inputs", :locals => { :f => f } %>
        <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
        <%= f.password_field :current_password %>
    <% end %>
  <%= f.submit "Update" %>
<% end %>
理论上只在需要时显示密码输入框。它还表明,Devise内置的逻辑是,OmniAuth用户不需要使用密码来编辑其帐户。我不知道这是否为真,但是这个教程让它看起来像是那样。但当OmniAuth用户尝试编辑他的帐户时,我会收到“当前密码不能为空”的错误提示。对于非OmniAuth用户也是一样的(这很合理,因为这些用户的编辑页面上也没有密码字段)。
进一步探索确认password_required?方法在通过OmniAuth和网站常规用户注册时都返回false。即使我将其更改为简单地运行超类方法,它仍然返回false。
你有什么关于password_required方法发生了什么的想法吗?我无法找到任何相关信息,但我觉得这正是现在的问题所在。
更新: 现在这个问题已经解决,但不是使用Railscast中概述的方法,该方法依赖于requires_password?方法,这是我仍然一无所知的主题。相反,我实施了这里概述的解决方案,建议在这里。因此,我现在只要求使用以下代码更新非OmniAuth帐户的密码:
class Users::RegistrationsController < Devise::RegistrationsController
def update
    @user = User.find(current_user.id)
    email_changed = @user.email != params[:user][:email]
    is_facebook_account = !@user.provider.blank?

    successfully_updated = if !is_facebook_account
      @user.update_with_password(params[:user])
    else
      @user.update_without_password(params[:user])
    end

    if successfully_updated
      # Sign in the user bypassing validation in case his password changed
      sign_in @user, :bypass => true
      redirect_to root_path
    else
      render "edit"
    end
  end
end

3
你应该总是尝试查找宝石的维基页面。这是Devise的链接:https://github.com/plataformatec/devise/wiki/_pages。而你想要做的是这个:https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password。 - Ashitaka
3个回答

23
最简单的方法是在您的RegistrationsController中覆盖update_resource方法。这是由devise在他们自己实现的控制器中建议的:
  # By default we want to require a password checks on update.
  # You can overwrite this method in your own RegistrationsController.
  def update_resource(resource, params)
    resource.update_with_password(params)
  end

因此,解决方案是在自己的控制器中覆盖此方法,如下所示:
class Users::RegistrationsController < Devise::RegistrationsController

  # Overwrite update_resource to let users to update their user without giving their password
  def update_resource(resource, params)
    if current_user.provider == "facebook"
      params.delete("current_password")
      resource.update_without_password(params)
    else
      resource.update_with_password(params)
    end
  end

end

2
简单明了!完美运作。 - Luis Crespo
1
这正是我一直在寻找的缺失部分。效果很好。 - jakeatwork
2
我还在update_resource方法上方添加了 protected,并根据 https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password 更新了我的路由,使用控制器 devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks", registrations: 'users/registrations' } - HarlemSquirrel
2
我认为current_user.provider == "facebook" 应该是 resource.provider == "facebook". current_user并不总是可用的。例如,如果用户被称为 current_logincurrent_user_loginresource方法会解析为当前@request.env['devise.mapping']。在回答中可能值得改变。 - Tyrone Wilson

1

0

我在某个地方看到过这个被使用。


def update
    params[:user].delete(:current_password)
    params[:user].delete(:password)
    params[:user].delete(:password_confirmation)
    if current_user.update_without_password(params[:user])
      redirect_to somewhere_wicked_path, notice => "You rock"
    else
      render 'edit', :alert => 'you roll'
    end
end

在您的控制器中的更新方法中使用类似这样的东西。我相信 Devise 中也有这种方法。


那些 params[:user].delete 是不必要的,因为 OP 在表单中隐藏了这些字段,所以它们永远不会被发送。 - Ashitaka
好的,我会更新我从中抄袭的代码并查看你是否正确。代码越少越好。 - pjammer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接