Home > Blockchain >  Check to find out if any file has been added to file_field before update, Ruby on Rails 7
Check to find out if any file has been added to file_field before update, Ruby on Rails 7

Time:09-07

I am new to Rails and in this project I am suppose to build a program that allows you to create user accounts and upload profile pictures to it. Then it suppose to send you an email to notify that your upload was succesfull.

  • User Accounts are handled with Devise
  • Image uploads are handled with Active Storage avatar attachments

On Devise, these kind of attachment uploads are handled in Edit Page, which is connected to Devise::RegistrationsController :

# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

  # POST /resource
  # def create
  #   super
  # end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  def update
    self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
    prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)

    resource_updated = update_resource(resource, account_update_params)
    yield resource if block_given?
    if resource_updated
      set_flash_message_for_update(resource, prev_unconfirmed_email)
      bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?

      respond_with resource, location: after_update_path_for(resource)
    else
      clean_up_passwords resource
      set_minimum_password_length
      respond_with resource
    end
    binding.irb

    # PAY ATTENTION TO HERE

    if @user.avatar.attached?
      UserMailer.upload_email(@user).deliver
    end
  end

  

  # DELETE /resource
  # def destroy
  #   @user = User.find(params[:user_id])
  #   puts @user
  #   if @user.destroy
  #     redirect_to root_path
  #   end
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [])
  end

  # If you have extra params to permit, append them to the sanitizer.
  def configure_account_update_params
    devise_parameter_sanitizer.permit(:account_update, keys: [:avatar])
  end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end

edit.html.erb :

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  # PAY ATTENTION TO HERE

  <div >
    <%= f.label :avatar %><br />
    <%= f.file_field :avatar %>
  </div>

<div >
    <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password, autocomplete: "current-password" %>
  </div>

  <div >
    <%= f.submit "Update"%>
  </div>
<% end %>

I want you to pay attention to the update action in registrations_controller, especially to the line that goes like:

if @user.avatar.attached?
      UserMailer.upload_email(@user).deliver
end

Now this is completely wrong, even though it sends email the first time you upload an image, after that it still sends emails once you click "update" without uploading any attachments, because the only condition is "avatar.attached?", which returns true since there is already an image attached.

Now, thanks to the binding.irb, I can check what is passed to update as params when there is a file added and when there is not.

  • When there is a file added:
irb(#<Users::RegistrationsController:0x0000023b767c6790>):001:0> params
=> #<ActionController::Parameters {"_method"=>"put", "authenticity_token"=>"6O0jz5U2XWfb4pu_An3pMh1BUq5o71n0U2_E3zWy0-efqm0orl_Vi5o_jo1B2o4Oc11AyRt-Zd6XBPJnmXpr8w", "user"=>#<ActionController::Parameters {"email"=>"***@hotmail.com", "avatar"=>#<ActionDispatch::Http::UploadedFile:0x0000023b767c5660 @tempfile=#<Tempfile:C:/Users/pc/AppData/Local/Temp/RackMultipart20220903-13632-upo8fa.png>, @original_filename="WhatsApp Image 2022-08-06 at 23.03.10.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"user[avatar]\"; filename=\"WhatsApp Image 2022-08-06 at 23.03.10.png\"\r\nContent-Type: image/png\r\n">, "password"=>"", "password_confirmation"=>"", "current_password"=>"password"} permitted: false>, "commit"=>"Update", "controller"=>"users/registrations", "action"=>"update"} permitted: false>

  • When there is not:
irb(#<Users::RegistrationsController:0x0000023b763ea950>):001:0> params
=> #<ActionController::Parameters {"_method"=>"put", "authenticity_token"=>"NCJA7QSTCuAVdogoAN9Z96B4EsJIRoGQyvwmNPVqvu1DZQ4KP_qCDFSrnRpDeD7LzmQApTvXvboOlxCMWaIG-Q", "user"=>#<ActionController::Parameters {"email"=>"***@hotmail.com", "password"=>"", "password_confirmation"=>"", "current_password"=>"123456"} permitted: false>, "commit"=>"Update", "controller"=>"users/registrations", "action"=>"update"} permitted: false>

As you can see, when there is a file added to f.file_field, you can see things like "avatar" "@tempfile" "original_filename" in the params.

So my logic is that before update action, I should check if there is a file added to f.file_field or not. If there is a file added to it, once the User is updated THEN I should send an email to notify it is succesfully uploaded but I dont know how to do that. Any ideas?

  • Rest of the relevant parts of my code is like :

UsersController:

class UsersController < ApplicationController
  before_action :set_user
  
  def profile;end
  
  
  
  private
  def set_user
    @user = User.find(params[:id])
  end
end

User Model (user.rb):

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  
  has_one_attached :avatar
end

My Tables (schema.rb) :

ActiveRecord::Schema[7.0].define(version: 2022_08_28_175416) do
  create_table "active_storage_attachments", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end

  create_table "active_storage_blobs", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.string "service_name", null: false
    t.bigint "byte_size", null: false
    t.string "checksum"
    t.datetime "created_at", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end

  create_table "active_storage_variant_records", force: :cascade do |t|
    t.bigint "blob_id", null: false
    t.string "variation_digest", null: false
    t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
  add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
end

CodePudding user response:

There are several ways to do this. But my suggestion is to create an after_save callback on the User model, something like this:

after_save :notify_new_avatar

def notify_new_avatar
  notify if new_avatar?
end

def notify
  UserMailer.notify_new_avatar
end

def new_avatar?
  # how do we determine this?
end

To determine if a new avatar was uploaded, I suggest using the features of Active Model Dirty.

I'm not clear what the attribute is that connotes an avatar change in your app, but User.avatar_id_previously_changed? will probably give you the detection you need.

  • Related