Search code examples
ruby-on-railsfacebookauthenticationdevisehelper

Devise - Omniauth – Hiding password fields if the user is signed in through Facebook


Using Devise with Omniauth I was successfully able to allow users to login with their Facebook accounts. With the help of the Omniauth wiki I was also able to disable having to put in a password when editing their account.

The current problem I'm faced with is hiding the password fields in my registrations#edit view if they signed up via Facebook. As a newcomer to both gems I'm still trying to adjust. So when it comes to setting up the necessary helper method(s) I'm sort've at a loss.

As instructed by the wiki here's what my Registrations controller looks like:

class RegistrationsController < Devise::RegistrationsController
  before_filter :configure_permitted_parameters, if: :devise_controller?

  def update
    @user = User.find(current_user.id)

    successfully_updated = if needs_password?(@user, params)
      @user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
    else
      # remove the virtual current_password attribute
      # update_without_password doesn't know how to ignore it
      params[:user].delete(:current_password)
      @user.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))
    end

    if successfully_updated
      set_flash_message :notice, :updated
      # Sign in the user bypassing validation in case their password changed
      sign_in @user, :bypass => true
      redirect_to after_update_path_for(@user)
    else
      render "edit"
    end
  end

  private

  # check if we need password to update user data
  # ie if password or email was changed
  # extend this as needed
  def needs_password?(user, params)
    user.email != params[:user][:email] ||
      params[:user][:password].present? ||
      params[:user][:password_confirmation].present?
  end

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) do |u|
      u.permit(:first_name, :last_name, :email, :password, :password_confirmation)
    end
    devise_parameter_sanitizer.for(:account_update) do |u|
      u.permit(:first_name, :last_name, :email, :password, :password_confirmation)
    end
  end
end

And here is my registrations#edit view:

<% provide :title, 'Edit Profile' %>

<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <div><%= f.label :first_name %><br />
  <%= f.text_field :first_name %></div>

  <div><%= f.label :last_name %><br />
  <%= f.text_field :last_name %></div>

  <div><%= f.label :email %><br />
  <%= f.email_field :email %></div>

  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
    <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
  <% end %>

  <div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
    <%= f.password_field :password, autocomplete: "off" %></div>

  <div><%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %></div>

  <div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password, autocomplete: "off" %></div>

  <div><%= f.submit "Update" %></div>
<% end %>

UPDATE

After some extensive research I was able to find a question that inderictly answered what I was looking for in terms of hiding the input fields (second to last comment in the question). The only problem is that for some reason it triggers a password validation error despite it not being there...

   <div>
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </div>

  <div>
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>

  <% if user_signed_in? && !current_user.provider == 'facebook' %>
    <div>
      <%= f.label :email %><br />
      <%= f.email_field :email %>
    </div>

    <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
      <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
    <% end %>

    <div>
      <%= f.label :password %><br />
      <%= f.password_field :password %>
    </div>

    <div>
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation %>
    </div>

    <div>
      <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
      <%= f.password_field :current_password %>
    </div>

Solution

  • After searching through the site I have FINALLY found out how to get this to work.

    My first order of business was to to use the first code example for the registrations controller from this devise wiki

      def update
        # For Rails 4
        account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
        # For Rails 3
        # account_update_params = params[:user]
    
        # required for settings form to submit when password is left blank
        if account_update_params[:password].blank?
          account_update_params.delete("password")
          account_update_params.delete("password_confirmation")
        end
    
        @user = User.find(current_user.id)
        if @user.update_attributes(account_update_params)
          set_flash_message :notice, :updated
          # Sign in the user bypassing validation in case their password changed
          sign_in @user, :bypass => true
          redirect_to after_update_path_for(@user)
        else
          render "edit"
        end
      end
    

    Next was adding a method to override the validation for the user's current password just above the code above. Special thanks to Lauri Laine for her answer in this post: Editing Users With Devise and Omniauth

      # 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
    

    Lastly I had to add :current_password as a virtual attribute to my user model and everything worked!

    attr_accessor :current_password
    

    Hopefully this helps anyone who has/will encounter this problem. I know it had me stumped for a minute but glad I was able to figure it all out. Will be testing this soon in conjunction with omniauth's twitter gem.