Good morning,
I have an Ingredient controller which allows you to massively associate photos with a series of ingredients.
In a local development environment, everything works well. I use a tmp/uploads folder to store photos locally to finally import the selected photos at the end to Cloudinary via the associate_photos method in the service (I have also already tried via the public/uploads folder).
On Heroku, it’s impossible to move forward, I’ve been going around in circles for many hours, it seems like I can’t find the photos when I upload them in the associate_photos method. However, they are clearly visible on my “review_photos” view, even after 15 minutes, so there is no problem with immediate deletion of the contents of the tmp folder and which proves that these photos are indeed somewhere….
I know that Rails is not optimized for local storage, and I realize that everything will be deleted once a day or on a new release, but I only need to keep these images for 10 – 15 minutes maximum.
Why not upgrade to Cloudinary or AWS type storage? Because loading takes longer. If I load 50 images, but I decide to only keep 5, I will have to wait a long time to load the 50 images and finally only keep 5…
It seems that the files disappear between the review method and the associate method, but then how can I explain that if I go back in the browser, the images appear again and remain visible?
Code of Ingredient controller :
class IngredientsController < ApplicationController
include CloudinaryHelper
before_action :set_supplier, only: [:upload_photos, :review_photos]
skip_before_action :verify_authenticity_token, only: [:serve_temp_upload]
def upload_photos_form
authorize :ingredient
@suppliers = policy_scope(Supplier).order(:name)
end
def upload_photos
skip_authorization
@supplier = Supplier.find(params[:supplier_id])
photo_service = Ingredients::PhotoService.new(params, current_user)
@uploaded_photos = photo_service.upload_photos(@supplier)
# Log pour vérifier l'existence des fichiers
@uploaded_photos.each do |photo|
filepath = Rails.root.join('tmp', 'uploads', current_user.master_account_id.to_s, photo.filename)
Rails.logger.info "File exists: #{File.exist?(filepath)} - Path: #{filepath}"
end
sleep(1) # Ajoute un délai d'une seconde
redirect_to review_photos_ingredients_path(supplier_id: @supplier.id, uploaded_photos: @uploaded_photos.map(&:id))
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error "Validation failed: #{e.record.errors.full_messages.join(', ')}"
sleep(1) # Ajoute un délai d'une seconde en cas d'erreur
redirect_to ingredients_upload_photos_path(supplier_id: @supplier.id), alert: "Failed to upload photos. Please try again."
end
def review_photos
authorize :ingredient
@supplier = Supplier.find(params[:supplier_id])
photo_service = Ingredients::PhotoService.new(params, current_user)
result = photo_service.review_photos(params[:uploaded_photos], @supplier)
@supplier = result[:supplier]
@uploaded_photos = result[:uploaded_photos]
# Ajout de logs pour vérifier l'existence des fichiers
@uploaded_photos.each do |photo|
filepath = Rails.root.join('tmp', 'uploads', @current_user.master_account_id.to_s, photo.filename)
Rails.logger.info "Review File exists: #{File.exist?(filepath)} - Path: #{filepath}"
end
render :review_photos
end
def associate_photos
authorize :ingredient
# Passer les données nécessaires au job Sidekiq
AssociatePhotosJob.perform_later(current_user.id, photos_data_params)
redirect_to ingredients_path
end
def check_photo
authorize :ingredient
photo_service = Ingredients::PhotoService.new(params, current_user)
result = photo_service.check_photo(params[:id])
render json: result
end
def serve_temp_upload
skip_authorization
file_path = Rails.root.join('tmp', 'uploads', current_user.master_account_id.to_s, "#{params[:filename]}.#{params[:format]}")
# Ajoutez un log pour vérifier le chemin d'accès
Rails.logger.debug "Trying to access file at: #{file_path}"
if File.exist?(file_path)
send_file file_path, type: Mime::Type.lookup_by_extension(params[:format]), disposition: 'inline'
else
Rails.logger.error "File not found at: #{file_path}"
render plain: 'File not found', status: :not_found
end
end
private
def permit_photos_data
params.require(:photos_data).map do |photo|
photo.permit(:filename, :ingredient_id)
end
end
def photos_data_params
params.require(:photos_data).map do |photo|
photo.permit(:filename, :ingredient_id)
end
end
def set_supplier
@supplier = Supplier.find(params[:supplier_id]) if params[:supplier_id]
end
end
Photo Service :
module Ingredients
class PhotoService
include CloudinaryHelper
def initialize(params, current_user)
@params = params
@current_user = current_user
@uploaded_photos = []
@master_account_id = current_user.master_account_id
end
def upload_photos(supplier)
Rails.logger.info "############## START UPLOAD PHOTOS ##############"
uploaded_photos = []
uploads_dir = Rails.root.join('tmp', 'uploads', @master_account_id.to_s)
FileUtils.mkdir_p(uploads_dir)
clean_uploads_directory(uploads_dir)
@params[:photos].each do |photo|
next if photo.blank?
filename = SecureRandom.hex + File.extname(photo.original_filename)
filepath = uploads_dir.join(filename)
FileUtils.mkdir_p(File.dirname(filepath))
File.open(filepath, 'wb') do |file|
file.write(photo.read)
end
Rails.logger.info "##################### File saved at: #{filepath}"
photo_upload = PhotoUpload.new(
user: @current_user,
supplier: supplier,
filename: filename,
original_filename: photo.original_filename,
filepath: filepath.to_s
)
if photo_upload.save
uploaded_photos << photo_upload
else
Rails.logger.error "Failed to save photo_upload: #{photo_upload.errors.full_messages.join(', ')}"
end
end
Rails.logger.info "############## END UPLOAD PHOTOS ##############"
uploaded_photos
end
def review_photos(photo_ids, supplier)
Rails.logger.info "############## START REVIEW PHOTOS ##############"
@uploaded_photos = PhotoUpload.where(id: photo_ids, user: @current_user, supplier_id: supplier.id)
Rails.logger.info "############## Uploaded photos: #{@uploaded_photos}"
uploaded_files = Dir.glob(Rails.root.join('tmp', 'uploads', @master_account_id.to_s, '*'))
Rails.logger.info "############## Uploaded files: #{uploaded_files}"
@uploaded_photos.each do |photo|
filename_without_extension = File.basename(photo.original_filename, File.extname(photo.original_filename))
Rails.logger.info "############## Filename without extension: #{filename_without_extension}"
normalized_filename = I18n.transliterate(filename_without_extension.strip.downcase)
Rails.logger.info "############## Normalized filename: #{normalized_filename}"
matched_ingredient = supplier.ingredients.find do |ingredient|
normalized_ean_code = I18n.transliterate(ingredient.ean_code.strip.downcase) if ingredient.ean_code
normalized_supplier_code = I18n.transliterate(ingredient.supplier_code.strip.downcase) if ingredient.supplier_code
normalized_name_supplier_ref = I18n.transliterate(ingredient.name_supplier_ref.strip.downcase) if ingredient.name_supplier_ref
normalized_filename == normalized_ean_code ||
normalized_filename == normalized_supplier_code ||
normalized_filename == normalized_name_supplier_ref
end
photo.update(ingredient_id: matched_ingredient ? matched_ingredient.id : nil)
end
Rails.logger.info "############## END REVIEW PHOTOS ##############"
{
supplier: supplier,
uploaded_photos: @uploaded_photos
}
end
def associate_photos(photos_data)
Rails.logger.info "##### START ASSOCIATE PHOTOS ##############"
Rails.logger.info "##### Photos data: #{photos_data}"
associated_files = []
upload_path = Rails.root.join('tmp', 'uploads', @master_account_id.to_s)
Rails.logger.info "## Checking directory existence: #{upload_path}"
unless Dir.exist?(upload_path)
Rails.logger.error "###### Directory does not exist: #{upload_path}"
return []
end
uploaded_files = Dir.glob(upload_path.join('*'))
Rails.logger.info "###### Uploaded files: #{uploaded_files}"
photos_data.each do |photo_data|
filename = photo_data[:filename]
ingredient_id = photo_data[:ingredient_id]
photo = PhotoUpload.find_by(filename: filename, user: @current_user)
photo_path = Rails.root.join('tmp', 'uploads', @master_account_id.to_s, filename)
Rails.logger.info "###### Photo path: #{photo_path}"
Rails.logger.info "###### Files in directory: #{Dir.entries(upload_path)}"
if ingredient_id.present?
Rails.logger.info "######## Ingredient ID: #{ingredient_id}"
ingredient = Ingredient.find(ingredient_id)
if File.exist?(photo_path)
Rails.logger.info "###### File exists: #{photo_path}"
ingredient.photo.purge if ingredient.photo.attached?
# ingredient.photo.attach(io: File.open(photo_path), filename: filename, content_type: 'image/jpg')
content_type = Mime::Type.lookup_by_extension(File.extname(photo_path).delete('.')).to_s
Rails.logger.info "Content type: #{content_type}"
uploaded_file = File.open(photo_path)
Rails.logger.info "Uploaded file: #{uploaded_file}"
# Upload to Cloudinary
cloudinary_response = Cloudinary::Uploader.upload(uploaded_file, folder: "ingredients/#{ingredient.id}")
Rails.logger.info "Cloudinary response: #{cloudinary_response}"
ingredient.photo.attach(io: StringIO.new(cloudinary_response['url']), filename: filename, content_type: content_type)
associated_files << filename
Rails.logger.info "Successfully associated file: #{filename} with ingredient: #{ingredient_id}"
else
Rails.logger.error "File does not exist: #{photo_path}"
end
else
Rails.logger.error "Ingredient ID is missing for file: #{filename}"
end
File.delete(photo_path) if File.exist?(photo_path)
photo.destroy if photo
end
Rails.logger.info "############## END ASSOCIATE PHOTOS ##############"
associated_files
end
def check_photo(ingredient_id)
ingredient = Ingredient.find(ingredient_id)
has_photo = ingredient.photo.attached?
photo_url = has_photo ? cl_image_path(ingredient.photo.key, height: 190, crop: 'fit') : nil
{
has_photo: has_photo,
warning_message: I18n.t("Existing image will be overwritten"),
photo_url: photo_url,
ingredient_name: ingredient.name_short
}
end
def self.clean_uploads_directory(user)
tmp_dir = Rails.root.join('tmp', 'uploads', user.master_account_id.to_s)
FileUtils.rm_rf(Dir.glob("#{tmp_dir}/*"))
end
private
def clean_uploads_directory(uploads_dir)
return unless File.directory?(uploads_dir)
Dir.foreach(uploads_dir) do |filename|
file_path = File.join(uploads_dir, filename)
File.delete(file_path) if File.file?(file_path)
end
end
end
end
Code of Job :
class AssociatePhotosJob < ApplicationJob
queue_as :default
def perform(user_id, photos_data)
user = User.find(user_id)
NotificationChannel.broadcast_to(user, message: I18n.t("The photo import has just started, and you'll receive a warning when it's finished."))
photo_service = Ingredients::PhotoService.new({}, user)
photo_service.associate_photos(photos_data)
NotificationChannel.broadcast_to(user, message: I18n.t('Photos successfully associated with ingredients'))
end
end
Logs in console heroku for uploads / review in service :
2024-06-27T10:33:47.250596+00:00 app[web.1]: I, [2024-06-27T10:33:47.250506 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] Started POST "/en/ingredients/upload_photos" for 85.26.49.18 at 2024-06-27 10:33:47 +0000
2024-06-27T10:33:47.251825+00:00 app[web.1]: I, [2024-06-27T10:33:47.251760 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] Processing by IngredientsController#upload_photos as HTML
2024-06-27T10:33:47.251894+00:00 app[web.1]: I, [2024-06-27T10:33:47.251861 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] Parameters: {"authenticity_token"=>"[FILTERED]", "photos"=>["", #<ActionDispatch::Http::UploadedFile:0x00007fafc5de8bc8 @tempfile=#<Tempfile:/tmp/RackMultipart20240627-2-en0og5.jpg>, @content_type="image/jpeg", @original_filename="151310.jpg", @headers="Content-Disposition: form-data; name="photos[]"; filename="151310.jpg"rnContent-Type: image/jpegrn">, #<ActionDispatch::Http::UploadedFile:0x00007fafc5de8a88 @tempfile=#<Tempfile:/tmp/RackMultipart20240627-2-uy1qjz.jpg>, @content_type="image/jpeg", @original_filename="151311.jpg", @headers="Content-Disposition: form-data; name="photos[]"; filename="151311.jpg"rnContent-Type: image/jpegrn">], "supplier_id"=>"1", "commit"=>"Upload", "locale"=>"en"}
2024-06-27T10:33:47.256844+00:00 app[web.1]: I, [2024-06-27T10:33:47.256785 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] ############## START UPLOAD PHOTOS ##############
2024-06-27T10:33:47.257907+00:00 app[web.1]: I, [2024-06-27T10:33:47.257849 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] ##################### File saved at: /app/tmp/uploads/2/fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg
2024-06-27T10:33:47.263100+00:00 app[web.1]: I, [2024-06-27T10:33:47.263048 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] ##################### File saved at: /app/tmp/uploads/2/829217fbf3d58d69de8acc35ec3f77ba.jpg
2024-06-27T10:33:47.267099+00:00 app[web.1]: I, [2024-06-27T10:33:47.267043 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] ############## END UPLOAD PHOTOS ##############
2024-06-27T10:33:47.267226+00:00 app[web.1]: I, [2024-06-27T10:33:47.267187 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] File exists: true - Path: /app/tmp/uploads/2/fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg
2024-06-27T10:33:47.267304+00:00 app[web.1]: I, [2024-06-27T10:33:47.267282 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] File exists: true - Path: /app/tmp/uploads/2/829217fbf3d58d69de8acc35ec3f77ba.jpg
2024-06-27T10:33:48.268115+00:00 app[web.1]: I, [2024-06-27T10:33:48.267991 #2] INFO -- : [07825ef4-2519-4ff2-899c-0a58cf1f051a] Redirected to https://gestion-restaurant-2c24499aa5da.herokuapp.com/en/ingredients/review_photos?supplier_id=1&uploaded_photos%5B%5D=107&uploaded_photos%5B%5D=108
2024-06-27T10:33:48.369422+00:00 app[web.1]: I, [2024-06-27T10:33:48.369338 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Started GET "/en/ingredients/review_photos?supplier_id=1&uploaded_photos%5B%5D=107&uploaded_photos%5B%5D=108" for 85.26.49.18 at 2024-06-27 10:33:48 +0000
2024-06-27T10:33:48.370621+00:00 app[web.1]: I, [2024-06-27T10:33:48.370549 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Processing by IngredientsController#review_photos as HTML
2024-06-27T10:33:48.370638+00:00 app[web.1]: I, [2024-06-27T10:33:48.370616 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Parameters: {"supplier_id"=>"1", "uploaded_photos"=>["107", "108"], "locale"=>"en"}
2024-06-27T10:33:48.376140+00:00 app[web.1]: I, [2024-06-27T10:33:48.376089 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## START REVIEW PHOTOS ##############
2024-06-27T10:33:48.376618+00:00 app[web.1]: I, [2024-06-27T10:33:48.376580 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## Uploaded photos: #<PhotoUpload::ActiveRecord_Relation:0x00007fafc5d73d28>
2024-06-27T10:33:48.376777+00:00 app[web.1]: I, [2024-06-27T10:33:48.376752 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## Uploaded files: ["/app/tmp/uploads/2/829217fbf3d58d69de8acc35ec3f77ba.jpg", "/app/tmp/uploads/2/fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg"]
2024-06-27T10:33:48.378214+00:00 app[web.1]: I, [2024-06-27T10:33:48.378170 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## Filename without extension: 151310
2024-06-27T10:33:48.378253+00:00 app[web.1]: I, [2024-06-27T10:33:48.378232 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## Normalized filename: 151310
2024-06-27T10:33:48.390831+00:00 app[web.1]: I, [2024-06-27T10:33:48.390781 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## Filename without extension: 151311
2024-06-27T10:33:48.390890+00:00 app[web.1]: I, [2024-06-27T10:33:48.390866 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## Normalized filename: 151311
2024-06-27T10:33:48.397925+00:00 app[web.1]: I, [2024-06-27T10:33:48.397871 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] ############## END REVIEW PHOTOS ##############
2024-06-27T10:33:48.398038+00:00 app[web.1]: I, [2024-06-27T10:33:48.398013 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Review File exists: true - Path: /app/tmp/uploads/2/fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg
2024-06-27T10:33:48.398088+00:00 app[web.1]: I, [2024-06-27T10:33:48.398071 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Review File exists: true - Path: /app/tmp/uploads/2/829217fbf3d58d69de8acc35ec3f77ba.jpg
2024-06-27T10:33:48.402857+00:00 app[web.1]: I, [2024-06-27T10:33:48.402803 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Rendered ingredients/review_photos.erb within layouts/application (Duration: 4.4ms | Allocations: 7521)
2024-06-27T10:33:48.445188+00:00 app[web.1]: I, [2024-06-27T10:33:48.445121 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Rendered layout layouts/application.html.erb (Duration: 46.7ms | Allocations: 21134)
2024-06-27T10:33:48.445745+00:00 app[web.1]: I, [2024-06-27T10:33:48.445663 #2] INFO -- : [11d8db07-a3b2-4d42-9c67-874c590389df] Completed 200 OK in 75ms (Views: 25.3ms | ActiveRecord: 34.9ms | Allocations: 32066)
2024-06-27T10:33:48.682193+00:00 app[web.1]: I, [2024-06-27T10:33:48.682106 #2] INFO -- : [ab66b32c-86c1-4ee2-afb3-5bdaae1837a5] Started GET "/en/serve_temp_upload/fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg" for 85.26.49.18 at 2024-06-27 10:33:48 +0000
2024-06-27T10:33:48.683232+00:00 app[web.1]: I, [2024-06-27T10:33:48.683096 #2] INFO -- : [ab66b32c-86c1-4ee2-afb3-5bdaae1837a5] Processing by IngredientsController#serve_temp_upload as JPEG
2024-06-27T10:33:48.683233+00:00 app[web.1]: I, [2024-06-27T10:33:48.683157 #2] INFO -- : [ab66b32c-86c1-4ee2-afb3-5bdaae1837a5] Parameters: {"locale"=>"en", "filename"=>"fd110c8b8c205bfa1b3b064ad0ee9ed9"}
2024-06-27T10:33:48.686372+00:00 app[web.1]: I, [2024-06-27T10:33:48.686318 #2] INFO -- : [ab66b32c-86c1-4ee2-afb3-5bdaae1837a5] Sent file /app/tmp/uploads/2/fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg (0.1ms)
2024-06-27T10:33:48.686481+00:00 app[web.1]: I, [2024-06-27T10:33:48.686458 #2] INFO -- : [ab66b32c-86c1-4ee2-afb3-5bdaae1837a5] Completed 200 OK in 3ms (ActiveRecord: 0.8ms | Allocations: 820)
2024-06-27T10:33:48.785761+00:00 app[web.1]: I, [2024-06-27T10:33:48.785668 #2] INFO -- : [9e9ac644-4789-4652-981e-ca412ad173c0] Started GET "/en/serve_temp_upload/829217fbf3d58d69de8acc35ec3f77ba.jpg" for 85.26.49.18 at 2024-06-27 10:33:48 +0000
2024-06-27T10:33:48.786956+00:00 app[web.1]: I, [2024-06-27T10:33:48.786890 #2] INFO -- : [9e9ac644-4789-4652-981e-ca412ad173c0] Processing by IngredientsController#serve_temp_upload as JPEG
2024-06-27T10:33:48.786984+00:00 app[web.1]: I, [2024-06-27T10:33:48.786956 #2] INFO -- : [9e9ac644-4789-4652-981e-ca412ad173c0] Parameters: {"locale"=>"en", "filename"=>"829217fbf3d58d69de8acc35ec3f77ba"}
2024-06-27T10:33:48.790722+00:00 app[web.1]: I, [2024-06-27T10:33:48.790663 #2] INFO -- : [9e9ac644-4789-4652-981e-ca412ad173c0] Sent file /app/tmp/uploads/2/829217fbf3d58d69de8acc35ec3f77ba.jpg (0.2ms)
2024-06-27T10:33:48.790858+00:00 app[web.1]: I, [2024-06-27T10:33:48.790828 #2] INFO -- : [9e9ac644-4789-4652-981e-ca412ad173c0] Completed 200 OK in 4ms (ActiveRecord: 0.7ms | Allocations: 804)
2024-06-27T10:33:49.185975+00:00 app[web.1]: I, [2024-06-27T10:33:49.185924 #2] INFO -- : [9b9ee34d-93fb-4b56-ad67-2ae128055836] Started GET "/ingredients/97/check_photo" for 85.26.49.18 at 2024-06-27 10:33:49 +0000
2024-06-27T10:33:49.186966+00:00 app[web.1]: I, [2024-06-27T10:33:49.186911 #2] INFO -- : [9b9ee34d-93fb-4b56-ad67-2ae128055836] Processing by IngredientsController#check_photo as */*
2024-06-27T10:33:49.186985+00:00 app[web.1]: I, [2024-06-27T10:33:49.186966 #2] INFO -- : [9b9ee34d-93fb-4b56-ad67-2ae128055836] Parameters: {"id"=>"97"}
2024-06-27T10:33:49.188472+00:00 app[web.1]: I, [2024-06-27T10:33:49.188422 #2] INFO -- : Registered connection (Z2lkOi8vZ2VzdGlvbi9Vc2VyLzM)
2024-06-27T10:33:49.195774+00:00 app[web.1]: I, [2024-06-27T10:33:49.195719 #2] INFO -- : [97a632d2-2547-452b-ade4-af37a2aa3a10] Started GET "/ingredients/94/check_photo" for 85.26.49.18 at 2024-06-27 10:33:49 +0000
2024-06-27T10:33:49.196575+00:00 app[web.1]: I, [2024-06-27T10:33:49.196506 #2] INFO -- : [97a632d2-2547-452b-ade4-af37a2aa3a10] Processing by IngredientsController#check_photo as */*
2024-06-27T10:33:49.196608+00:00 app[web.1]: I, [2024-06-27T10:33:49.196573 #2] INFO -- : [97a632d2-2547-452b-ade4-af37a2aa3a10] Parameters: {"id"=>"94"}
2024-06-27T10:33:49.205060+00:00 app[web.1]: I, [2024-06-27T10:33:49.205014 #2] INFO -- : [97a632d2-2547-452b-ade4-af37a2aa3a10] Completed 200 OK in 8ms (Views: 0.2ms | ActiveRecord: 3.2ms | Allocations: 1768)
After this step, I see well the images in navigator in page review_photos. If I wait 15 minutes and refresh, I still see photos.
Logs in console when i click on associate :
2024-06-27T10:35:46.247239+00:00 heroku[router]: at=info method=POST path="/en/ingredients/associate_photos" host=gestion-restaurant-2c24499aa5da.herokuapp.com request_id=18b1d3db-be1e-462e-951f-fb037f1db53c fwd="85.26.49.18" dyno=web.1 connect=0ms service=21ms status=302 bytes=1241 protocol=https
2024-06-27T10:35:46.227028+00:00 app[web.1]: I, [2024-06-27T10:35:46.226936 #2] INFO -- : [18b1d3db-be1e-462e-951f-fb037f1db53c] Started POST "/en/ingredients/associate_photos" for 85.26.49.18 at 2024-06-27 10:35:46 +0000
2024-06-27T10:35:46.228061+00:00 app[web.1]: I, [2024-06-27T10:35:46.227997 #2] INFO -- : [18b1d3db-be1e-462e-951f-fb037f1db53c] Processing by IngredientsController#associate_photos as TURBO_STREAM
2024-06-27T10:35:46.228106+00:00 app[web.1]: I, [2024-06-27T10:35:46.228071 #2] INFO -- : [18b1d3db-be1e-462e-951f-fb037f1db53c] Parameters: {"authenticity_token"=>"[FILTERED]", "photos_data"=>[{"filename"=>"fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg", "ingredient_id"=>"97"}, {"filename"=>"829217fbf3d58d69de8acc35ec3f77ba.jpg", "ingredient_id"=>"94"}], "commit"=>"Associate photos", "locale"=>"en"}
2024-06-27T10:35:46.242823+00:00 app[web.1]: I, [2024-06-27T10:35:46.242742 #2] INFO -- : [18b1d3db-be1e-462e-951f-fb037f1db53c] [ActiveJob] Enqueued AssociatePhotosJob (Job ID: f0778fa1-e2b8-41ca-91cf-29e4c6503893) to Sidekiq(default) with arguments: 3, [#<ActionController::Parameters {"filename"=>"fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg", "ingredient_id"=>"97"} permitted: true>, #<ActionController::Parameters {"filename"=>"829217fbf3d58d69de8acc35ec3f77ba.jpg", "ingredient_id"=>"94"} permitted: true>]
2024-06-27T10:35:46.243223+00:00 app[web.1]: I, [2024-06-27T10:35:46.243159 #2] INFO -- : [18b1d3db-be1e-462e-951f-fb037f1db53c] Redirected to https://gestion-restaurant-2c24499aa5da.herokuapp.com/en/ingredients
2024-06-27T10:35:46.426921+00:00 app[web.1]: I, [2024-06-27T10:35:46.426812 #2] INFO -- : [a57b9326-db06-4f93-b7f6-a22772909aba] Started GET "/en/ingredients" for 85.26.49.18 at 2024-06-27 10:35:46 +0000
2024-06-27T10:35:46.428076+00:00 app[web.1]: I, [2024-06-27T10:35:46.428005 #2] INFO -- : [a57b9326-db06-4f93-b7f6-a22772909aba] Processing by IngredientsController#index as TURBO_STREAM
2024-06-27T10:35:46.428086+00:00 app[web.1]: I, [2024-06-27T10:35:46.428069 #2] INFO -- : [a57b9326-db06-4f93-b7f6-a22772909aba] Parameters: {"locale"=>"en"}
2024-06-27T10:35:46.242961+00:00 app[worker.1]: pid=2 tid=1okm class=AssociatePhotosJob jid=ac84b420282a6df91872f1e2 INFO: start
2024-06-27T10:35:46.243930+00:00 app[worker.1]: I, [2024-06-27T10:35:46.243815 #2] INFO -- : [ActiveJob] [AssociatePhotosJob] [f0778fa1-e2b8-41ca-91cf-29e4c6503893] Performing AssociatePhotosJob (Job ID: f0778fa1-e2b8-41ca-91cf-29e4c6503893) from Sidekiq(default) enqueued at 2024-06-27T10:35:46Z with arguments: 3, [{"filename"=>"fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg", "ingredient_id"=>"97"}, {"filename"=>"829217fbf3d58d69de8acc35ec3f77ba.jpg", "ingredient_id"=>"94"}]
2024-06-27T10:35:46.279922+00:00 app[worker.1]: I, [2024-06-27T10:35:46.279808 #2] INFO -- : [ActiveJob] [AssociatePhotosJob] [f0778fa1-e2b8-41ca-91cf-29e4c6503893] ############## START ASSOCIATE PHOTOS ##############
2024-06-27T10:35:46.279956+00:00 app[worker.1]: I, [2024-06-27T10:35:46.279926 #2] INFO -- : [ActiveJob] [AssociatePhotosJob] [f0778fa1-e2b8-41ca-91cf-29e4c6503893] ############## Photos data: [{"filename"=>"fd110c8b8c205bfa1b3b064ad0ee9ed9.jpg", "ingredient_id"=>"97"}, {"filename"=>"829217fbf3d58d69de8acc35ec3f77ba.jpg", "ingredient_id"=>"94"}]
2024-06-27T10:35:46.280020+00:00 app[worker.1]: I, [2024-06-27T10:35:46.279997 #2] INFO -- : [ActiveJob] [AssociatePhotosJob] [f0778fa1-e2b8-41ca-91cf-29e4c6503893] ############## Checking directory existence: /app/tmp/uploads/2
2024-06-27T10:35:46.280047+00:00 app[worker.1]: E, [2024-06-27T10:35:46.280028 #2] ERROR -- : [ActiveJob] [AssociatePhotosJob] [f0778fa1-e2b8-41ca-91cf-29e4c6503893] ############## Directory does not exist: /app/tmp/uploads/2
2024-06-27T10:35:46.281183+00:00 app[worker.1]: I, [2024-06-27T10:35:46.281110 #2] INFO -- : [ActiveJob] [AssociatePhotosJob] [f0778fa1-e2b8-41ca-91cf-29e4c6503893] Performed AssociatePhotosJob (Job ID: f0778fa1-e2b8-41ca-91cf-29e4c6503893) from Sidekiq(default) in 37.46ms
2024-06-27T10:35:46.282366+00:00 app[worker.1]: I, [2024-06-27T10:35:46.282307 #2] INFO -- sentry: [Transport] Sending envelope with items [transaction, client_report] ee732149c355405588380b72f3b44131 to Sentry
2024-06-27T10:35:46.283312+00:00 app[worker.1]: pid=2 tid=1okm class=AssociatePhotosJob jid=ac84b420282a6df91872f1e2 elapsed=0.039 INFO: done
2024-06-27T10:35:46.568304+00:00 app[web.1]: I, [2024-06-27T10:35:46.568211 #2] INFO -- : [a57b9326-db06-4f93-b7f6-a22772909aba] Rendered ingredients/index.html.erb within layouts/application (Duration: 130.3ms | Allocations: 54504)
2024-06-27T10:35:46.611448+00:00 app[web.1]: I, [2024-06-27T10:35:46.611347 #2] INFO -- : [a57b9326-db06-4f93-b7f6-a22772909aba] Rendered layout layouts/application.html.erb (Duration: 173.4ms | Allocations: 67800)
2024-06-27T10:35:46.611678+00:00 app[web.1]: I, [2024-06-27T10:35:46.611644 #2] INFO -- : [a57b9326-db06-4f93-b7f6-a22772909aba] Completed 200 OK in 183ms (Views: 115.1ms | ActiveRecord: 62.0ms | Allocations: 70203)
2024-06-27T10:35:46.616105+00:00 app[web.1]: I, [2024-06-27T10:35:46.616026 #2] INFO -- sentry:
Logs in dev environment on local machine when I clic on associate :
024-06-27T08:59:55.593Z pid=27250 tid=2ny class=ActiveStorage::AnalyzeJob jid=dafd2604d2e1e81d599c82cb INFO: start
2024-06-27T08:59:55.595Z pid=27250 tid=2hi class=AssociatePhotosJob jid=31d7031ada10569c22cff418 INFO: Enqueued ActiveStorage::AnalyzeJob (Job ID: b862e0ed-d028-4dc7-bbc8-9c7de66ca353) to Sidekiq(default) with arguments: #<GlobalID:0x00007fb012ec5b98 @uri=#<URI::GID gid://gestion/ActiveStorage::Blob/522>>
2024-06-27T08:59:56.428Z pid=27250 tid=2hi class=AssociatePhotosJob jid=31d7031ada10569c22cff418 INFO: Cloudinary Storage (781.9ms) Deleted file from key: 8aw1ytl9zy7yc7j3s7mxchsletp1
2024-06-27T08:59:58.762Z pid=27250 tid=2hi class=AssociatePhotosJob jid=31d7031ada10569c22cff418 INFO: Cloudinary Storage (2296.5ms) Uploaded file to key: 64v6cs5m6zxxtukhn4jw9dccet65 (checksum: EtNII0egV28Jr8KCPYFZLw==)
2024-06-27T08:59:58.764Z pid=27250 tid=2h6 class=ActiveStorage::AnalyzeJob jid=e4d49d45a9bfc311b5d639dc INFO: start
2024-06-27T08:59:58.765Z pid=27250 tid=2hi class=AssociatePhotosJob jid=31d7031ada10569c22cff418 INFO: Enqueued ActiveStorage::AnalyzeJob (Job ID: 3aee7198-a22a-45f6-a2f8-6fbcfc5ed054) to Sidekiq(default) with arguments: #<GlobalID:0x00007fb012d40a70 @uri=#<URI::GID gid://gestion/ActiveStorage::Blob/523>>
2024-06-27T08:59:58.787Z pid=27250 tid=2hi class=AssociatePhotosJob jid=31d7031ada10569c22cff418 INFO: Performed AssociatePhotosJob (Job ID: 248a1e9d-c02f-4b9b-8b96-2ede80242603) from Sidekiq(default) in 5745.35ms
2024-06-27T08:59:58.791Z pid=27250 tid=2hi class=AssociatePhotosJob jid=31d7031ada10569c22cff418 elapsed=6.16 INFO: done
Thank a lot