user3574603
user3574603

Reputation: 3618

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

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?

Upvotes: 6

Views: 10053

Answers (4)

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.

Upvotes: 0

Pere Joan Martorell
Pere Joan Martorell

Reputation: 3093

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

Upvotes: 5

Jan
Jan

Reputation: 16074

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

Upvotes: 0

mu is too short
mu is too short

Reputation: 434585

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.

Upvotes: 3

Related Questions