ruby-on-railsrubyactiverecordactive-model-serializers

Why does Rails rollback even though the model exists?


I am trying to find out why my update method is not working in my Rails API. It should update the bio field. I have my API hosted on Heroku and am using the Heroku logs to debug in production. I used the exists? method to make sure the user is in the db and yet when the update method is called it rollsback after doing this check. I don't understand what is the cause of this?

Here are the Heroku logs of the output

2022-04-15T02:54:34.083586+00:00 app[web.1]: I, [2022-04-15T02:54:34.083515 #4]  INFO -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d] Started PATCH "/users/8" for 98.248.0.125 at 2022-04-15 02:54:34 +0000
2022-04-15T02:54:34.084345+00:00 app[web.1]: I, [2022-04-15T02:54:34.084290 #4]  INFO -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d] Processing by UsersController#update as HTML
2022-04-15T02:54:34.084376+00:00 app[web.1]: I, [2022-04-15T02:54:34.084350 #4]  INFO -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d]   Parameters: {"bio"=>"test", "id"=>"8", "user"=>{"bio"=>"test"}}
2022-04-15T02:54:34.087450+00:00 app[web.1]: D, [2022-04-15T02:54:34.087403 #4] DEBUG -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d]   User Load (1.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 8], ["LIMIT", 1]]
2022-04-15T02:54:34.089711+00:00 app[web.1]: D, [2022-04-15T02:54:34.089664 #4] DEBUG -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d]   User Exists? (1.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 8], ["LIMIT", 1]]
2022-04-15T02:54:34.092004+00:00 app[web.1]: D, [2022-04-15T02:54:34.091963 #4] DEBUG -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d]   TRANSACTION (0.9ms)  BEGIN
2022-04-15T02:54:34.093523+00:00 app[web.1]: D, [2022-04-15T02:54:34.093465 #4] DEBUG -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d]   User Exists? (1.4ms)  SELECT 1 AS one FROM "users" WHERE "users"."username" = $1 AND "users"."id" != $2 LIMIT $3  [["username", "newperson"], ["id", 8], ["LIMIT", 1]]
2022-04-15T02:54:34.095530+00:00 app[web.1]: D, [2022-04-15T02:54:34.095493 #4] DEBUG -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d]   TRANSACTION (0.9ms)  ROLLBACK
2022-04-15T02:54:34.096881+00:00 app[web.1]: I, [2022-04-15T02:54:34.096842 #4]  INFO -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d] [active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (0.07ms)
2022-04-15T02:54:34.097078+00:00 app[web.1]: I, [2022-04-15T02:54:34.097050 #4]  INFO -- : [9e5ea776-5b15-420f-8cdc-0601480e0e3d] Completed 422 Unprocessable Entity in 13ms (Views: 0.6ms | ActiveRecord: 5.5ms | Allocations: 2816)
2022-04-15T02:54:34.101664+00:00 heroku[router]: at=info method=PATCH path="/users/8" host=anime-axis-api.herokuapp.com request_id=9e5ea776-5b15-420f-8cdc-0601480e0e3d fwd="98.248.0.125" dyno=web.1 connect=0ms service=17ms status=422 bytes=1096 protocol=https

Here is my update method:

def update
  if User.exists?(8)
    @current_user.update!(user_params)
    render json: @current_user, status: :ok
  end
end

private

def user_params
  # added require
  params.require(:user).permit(:username, :password, :password_confirmation, :bio, :avatar, :email)
end

My User model:

class User < ApplicationRecord
  has_secure_password
  has_many :anime_lists
  has_many :animes, through: :anime_lists
  has_many :manga_lists
  has_many :mangas, through: :manga_lists

  validates :username, presence: true, confirmation: {case_sensitive: false}, uniqueness: true, length: {in: 6..30}
  validates :password, presence: true, confirmation: true
end

Solution

  • I think your validations are the issue. Password presence is validating on every update. Since User#password is nil and you don't have a password in your params, it fails.

    class User < ApplicationRecord
      has_secure_password
      validates :password, presence: true, confirmation: true
    end
    
    >> User.create(email: 'test@user.com', password: '123456');
    >> User.first.update!(email: 'test@user.com')
      User Load (0.8ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
    ActiveRecord::RecordInvalid: Validation failed: Password can't be blank
    

    has_secure_password also adds its own validations.

    https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password

    If you want to customize password validations use

    has_secure_password validations: false
    # + your password validations
    

    To get some ideas, you can take a look at how devise does validations:

    https://github.com/heartcombo/devise/blob/v4.8.1/lib/devise/models/validatable.rb#L60