Why do I get `ActiveStorage::FileNotFoundError` when trying to seed the db with attachments?
Asked Answered
H

4

6

I'm trying to seed my development database. One of the models Project has images associated with it.

I have put a placeholder image in ./db/seed_files/. My seed file looks like this:

# Add projects
1000.times do
  project = Project.new(
    name: Faker::Marketing.buzzwords.capitalize,
    description: Faker::Lorem.sentence(rand(1..30))
  )
  image_file = File.open("./db/seed_files/placeholder_image.png")
  project.images.attach(io: image_file, filename: "placeholder_image.png", content_type: "image/png")
  project.save
end

This runs fine. It attaches one image to each project.

However, I want to seed each project with multiple images. I thought I could attach the same image multiple times.

I have tried:

# Add projects
1000.times do
  project = Project.new(
    name: Faker::Marketing.buzzwords.capitalize,
    description: Faker::Lorem.sentence(rand(1..30))
  )
  image_file = File.open("./db/seed_files/placeholder_image.png")
  rand(1..3).times do
    project.images.attach(io: image_file, filename: "placeholder_image.png", content_type: "image/png")
  end
  project.save
end

But this results in an error: ActiveStorage::FileNotFoundError.

/Users/greidods/.rvm/gems/ruby-2.6.1/bundler/gems/rails-b366be3b5b28/activestorage/lib/active_storage/service/disk_service.rb:136:in `rescue in stream'
/Users/greidods/.rvm/gems/ruby-2.6.1/bundler/gems/rails-b366be3b5b28/activestorage/lib/active_storage/service/disk_service.rb:129:in `stream'
/Users/greidods/.rvm/gems/ruby-2.6.1/bundler/gems/rails-b366be3b5b28/activestorage/lib/active_storage/service/disk_service.rb:28:in `block in download'
/Users/greidods/.rvm/gems/ruby-2.6.1/bundler/gems/rails-b366be3b5b28/activesupport/lib/active_support/notifications.rb:180:in `block in instrument'
/Users/greidods/.rvm/gems/ruby-2.6.1/bundler/gems/rails-b366be3b5b28/activesupport/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
...

I have a feeling that there's a approach to seeding a row with multiple attachments.

What is causing this error? Why can I attach the image once but not multiple times?

Hutson answered 9/3, 2019 at 15:1 Comment(2)
you don't need to open the file multiple timesChongchoo
I've updated the code to ensure I only open the file once. However, the error still occurs when trying to attach the file multiple times.Hutson
C
5

In my case, what I noticed is that attaching attachments with ActiveStorage is not working properly when doing it inside a transaction. This applies to migrations, seeds or callbacks. What I finally did to avoid my logic being wrapped inside a transaction is to run the logic inside a thread:

Thread.new do
  # your logic here
end.join
Cartridge answered 11/2, 2022 at 14:36 Comment(3)
This was exactly what happened to me. Thank you for sharing this answer!Olpe
Same was happening to me! Removing transaction block solved this issue.Distinguished
Oh my dear lord, you saved me. I struggled all morning because I did not knew this.Satiable
D
3

I can't exactly reproduce your problem (I keep getting ActiveStorage::IntegrityError exceptions rather than ActiveStorage::FileNotFoundError) but I think I know what's going on. After the first time you attach the image:

project.images.attach(io: image_file, filename: "placeholder_image.png", content_type: "image/png")

the image_file's current position will be at the end of the file. Now when Active Storage tries to read the file again, it won't get any data so either the checksum fails (my IntegrityError) or Active Storage figures that there's no file there (your FileNotFoundError).

The solution is to reset the file position back to the beginning by calling #rewind:

rand(1..3).times do
  project.images.attach(io: image_file, filename: "placeholder_image.png", content_type: "image/png")
  image_file.rewind
end

You can image_file.rewind before or after the project.images.attach call, rewinding a freshly opened file doesn't do anything interesting. #rewind won't always be supported (or desired) by the io you pass to #attach so Active Storage can't really do this itself.

Alternatively, you could open the file on each iteration:

rand(1..3).times do
  image_file = File.open("./db/seed_files/placeholder_image.png")
  project.images.attach(io: image_file, filename: "placeholder_image.png", content_type: "image/png")
end

I'm assuming that the missing do for the times block in your question is just a typo BTW.

Doghouse answered 9/3, 2019 at 19:20 Comment(0)
M
0

I got it working by simply using an array as parameter:

image_file = File.open("./db/seed_files/placeholder_image.png")
files = []
  rand(1..3).times do
    files << {io: image_file, 
              filename: "placeholder_image.png",
              content_type: "image/png"
             }
    project.images.attach(files)
  end

Moran answered 7/5, 2021 at 21:10 Comment(0)
P
0

I also had an IO process attaching files wrapped in a transaction. My code did work on my dev computer, but failed on AWS. Pere's answer about using Thread.new solved my problem as well (almost).

After using Thread.new I found that sometimes the attachment would fail silently. Thinking about it, I guess it's pretty bad practice to have IO processes running in a transaction do block - we'd want to have the transaction block complete as fast as possible in order to not block the database.

I switched to triggering the .attach as a background job, so during the transaction I call a method which calls the file to be attached later in a separate job. For me the successful file upload is not a criteria to stop the whole transaction. I recommend this approach for any app which can afford it.

Psychrometer answered 31/12, 2023 at 19:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.