Search code examples
ruby-on-railsactiverecord

Unable to use Active Record's update method on a single attribute. Is it due to callbacks or validations?


In my users_controller.rb, I have a method where I want to update just one field: confirmation_token. This worked perfectly fine with this schema:

ActiveRecord::Schema[7.0].define(version: 2023_08_04_115747) do
  create_table "users", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
    t.string "email"
    t.string "role"
    t.string "confirmation_token"
    t.boolean "confirmed"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "password_digest"
  end

end

And with this method:

def request_reset_password
    email = params[:email]
    @user_email = User.find_by(email: params[:email])
    @user = User.find(@user_email.id)
    if @user && @user.confirmed == true && @user.confirmation_token == nil
      @user.confirmation_token = SecureRandom.hex(4)
      @user.update(confirmation_token: @user.confirmation_token)
      UserMailer.with(user: @user).request_password_reset.deliver_now
      redirect_to root_path, notice: "Email sent successfully."
    else
      redirect_to root_path, alert: "Email doesn't exist or is not confirmed yet."
    end
end

Once I created a migration to add the username field on the User model, I am not able to update this record, the DB will rollback at the same request. The only workaround is to use update_columns, to bypass validation. For the love of God, I can't understand which validations hinder the update method. And what is even stranger is that the UserMailer sends the mail, even if the record wasn't updated.

Would be really grateful to anyone who can enlighten me, as I am pretty sure I am missing something relatively simple.

For context, here are my validations:

validates :email, presence: true, uniqueness: true, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i, message: "is not a valid email address"}
validates :password, presence: true, length: { minimum: 8}
validates :username, presence: true, uniqueness: true, length: { minimum: 3, maximum: 20, message: "must be between 3 and 20 characters"}

Solution

  • Apparently the password validation failed. The solution was to skip the validation if the record exists with the following method:

    def should_validate_password?
        new_record? || password.present?
    end