Reputation: 6168
I want to update users attributes without password in devise. The case is like, if password and password confirmation fields are not blank then I need devise error and if they are blank then other user attributes need to be updated. How could I do this with devise?
Thanks in advance!
Upvotes: 81
Views: 59474
Reputation: 6712
If you really want to update the password without the current_password then you need to use the user reset_password
method
@user.reset_password(params[:user][:password], params[:user][:password_confirmation])
Here is a complete working solution to the problem that works in all scenarios:
if params.dig(:user, :password).blank?
updated = @user.update_without_password(params[:user].to_unsafe_hash)
else
if params.dig(:user, :current_password).nil?
@user.reset_password(params[:user][:password], params[:user][:password_confirmation])
updated = @user.update_without_password(params[:user].to_unsafe_hash)
else
updated = @user.update_with_password(params[:user].to_unsafe_hash)
end
bypass_sign_in(@user)
end
Upvotes: 0
Reputation: 71
Anyone visiting in 2020, this is the simplest solution I found:
in registrations_controller.rb
:
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
# Require current password if user is trying to change password.
return super if params["password"]&.present?
# Allows user to update registration information without password.
resource.update_without_password(params.except("current_password"))
end
end
In routes.rb:
devise_for :users, controllers: {
registrations: 'users/registrations'
}
Full credit to: Masatoshi Nishiguchi
https://www.mnishiguchi.com/2017/11/24/rails-devise-edit-account-without-password/
Upvotes: 5
Reputation: 9455
For future Googlers, I just spent 3 hours on this, here's what worked for me.
In my User model I remove :validatable and added validates method for the password and password_confirmation only if they are present:
class User < ApplicationRecord
devise :database_authenticatable, :registranable, recoverable, :rememberable
validates :password, length: { in: 6..128 }, if: lambda {self.password.present?}
validates_confirmation_of :password, if: lambda {self.password.present?}
end
Then in my users_controller's update method I delete the password and password_confirmation params if they are blank
class UsersController > ApplicationController
def update
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to @user, notice: 'User was successfully updated' }
else
format.html { render :edit }
end
end
end
end
Simple, plain, easy and no jerking around with Devise's convoluted way of doing things.
You're all welcome.
Upvotes: 3
Reputation: 671
to update attributes for user, you will need to use :current_password, to check 'do your user use correct current_password or someone wants to break your user'
so in form:
= f.input :current_password,
hint: "we need your current password to confirm your changes",
required: true,
input_html: { autocomplete: "current-password" }
in controller:
if your_user.valid_password?(params[:user][:current_password])
your_user.update_attributes(user_params.except(:current_password, :password, :password_confirmation))
end
the first line checks 'do your user sends the correct password or not', and then we can to update the attributes without garbage
Upvotes: 0
Reputation: 181
params[:user][:password] is not working in rails 6
You have to change params[:user][:password] to params[:password]
Remove [:user] in all params
my source code is below
before run this command
rails generate devise:controllers users -c=registrations
class Users::RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
protected
def update_resource(resource, params)
puts "params ===> #{params}"
if params[:password].blank? && params[:password_confirmation].blank?
params.delete(:password)
params.delete(:password_confirmation)
params.delete(:current_password)
resource.update_without_password(params)
else
super
end
end
end
Upvotes: 5
Reputation: 822
Better and short way: add check params into the user_params block. For example:
def user_params
up = params.require(:user).permit(
%i[first_name last_name role_id email encrypted_password
reset_password_token reset_password_sent_at remember_created_at
password password_confirmation]
)
if up[:password].blank? && up[:password_confirmation].blank?
up.delete(:password)
up.delete(:password_confirmation)
end
up
end
Upvotes: 0
Reputation: 21
After much exploration of the above possibilities I finally found a solution which allows you to update some attributes without a password and some with:
I made a view for user/edit.html.erb with the following form in it.
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.submit "Save changes"%>
<% end %>
I set routes in routes.rb
resources :users, only: [:show, :index, :edit, :update]
I made edit and update methods in users_controller.rb
def edit
@user = current_user
end
def update
@user = current_user
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to root_url
else
render 'edit'
end
end
def user_params
params.require(:user).permit(:name, :avatar, :whatsup)
end
I used this edit view for changes which did not require a password. It skips the devise registration controller entirely because I link to it with
edit_user_path(current_user)
I also set the params so that email and password cannot be changed here. To update password and email, I link to the stock generated devise view:
edit_user_registration_path(current_user)
I admit that this is a huge work around but none of the simpler solutions solved all of the above problems.
Upvotes: 0
Reputation: 296
I find this slight variation on the code above easier to follow:
def update
@user = User.find(params[:id])
method = if user_params[:password].blank?
:update_without_password
else
:update_with_password
if @user.send(method, user_params)
redirect_to @user, notice: 'User settings were saved.'
else
render :edit
end
end
Upvotes: 1
Reputation: 4315
If you still want to support password change but make it optional, check the availability of current_password
as such:
class MyRegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
if params[:current_password].blank?
resource.update_without_password(params.except(:current_password))
else
resource.update_with_password(params)
end
end
end
That way if the current_password is present you can proceed and update the password, else ignore it and update without password.
Upvotes: 5
Reputation: 93
As workaround put in your User model
def password_required?
encrypted_password.blank? || encrypted_password_changed?
end
Upvotes: 1
Reputation: 102
I had the exact same issue and this the solution I came up with and believe me it works.
What i did is create a second user_params
method and named it user_params_no_pass
anyway take a look: what is happening here is that the admin will provide a password when the password need to be updated else leave the password blank. when the password is blank the user_params_no_pass
is used else user_params
. I hope that helps
def update
if @user.valid_password?(params[:user][:password])
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to @user, notice: 'User profile was successfully updated.' }
format.json { render :show, status: :ok, location: @user }
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
else
respond_to do |format|
if @user.update(user_params_no_pass)
format.html { redirect_to @user, notice: 'User profile was successfully updated without password.' }
format.json { render :show, status: :ok, location: @user }
else
format.html { render :edit }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
end
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:user_name, :first_name, :middle_name, :last_name, :dob, :gender, :race, :hispanic, :leader, :mentor, :student, :email, :organization_id, :password, :opus,
:release_date, :days_to_release, :current_level, :is_active)
end
def user_params_no_pass
params.require(:user).permit(:user_name, :first_name, :middle_name, :last_name, :dob, :gender, :race, :hispanic, :leader, :mentor, :student, :email, :organization_id, :opus,
:release_date, :days_to_release, :current_level, :is_active)
end
Upvotes: 0
Reputation: 89
It's more business logic so I wouldn't put too much code in the controller. I suggest overriding Devise::Models::Validatable#password_required?
in your model:
def password_required?
new_record? || password.present? || password_confirmation.present?
end
Upvotes: 1
Reputation: 349
I instead override #password_required? method inside user model.
class User < ActiveRecord::Base
devise :database_authenticatable, :validatable #, ...
def password_required?
if respond_to?(:reset_password_token)
return true if reset_password_token.present?
end
return true if new_record?
password.present? || password_confirmation.present?
end
end
So if user is new he will be required to specify password. However exist user will be required to specify password only if he fills in either password or password_confirmation attributes.
For more details see: https://github.com/plataformatec/devise/blob/master/lib/devise/models/validatable.rb#L33
My implementation is almost identical as original: https://github.com/plataformatec/devise/blob/master/lib/devise/models/validatable.rb#L53
except that I check for presence (which returns false for blank strings)
Here's discussion about my pull request on this matter: https://github.com/plataformatec/devise/pull/3920
Upvotes: 3
Reputation: 11
If you want Devise to check current password only when user tries to change password (that means you can change other attributes without providing current password):
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
if params[:password].blank? && params[:password_confirmation].blank?
resource.update_without_password(params)
else
super
end
end
end
Also in your model:
attr_accessor :current_password
And don't forget about
devise_for :users, controllers: {registrations: 'registrations'}
in routes.rb.
Upvotes: 1
Reputation: 2365
I solve this problem with my interface. There are two ways it can go.
This bit of jQuery disables the fields before the form submits in the event that they are empty. It requires a certain markup pattern, check the demo on Codepen.
$(".password-fields").each(function() {
var $fields, $form;
$form = $(this).parents("form");
$fields = $(this).find("input");
$form.on("submit", function() {
$fields.each(function() {
if ($(this).val() === "") {
$(this).attr("disabled", "disabled");
}
});
});
});
Another option is to remove the password fields from the form if the user hasn't indicated that they want to update their password. Here's the example on CodePen.
$(".password-fields").each(function () {
var $fields, $button, html;
$fields = $(this);
$button = $fields.prev(".update-password").find("input");
html = $fields.html();
$button
.on("change", function () {
if ( $(this).is(":checked") ) {
$fields.html(html);
}
else {
$fields.html("");
}
})
.prop("checked", "checked")
.click();
});
In either case, it requires no update to the app itself. You are just submitting the fields you want to change.
Upvotes: 3
Reputation: 12273
Is this what you're look for? From the Devise wiki
Allow users to edit their account without providing a password
It shows how to do it for rails 3 and rails 4
=======================
John's solution is a simpler alternative
Upvotes: 68
Reputation: 596
I think with Devise 3.2+ you can just override update_resource in your subclassed controller. Here's an example for the question originally asked:
class MyRegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
resource.update_without_password(params)
end
end
Upvotes: 23
Reputation: 3460
I think this is a much better solution:
if params[:user][:password].blank? && params[:user][:password_confirmation].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
This prevents you from having to change the Devise controller by simply removing the password field from the form response if it is blank.
Just be sure to use this before @user.attributes = params[:user]
or whatever you use in your update
action to set the new parameters from the form.
Upvotes: 97
Reputation: 4278
I solved this problem as follows: if form submitting with password, then need fill current_password field or form updated without password and current_password
in model:
class User
devise: bla-bla-bla
attr_accessor :current_password
end
In controller:
class Users::RegistrationsController < Devise::RegistrationsController
layout 'application'
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
# custom logic
if params[:user][:password].present?
result = resource.update_with_password(params[resource_name])
else
result = resource.update_without_password(params[resource_name])
end
# standart devise behaviour
if result
if is_navigational_format?
if resource.respond_to?(:pending_reconfirmation?) && resource.pending_reconfirmation?
flash_key = :update_needs_confirmation
end
set_flash_message :notice, flash_key || :updated
end
sign_in resource_name, resource, :bypass => true
respond_with resource, :location => after_update_path_for(resource)
else
clean_up_passwords resource
respond_with resource
end
end
end
Upvotes: 8