I executed following code
#user = User.find(current_user.id)
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(params[:user])
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(params[:user])
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
render "edit"
end
but update_without_password give false and database is rollbacked.
Do I have to do something for update_without_password?
I just went through my own issues with this. The following code should work as advertised (as found on the wiki page for Devise).
def update
#user = User.find(current_user.id)
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(params[:user])
else
params[:user].delete(:current_password)
#user.update_without_password(params[:user])
end
if successfully_updated
set_flash_message :notice, :updated
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
render "edit"
end
end
Make sure you also define the private method for 'needs_password?'
def needs_password?(user, params)
(params[:user].has_key?(:email) && user.email != params[:user][:email])
|| !params[:user][:password].blank?
end
My issue was that I had removed the "email" field from the form in the 'edit.html.erb' file, so the 'needs_password?' method kept returning true since user.email was never equal to nil. To fix the issue, I added in a check params[:user].has_key?(:email) to see if 'email' exists in the hash.
FWIW, the 'update_without_password' pretty much works the same way as 'update_with_password' except that it strips out the 'password' and 'password_confirmation' parameters before calling 'update_attributes' on the object, so you shouldn't have to do anything more. Try looking upstream a bit and see if you really are calling 'update_without_password'.
Related
I currently have a user that has extra attributes that I want them to be able to update without entering their current password.
So I had to create my own RegistrationsController and override the update method. (Taken from Devise github page).
def update
account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
# 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 edit_user_registration_path
else
render 'edit'
end
end
But now, I want to require them to put in their correct current password if they want to change their password. This seems to be an issue because if they do put in their current password, I have to add it to the permitted parameters. But then it tells me that current_password is not a valid attribute of the user.
def configure_permitted_parameters
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:name, :email, :attr1, :attr2,
:password, :password_confirmation) }
end
Use update_with_password and update_without_password mehotds
with_password = params[:current_password].present?
if #user.update_user(with_password)
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case their password changed
sign_in #user, :bypass => true
redirect_to edit_user_registration_path
else
render 'edit'
end
private
def update_user(with_password)
if with_password
#user.update_with_passoword(account_update_params)
else
#user.update_passoword(account_update_params)
end
end
See documentation
I am trying to allow the user to deactivate their account, and reactivate their account. I've got code which allows changes a is_active integer value to 0 when they deactivate the account.
I am using devise 3, rails 4 and postgres.
Here is my destroy method in app/controllers/registration_controller.rb which overrides devise's method and keeps the data in the database.
def destroy
#user = User.find(current_user.id)
#user.is_active = 0
if #user.save
set_flash_message :notice, :destroyed
sign_out #user
redirect_to root_path
else
render "edit"
end
end
Then I copied the idea and made a button for activating the account and used this code in the same registration controller:
def activate
#user = User.find(current_user.id)
#user.is_active == nil
if #user.save
set flash_message :notice
redirect_to edit_user_registration_path
else
render "edit"
end
end
How can I set the is_active integer type back to a blank space?
I think here's the problem:
#user.is_active == nil #< -- conditional double =
should be
#user.is_active = nil #< -- assignation
I am trying to do two things:
1) Change the default "edit user form" - provided with devise - to remove "password" and allow the other fields to be updated without having to enter a password ie remove the default validation for password.
2) Create a separate form for changing password
I have got everything to work, there is only one problem, in the separate form for updating password, I have included a field for current password. When using the form, no validation is made for current password, so I changed
#user.update_attributes(params[:user])
to
#user.update_with_password(params[:user])
This worked, however it raised another issue. Back in the main form with all the other details except password, the form now asks for a "current password". How can I achieve this without a validation for current password being called on the main form?
here is my registrations controller:
def update
#user = User.find(current_user.id)
if #user.update_attributes(params[:user])
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
clean_up_passwords(resource)
respond_with_navigational(resource) do
if params[:change_password] # or flash[:change_password]
render :change_password
else
render :edit
end
end
end
end
Thanks!
Solution 1
I have found a solution to the problem (albeit a very messy one):
def update
#user = User.find(current_user.id)
if params[:user][:password].blank?
if #user.update_attributes(params[:user])
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
respond_with_navigational(resource) do
render :edit
end
end
else
if #user.update_with_password(params[:user])
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
clean_up_passwords(resource)
respond_with_navigational(resource) do
render :change_password
end
end
end
Solution 2
Can you suggest a better solution?
Did you bother to check out Devise wiki? There are examples for both this cases
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-password
You should be looking at #user.update_with_password(params[:user]) vs #user.update_attributes(params[:user])
The accepted answer does not fully address the question. Which, I believe is to have a separate form for user profile attributes (like email, first name, etc) vs. the password. Here's what you need to do for that:
First, leverage the Devise::RegistrationsController for your profile updates.
Customize the view and remove the password and password_confirmation fields. Devise ignores these if they are not present in the put.
If you don't want to require the current password to make profile changes, read this. Not recommended; not secure.
Second, create your own controller to manage the password updates and your own helper to require current_password, password, and password_confirmation on update.
class PasswordsController < ApplicationController
before_filter :authenticate_user!
def edit
#user = current_user
end
def update
#user = User.find(current_user.id)
if #user.update_password_with_password(user_params)
# Sign in the user by passing validation in case their password changed
sign_in #user, :bypass => true
redirect_to edit_password_path, flash: { success: "Successfully updated password" }
else
render "edit"
end
end
private
def user_params
params.require(:user).permit(:current_password, :password, :password_confirmation)
end
end
Here's the helper, update_password_with_password that will require the new password fields.
class User < ActiveRecord::Base
def update_password_with_password(params, *options)
current_password = params.delete(:current_password)
result = if valid_password?(current_password)
update_attributes(params, *options)
else
self.assign_attributes(params, *options)
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end
clean_up_passwords
result
end
end
I overrided a little devise gem to allow user change some details with or without account depends on situation.
I don't want to use devise error messages, because I'm using jQuery validation plugin.
I want to show some notice error, when some devise errors exists, but I can't get where I need to put flash[:error].
Here is my update action:
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
if params[:user][:password].present?
clean_up_passwords resource
render "edit_user/edit_password"
end
end
So, can someone help me with this ?
If you are using Jquery validation have you enabled this on your views by doing something similar <%= form_for #user, :validate => true, do |f|%>
Update
You could do something somewhat similar to this
def update_password
#user = current_user
#current_password = params[:user][:current_password]
#password = params[:user][:password]
if #user.valid_password?(#current_password)
if #current_password == #password
redirect_to user_path(#user)
flash[:error] = "Current password cannot be the same as your new password."
else
if #user.update_attributes(params[:user])
redirect_to login_path
flash[:success] = "Password has been changed."
else
redirect_to user_path(#user)
flash[:error] = "Didn't save contact an administrator."
end
end
else
redirect_to user_path(#user)
flash[:error] = "Current password is incorrect."
end
end
Providing you add the top bit I suggested into your view and have installed Jquery validation correctly this should work
Now I realise this topic has been covered many times before. However there did not appear to be a solution that wanted to make the current password field exempt only when the password in the database was blank.
I currently have the password required in my user model like:
def password_required?
(authentications.empty? || !password.blank?) && super
end
Then I copied the update function across into my registrations controller:
def update
if resource.update_with_password(params[resource_name])
set_flash_message :notice, :updated
sign_in resource_name, resource, :bypass => true
redirect_to after_update_path_for(resource)
else
clean_up_passwords(resource)
render_with_scope :edit
end
end
I just don't know how to go about ensuring that devise does not need a password when editing a blank password, do I also need to remove the current_password field from the view and do this?
<% if current_user.password_required? %>
<p><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %></p>
<% end %>
<p><%= f.submit "Update" %></p>
Any suggestions would be great on this as I am sure I am overlooking something but I am still new to Rails on a whole. Thanks!
Okay so I have finally figured this out!
Add the following to your class RegistrationsController < Devise::RegistrationsController
def update
if resource.update_with_password(params[resource_name])
set_flash_message :notice, :updated
sign_in resource_name, resource, :bypass => true
redirect_to after_update_path_for(resource)
else
clean_up_passwords(resource)
render_with_scope :edit
end
end
Then the following to your User model:
def update_with_password(params={})
current_password = params.delete(:current_password) if !params[:current_password].blank?
if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end
result = if has_no_password? || valid_password?(current_password)
update_attributes(params)
else
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
self.attributes = params
false
end
clean_up_passwords
result
end
def has_no_password?
self.encrypted_password.blank?
end
The only thing I was slightly confused about is that in the edit view:
<% if !current_user.has_no_password? %>
I wrapped it in that if, I would have thought it would have been:
<% if current_user.has_no_password? %>
If anyone can see anything to improve my code or a different, more efficient way let me know!
Another very minor change on requiring the current password field or not: my goal is never require a current password unless they are updating their password. for oauth'ed accounts, I check for the facebook id in the view, and don't display the password fields at all.
In the registrations controller:
Users::RegistrationsController < Devise::RegistrationsController
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
if resource.update_with_password(params[resource_name])
set_flash_message :notice, :updated if is_navigational_format?
sign_in resource_name, resource, :bypass => true
respond_with resource, :location => after_update_path_for(resource)
else
clean_up_passwords(resource)
respond_with_navigational(resource){ render_with_scope :edit }
end
end
In User.rb, I use update_with_password, and then call verify_password_and_update only if the user has entered something in the password field from the view. Otherwise, I clear the current password param and call update_without_password (this method is built in to devise now as well):
#to remove the current password check if updating a profile originally gotten via oauth (fb, twitter)
def update_with_password(params={})
if params[:password].blank?
params.delete(:current_password)
self.update_without_password(params)
else
self.verify_password_and_update(params)
end
end
def update_without_password(params={})
params.delete(:password)
params.delete(:password_confirmation)
result = update_attributes(params)
clean_up_passwords
result
end
def verify_password_and_update(params)
#devises' update_with_password
# https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb
current_password = params.delete(:current_password)
if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end
result = if valid_password?(current_password)
update_attributes(params)
else
self.attributes = params
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end
clean_up_passwords
result
end