Active storage has_many_attached is purging previous uploads - ruby-on-rails

I'm trying to add more files to a has_many_attached, but when I upload a new file the previous file is purged. Uploading multiple files does add multiple files, but they are all purged on the next upload as well. Is this intended behavior? if so, how do I prevent the purging?
log.rb
class Log < ApplicationRecord
has_many_attached :uploads
end
_form.html.erb
<%= form_for #log, remote: true do |f| %>
<%= f.file_field :uploads, multiple: true %>
<% end %>

You can prevent overwriting the existing attachments by adding the following line to config/environments/development.rb, config/environments/test.rb and config/environments/production.rb, as kindly indicated by quantavi in this issue: https://github.com/richardvenneman/trestle-active_storage/issues/41
config.active_storage.replace_on_assign_to_many = false
Apparently in Rails 6 the default behavior when uploading files again is to purge the previously uploaded files. You can find a longer thread about it in this Rails issue which Aarthi linked in a comment. The line above changes this setting so that consecutive uploads append files instead of overwriting the old ones.
(As you may have inferred from the link to the issue I ran into the same issue when using the Trestle admin panel with the complementary trestle-active_storage gem, that adds active storage field support.)

I kept my old assets with an hidden_field tag like this:
<% #product.photos.each do |ph| %>
<%= f.hidden_field :photos, multiple: true, value: ph.signed_id %>
<% end %>
This worked for me

Related

ActiveStorage has_many_attached incorrect storage location when inside of transaction

Messing with active storage for the first time. My has_one_attached setups work fine, however my has_many_attached is giving me issues both rendering the file and an incorrect download link:
# frozen_string_literal: true
class DocumentCategory < ApplicationRecord
validates :name, presence: true
has_many_attached :documents
end
seeds file:
DocumentCategory.create!(name: "Governing Documents")
file = Rails.root.join("spec/fixtures/test.png")
DocumentCategory.first.documents.attach(io: File.open(file), filename: "Random File")
view:
<% #document_category.documents.each do |file| %>
<li class='py-2 text-[14px] text-[#3182E2]'>
<div>
<%= link_to file.filename, rails_blob_path(file, disposition: 'attachment') %>
<%= image_tag file, class: 'w-full h-48 mb-1 object-cover'%>
</div>
</li>
<% end %>
For each document in the category, the image is missing, and if I click the link, it opens a new tab to a file that doesn't exist. I'm doing the same thing for each file in the loop to my singular has_one_attached on my other models.
EDIT
This turned out to be due to an existing issue with calling attach multiple times within a transaction, and I had my seeds wrapped in an active record transaction block. Removing the transaction fixed the issue.
https://github.com/rails/rails/issues/41661

Showing attachment in edit view Rails 6 (active storage)

Situation:
I sucessfully upload a file called :physical_copy in my form partial "_a_partial.html.erb", which is used for creating new objects and editing existing objects at the same time
Code:
<%= f.file_field :physical_copy,
required: true,
class: 'form-control',
value: #tax_relevant_document.physical_copy %>
New & create works perfectly fine.
Problem:
The file is not shown and hence deleted when editing, which is not the planned behavior. I can work around by first downloading and "re-uploading" but this shouldn't be the normal behavior.
Guess:
This "value" thing looks awry to me but removal of the attribute didn't help.
I would like:
edit view to show the existing filename in the field as basically value representation
the form should include the existing attachment in the field
I found a solution and made it conditional, which works fine in my case:
<% if !object.attachment_attribute.attached? %>
(File Field from above)
<% else # (attachment exists) %>
<%=
link_to(
object.attachment_attribute.filename,
rails_blob_path(object.attachment_attribute, disposition: 'attachment')
)
%>
<% end %>
Basically the attachment for download!

Get path to ActiveStorage file on disk

