可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm trying to update a user without having to provide a password, but approaches that worked on older devise/rails versions no longer work with devise 3 and rails 4 strong parameters.
I'm using my user_controller to update but I have also tried using a custom devise registration controller with devise_parameter_sanitizer, without success.
The form does not require a password (has no password field) and the user_controller handling the update looks like so:
# PATCH/PUT /users/1
def update
if user_params[:password].blank?
Rails.logger.info "entered if statement"
user_params.delete :password
user_params.delete :password_confirmation
Rails.logger.info(user_params.inspect)
end
@user = current_user
if @user.update(user_params)
redirect_to @user, notice: 'User was successfully updated.'
else
Rails.logger.info(@user.errors.inspect)
render action: 'edit'
end
end
private
def user_params
params.require(:user).permit(:screen_name, :full_name, :email, :about,
:location, :profile_pic, :password, :password_confirmation, :current_password)
end
.. the log after a submit looks like:
Started PATCH "/users/13" for 127.0.0.1 at 2013-05-29 11:18:18 +0100
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"20avah2OzaOVubAiam/SgvbYEQ4iijEWQqmNo7xD4rY=", "user"=>{"screen_name"=>"Darcbar", "full_name"=>"Barry Darcy", "about"=>"", "location"=>"", "website_url"=>"", "twitter_username"=>"", "email"=>"barry@gmail.com"}, "commit"=>"Save changes", "id"=>"13"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 13 ORDER BY "users"."id" ASC LIMIT 1
Entered if statement...
{"screen_name"=>"Darcbar", "full_name"=>"Barry Darcy", "email"=>"barry@gmail.com", "about"=>"", "location"=>"", "twitter_username"=>"", "website_url"=>""}
(0.2ms) BEGIN
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE ("users"."email" = 'barry@gmail.com' AND "users"."id" != 13) LIMIT 1
(0.2ms) ROLLBACK
#<ActiveModel::Errors:0x007fedf45bb640 @base=#<User id: 13, username: "darcbar", full_name: "Barry Darcy", about: "", location: "", email: "barry@gmail.com", encrypted_password: "$2a$10$Mb4zsRPPqZ9CYz0zdLMBU.62NyIk/T8s6Zw/uRTwWov3...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 9, current_sign_in_at: "2013-05-28 17:51:20", last_sign_in_at: "2013-05-28 16:42:52", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", authentication_token: nil, created_at: "2013-05-27 14:03:41", updated_at: "2013-05-28 17:51:20", screen_name: "Darcbar", profile_pic_file_name: nil, profile_pic_content_type: nil, profile_pic_file_size: nil, profile_pic_updated_at: nil>,
@messages={:password=>["please enter a password with at least 5 characters", "please enter a password with at least 5 characters"]}>
Rendered users/edit.html.haml within layouts/application (3.0ms)
Rendered partials/head/_user_options.haml (1.8ms)
Completed 200 OK in 74ms (Views: 12.1ms | ActiveRecord: 1.7ms)
Does anyone know why the password errors are present?
回答1:
The password validation is coming from the user model:
validates :password, presence: true
The solution is to only validate presence on create and allow_blank on update:
validates :password, presence: true, length: {minimum: 5, maximum: 120}, on: :create
validates :password, length: {minimum: 5, maximum: 120}, on: :update, allow_blank: true
回答2:
As of 2014, you can simply override a protected method and do:
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
resource.update_without_password(params)
end
end
回答3:
You can use @user.update_without_password(user_params)
method to update your other fields.
For example, I have this in my custom users_controller.rb. I update with remote call (ajax).
#users_controller.rb
def update
respond_to do |format|
if needs_password?(@user, user_params)
if @user.update_with_password(user_params_password_update)
flash[:success] = 'User was successfully updated. Password was successfully updated'
format.js {render 'update'}
else
error = true
end
else
if @user.update_without_password(user_params)
flash[:success] = 'User was successfully updated.'
format.js {render 'update'}
else
error = true
end
end
if error
flash[:error] = @user.errors.full_messages.join(', ')
format.js {render json: @user.errors.full_messages, status: :unprocessable_entity}
end
end
end
private
def needs_password?(user, user_params)
!user_params[:password].blank?
end
def user_params
params[:user].permit(:email, :password, :password_confirmation, :username, :full_name)
end
#Need :current_password for password update
def user_params_password_update
params[:user].permit(:email, :password, :password_confirmation, :current_password, :username, :full_name)
end
回答4:
The key is in this "user_params[:password].blank?". The next is a example of the code:
def update
if user_params[:password].blank?
params = user_params_without_password
else
params = user_params
end
respond_to do |format|
if @user.update(params)
format.html { redirect_to @user, notice: t(:user_update) }
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
private
def set_user
@user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:email, :username, :first_name, :last_name, :admin, :locked, :password)
end
def user_params_without_password
params.require(:user).permit(:email, :username, :first_name, :last_name, :admin, :locked)
end
Hope you help
回答5:
I went round in circles on this for ages. The answers are all in validatable as suggested by mrstif above. If you use the validatable module Devise works out of the box (with configuration options) allowing you to update user details without supplying a password so be very careful about rolling your own password validations.
回答6:
simply override the Devise by creating app/controller/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
resource.update(params.except(:current_password))
end
end
this code will directly update user params except :current_password
and update config/routes.rb
devise_for :users, controllers: {registrations: 'registrations'}
回答7:
My goal was to enable editing user attributes without requiring a password, unless it's changing email, password or deleting the account. And here's what worked for me:
app/controllers/registrations_controller.rb:
class RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters
...
def update
params[:user][:team_attributes][:id] = current_user.team.id
account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
if password_required?
successfully_updated = resource.update_with_password(account_update_params)
else
account_update_params.delete(:current_password)
successfully_updated = resource.update_without_password(account_update_params)
end
if successfully_updated
sign_in resource, bypass: true
redirect_to '/'
else
render :edit
end
end
def destroy
current_password = devise_parameter_sanitizer.sanitize(:account_update)[:current_password]
resource.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
error_messages = 'Current password ' + resource.errors[:current_password].join
if resource.destroy_with_password(current_password)
redirect_to '/'
else
redirect_to delete_account_path, notice: error_messages
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:account_update) do |user_params|
user_params.permit(:username, :email, :password, :password_confirmation, :current_password, :name, :phone_number
end
end
private
def password_required?
(resource.email != params[:user][:email] if params[:user][:email].present?) || params[:user][:password].present?
end
end
Update config/routes.rb:
devise_for :users, controllers: { registrations: 'registrations' }
In views/devise/registrations/edit.html.haml
# edit form
...
= simple_nested_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: 'mo-form' }, defaults: { placeholder: false, hint: false }) do |f|
...
# delete form
...
= simple_form_for(resource, as: resource_name, url: user_registration_path(resource_name), method: :delete, html: { class: 'mo-form' }, defaults: { placeholder: false, hint: false }) do |f|
...