I'm practicing the use of Rails active_storage
. So I created an app that only has one model - User
. The User
has two main db columns - username
and profile_pic
. Then through active_storage
it has_many_attached :avatars
. The feature I am implementing is that before_validation of the user record I want to assign the first avatar attached (user can upload many images upon signup) as the profile_pic
. Here's the complete model
class User < ApplicationRecord
include Rails.application.routes.url_helpers
has_many_attached :avatars
validates :username, presence: true
before_validation :assign_profile_pic
def change_profile_pic
self.profile_pic = rails_blob_path(avatars.last, only_path: true)
save
end
private
def assign_profile_pic
self.profile_pic = rails_blob_path(avatars.first, only_path: true)
end
end
Here's the users controller
class V1::UsersController < ApplicationController
# include Rails.application.routes.url_helpers
def show
user = User.find_by(username: params[:username])
if user.present?
render json: success_json(user), status: :ok
else
head :not_found
end
end
def create
user = User.new(user_params)
if user.save
render json: success_json(user), status: :created
else
render json: error_json(user), status: :unprocessable_entity
end
end
def update
user = User.find_by(username: params[:username])
if user.update(user_params)
user.change_profile_pic
render json: success_json(user), status: :accepted
else
render json: error_json(user), status: :unprocessable_entity
end
end
def avatar
user = User.find_by(username: params[:user_username])
if user&.avatars&.attached?
redirect_to rails_blob_url(user.avatars[params[:id].to_i])
else
head :not_found
end
end
private
def user_params
params.require(:user).permit(:username, avatars: [])
end
def success_json(user)
{
user: {
id: user.id,
username: user.username
}
}
end
def error_json(user)
{ errors: user.errors.full_messages }
end
end
Creating the user isn't a problem. It works as expected, it assigns user.avatars.first
automatically as the user.profile_pic
upon creation. The problem happens in the update
part. You see I am calling change_profile_pic
user method upon successful update (in the users_controller
). The problem is the user.profile_pic
never gets updated. I have debugged this many times and there's nothing wrong with the user_params
. What am I missing?
before_validation
runs every time before before_save
. You're setting it once, and then setting it again. See the order here: https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Avoid validating with assign_profile_pic
if you're merely changing profile pic. A clean way to do this is with ActiveModel::AttributeMethods
class User < ApplicationRecord
include Rails.application.routes.url_helpers
has_many_attached :avatars
validates :username, presence: true
before_validation :assign_profile_pic, unless: :changing_profile_pic?
attribute :changing_profile_pic, :boolean, default: false
def change_profile_pic
self.profile_pic = rails_blob_path(avatars.last, only_path: true)
self.changing_profile_pic = true
save
end
private
def assign_profile_pic
self.profile_pic = rails_blob_path(avatars.first, only_path: true)
end
end