I need to get the path to the file on disk which is using ActiveStorage. The file is stored locally.
When I was using paperclip, I used the path method on the attachment which returned the full path.
Example:
user.avatar.path
While looking at the Active Storage Docs, it looked like rails_blob_path would do the trick. After looking at what it returned though, it does not provide the path to the document. Thus, it returns this error:
No such file or directory # rb_sysopen -
Background
I need the path to the document because I am using the combine_pdf gem in order to combine multiple pdfs into a single pdf.
For the paperclip implementation, I iterated through the full_paths of the selected pdf attachments and load them into the combined pdf:
attachment_paths.each {|att_path| report << CombinePDF.load(att_path)}
Use:
ActiveStorage::Blob.service.path_for(user.avatar.key)
You can do something like this on your model:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_on_disk
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
I'm not sure why all the other answers use send(:url_for, key). I'm using Rails 5.2.2 and path_for is a public method, therefore, it's way better to avoid send, or simply call path_for:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_path
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
Worth noting that in the view you can do things like this:
<p>
<%= image_tag url_for(#user.avatar) %>
<br>
<%= link_to 'View', polymorphic_url(#user.avatar) %>
<br>
Stored at <%= #user.image_path %>
<br>
<%= link_to 'Download', rails_blob_path(#user.avatar, disposition: :attachment) %>
<br>
<%= f.file_field :avatar %>
</p>
Thanks to the help of #muistooshort in the comments, after looking at the Active Storage Code, this works:
active_storage_disk_service = ActiveStorage::Service::DiskService.new(root: Rails.root.to_s + '/storage/')
active_storage_disk_service.send(:path_for, user.avatar.blob.key)
# => returns full path to the document stored locally on disk
This solution feels a bit hacky to me. I'd love to hear of other solutions. This does work for me though.
You can download the attachment to a local dir and then process it.
Supposing you have in your model:
has_one_attached :pdf_attachment
You can define:
def process_attachment
# Download the attached file in temp dir
pdf_attachment_path = "#{Dir.tmpdir}/#{pdf_attachment.filename}"
File.open(pdf_attachment_path, 'wb') do |file|
file.write(pdf_attachment.download)
end
# process the downloaded file
# ...
end

Direct Uploads to S3 using Carrierwave

I've recently converted the below from using Paperclip to Carrierwave uploading to Amazon S3 so I can make use of the carrierwave_direct gem and then Sidekiq or another background processing gem.
class Release < ActiveRecord::Base
has_many :releases_tracks, :dependent => :destroy
has_many :tracks, :through => :releases_tracks, :order => "releases_tracks.position DESC"
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
accepts_nested_attributes_for :releases_tracks
end
class Track < ActiveRecord::Base
mount_uploader :track, TrackUploader
has_many :releases_tracks, :dependent => :destroy
has_many :releases, :through => :releases_tracks
end
/views/releases/track_upload.html.erb
<%= form_for(#release, :html => { :multipart => true }) do |f| %>
<h3>Upload Tracks for <%= #release.title %></h3>
<% index = 0 %>
<%= f.fields_for :tracks do |builder| %>
<%= #release.tracks[index].name %>
<%= f.file_field :track, :class => "file styled", :title => 'Select Track'%>
<% index += 1 %>
<% end %>
<%= f.submit "Upload Tracks", :class => "submit" %>
<% end %>
Carrierwave uploads are working, but I can't figure out how to get the direct part working. Partly because I can't figure out how to incorporate the suggested form code:
<%= direct_upload_form_for #uploader do |f| %>
<%= f.file_field :track %>
<%= f.submit %>
<% end %
Or the where in my track OR release controller I place the suggested:
#uploader = User.new.track
#uploader.success_action_redirect = new_user_url
The readme https://github.com/dwilkie/carrierwave_direct and Railscast http://railscasts.com/episodes/383-uploading-to-amazon-s3 both point towards uploading your file first and then creating your database entry. In my app the db entries already exist. The Railscast does say it's possible but doesn't go through it. So that's the first problem.
The second is that I need to upload more than one file at a time. The code above does achieve that, but very slowly and it of course renders my app pretty useless as it does so.
Can anyone help? Massive thanks in advance!
First of all I would advise you not to use carrierwave_direct, I really don't like this gem, for many reasons.
One of those reasons, is that as it's said in the docs
Please be aware that this gem only supports single
file uploads. If you want to upload multiple files simultaneously
you'll have to use a javascript or flash uploader.
But if you want to use it, here is what I guess you have to do :
So first about the
#uploader = User.new.track
#uploader.success_action_redirect = new_user_url
It seems you are trying to upload tracks, and as you said your models have already been created, I guess your are trying to upload new tracks for an existing release. Correct me if I'm wrong.
so you should create the #uploader var in the #track_upload method of your ReleasesController.
class ReleasesController
...
def track_update
#uploader = User.new.track
#uploader.success_action_redirect = new_user_url
end
...
end
then in the associated view (/views/releases/track_upload.html.erb), you can use the direct_upload_form
<%= direct_upload_form_for #uploader do |f| %>
<%= f.file_field :track %>
<%= f.submit %>
<% end %>
The form will upload the file directly to s3, just after you selected the file. Then I don't know exactly how, but carrierwave_direct should give you back the url of the uploaded file.
I'm not sure about that as I've never been that far with it, but the idea is that your file just got uploaded to s3, now it has to be linked to your model, so the file doesn't get 'lost'.
So maybe carrierwave_direct is doing things on its own (I doubt that ...) by doing some ajax requests or anything else.
Anyway as you want to upload more than one file, I'd like to point you to a tutorial I recently wrote
This shows how to upload files directly to s3, without carrierwave_direct, but by doing things on your own. This requires a little bit more code and time, but you have more control about what's happening.
In your case, you'll want to put the form I'm using in my tutorial in your view, in the /views/releases/track_upload.html.erb view.
Then once you'll select a file, the successful AJAX request(emitted by the jQueryFileUpload plugin) will give you the URL of the uploaded file so you can save it in your Track model (you'll probably want to emit a new AJAX request to your server to create the new Track model, or to populate an other form on the page, like the one you were using in the /views/releases/track_upload.html.erb file, and then the tracks will be saved on submit.)
I'm not sure I'm really clear about that, let me know if you need more explanations.
And the good thing about that is that if you simply add multiple to your file input, then the awesome jQueryFileUpload plugin will send a request per file to s3, then you'll get the URL's of the uploaded files in each ajax results :D
And you can tweak things to add progress bars, and things like that with the jQuery plugin, you can really create awesome things.
Hope it'll help you !

Problem with file uploads in a nested form using Rails3 with Mongoid and Carrierwave

Im having a problem transferring an SQLlite Rails 3 app over to a Mongoid Rails 3 app. In the SQLlite version, I am easily able to include an image upload form (using Paperclip) from one model ('image') within a nested form from another model ('product'). Here's my 'new' product form:
<%= form_for #product, :html => {:multipart => true} do |f| %>
<% f.fields_for :images do |image_form| %>
<%= f.label :productphoto %>
<%= f.file_field :productphoto %><br />
<% end %>
<% end %>
And here's the 'show' view:
<% #product.images.each do |image| %>
<%= image_tag image.productphoto.url(:gallerythumb) %><br />
<% end %>
When I try to use the same product views in my Mongoid Rails 3 app (using Carrierwave), I get the following error:
TypeError in Stores#show:
can't convert nil into String
<%= image_tag product.image.url(:gallerythumb) %>
Im pretty sure my models in the Mongoid version are correct because if I add a string (like 'name') to my 'image' model and nest that in the 'Product' form, it works. Also, Im able to upload an image into a non-nested model form.
Any help would be greatly appreciated!
I just had a similar problem myself. The problem is not the image upload I think, but the problem is that Rails doesn't recognize :images as being an Array. If you look into the Rails source of the fields_for helper you see that it checks for a method "_attributes=". If that's not there the form will be posted as normal fields and not as an array (params will be "images" instead of "images[0]")
You have to add the following line to your model:
accepts_nested_attributes_for :images
It is carrierwave or mongoid bug
https://github.com/jnicklas/carrierwave/issues#issue/81
This is most likely the issue that Lewy linked to -- that problem is specific to arrangements where your Carrierwave uploader is mounted on a child document in an embedded association and you are saving the parent, and though you don't explicitly show if this is how your data is modeled, I suspect that's the case since you noted that it works with a non-nested form (presumably saving the child document then, not the parent).
If you dig around in the discussions linked from that issue, you'll find some proposed workarounds. Here's what I ended up with to get Carrierwave working in this situation for me:
https://gist.github.com/759788
Full credit is to due to zerobearing2 whose gist I forked, I just made minor changes to get it working in Rails 3.0.3 and commented on my gist with summary info on the relevant discussions.

Resources