Updating `User` attributes without requiring password

前端 未结 9 1647
醉酒成梦
醉酒成梦 2021-01-30 17:27

Right now, users can edit some their attributes without having to enter their password because my validations are set up like this:

validates :password, :prese         


        
相关标签:
9条回答
  • 2021-01-30 18:24

    I had the same problem, and the solutions above didn't work for me. I found the real culprit in my case: I had an encrypt_password callback in my User model, which was setting the password to blank each time.

    before_save :encrypt_password

    I fixed it by adding a condition at the end for this call back:

    before_save :encrypt_password, :unless => Proc.new { |u| u.password.blank? }

    0 讨论(0)
  • 2021-01-30 18:24

    The correct answer no-longer works for rails 4. I believe my answer is the cleanest and the most versatile that will work whenever you want to leave out any attributes (not just the password). This approach will be needed if you want to update the separate attributes of any model in a number of different places.

    For example, if you want to do what Stack Overflow does and have the passwords updatable via a security page, the profile image updatable via the user show view and the bulk of a user's information updatable via a user edit view.

    1) Extend the hash class with a class method to delete blank values. We will use this method to remove blank values that are not being updated but are still present in the params hash:

    1a) Create a hash.rb file in your lib directory, under an ext directory:

    command line

    $ mkdir lib/ext
    $ touch lib/ext/hash.rb 
    

    1b) Inside hash.rb, 'create' a Hash class and create a .delete_blanks! method:

    lib/ext/hash.rb

    class Hash
        def delete_blanks!
            delete_if { |k, v| v.nil? }
        end
    end
    

    1c) Require this file (and your entire lib directory) into the rails referencing it in an initializer:

    config/boot.rb

    # other things such as gemfiles are required here, left out for brevity
    
    Dir['lib/**/*.rb'].each { |f| load(f) } # requires all .rb files in the lib directory 
    

    2) Inside the users#update action, implement our shiny new delete_blanks! class method to remove the attributes we're not updating from the params hash. Then, update the user instance via the update_attributes method, *not the update method!

    2a) Firstly, let's use the delete_blanks! method to fix our user_params hash:

    app/controllers/users_controller.rb

    new_params = user_params.delete_blanks!
    

    2b) And now let's update the instance using the update_attributes method, (again, not the update method):

    app/controllers/users_controller.rb

    @user.update_attributes(new_params)
    

    Here's how the finished users#update action should look:

    app/controllers/users_controller.rb

    def update
    
        new_params = user_params.delete_blanks!
    
        if @user.update_attributes(new_params)
            redirect_to @user, notice: 'User was successfully updated.'
        else
            render action: 'edit' // or whatever you want to do
        end
    end
    

    3) In the User model, add the if: :<attribute> option to all of your validations. This is to make sure the validation is only triggered if the attribute is present in the params hash. Our delete_blanks! method will have removed the attribute from the params hash, so the validation for password, for example, won't be run. It's also worth noting that delete_blanks! only removes hash entries with a value of nil, not those with empty strings. So if someone leaves out the password on the user create form (or any form with a field for the password), a presence validation will take effect because the :password entry of the hash won't be nil, it'll be an empty string:

    3a) Use the if: option on all validations:

    app/models/user.rb

    VALID_EMAIL_REGEX = /[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9\-.]/
    
    validates :first_name, presence: true, if: :first_name
    validates :last_name, presence: true, if: :last_name
    validates :user_name, presence: true, if: :user_name
    
    validates :email, presence: true, 
                      uniqueness: { case_sensitive: false },
                      format: { with: VALID_EMAIL_REGEX }, if: :email 
    
    validates :password, length: { minimum: 6, maximum: 10 }, if: :password
    

    And that's it. Now the user model can be updated over many, many different forms all over your app. Presence validations for an attribute still come into play on any form that contains a field for it, e.g. the password presence validation still would come into play in the user#create view.

    This may seem more verbose than other answers, but I believe this is the most robust way. You can update in isolation an infinite number of attributes for User instances, on an infinite amount of different models. Just remember when you want to do this with a new model you need to repeat the steps 2a), 2b) and 3a)

    0 讨论(0)
  • 2021-01-30 18:28
    # It smells
    
    def update
      if params[:user][:password].blank?
        params[:user].delete :password
        params[:user].delete :password_confirmation
      end
    
      if @user.update_attributes(params[:user])
        flash[:success] = "Edit Successful."
        redirect_to @user
      else
        @title = "Edit user"
        render 'edit'
      end
    end
    
    # Refactoring
    
    class User < ActiveRecord::Base
      ...
      def update_attributes(params)
        if params[:password].blank?
          params.delete :password
          params.delete :password_confirmation
          super params
        end
      end
      ...
    end
    
    def update
      if @user.update_attributes(params[:user])
        flash[:success] = "Edit Successful."
        redirect_to @user
      else
        @title = "Edit user"
        render 'edit'
      end
    end
    
    # And little better
    
    class User < ActiveRecord::Base
      ...
      def custom_update_attributes(params)
        if params[:password].blank?
          params.delete :password
          params.delete :password_confirmation
          update_attributes params
        end
      end
      ...
    end
    
    def update
      if @user.custom_update_attributes(params[:user])
        flash[:success] = "Edit Successful."
        redirect_to @user
      else
        @title = "Edit user"
        render 'edit'
      end
    end
    
    0 讨论(0)
提交回复
热议问题