Reputation: 3618
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
Reputation: 43
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
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
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
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