With the release of Rails 5.2, the much used Paperclip gem is now deprecated and it's advised to use Active Storage that ships with Rails. I'm starting a new project and set up Active Storage with ease, but my problem comes when trying to add a name or description to the file uploads.
With Paperclip I would add a column to the model called something like file_upload_name, so that as well as having a file name "something.pdf" I could also add a name or description such as "My Important Document" on the upload form.
For the projects that I'm doing, this is a vital part of the upload process and ideally needs to be done at the time of upload. As Active Record doesn't store to a model in such a way it's not as simple as just adding a column and adding fields to a form. It seems something that should be relatively simple but I can't figure it out or find any information about how best to do it. Any help much appreciated.
Here's an example of what I'm trying to achieve:
With Active Storage the end result is a multiple file upload button, with no naming etc.
You should create a new model to wrap each attached file. That model would then have the ActiveStorage attachment defined on it, as well as whatever other attributes you need to capture. Ex:
class Attachment < ApplicationRecord
has_one_attached :file
end
Rails then treats file kind of like an attribute for each Attachment. You can define your other attributes (e.g. upload_name, etc.) on the Attachment model. Based on your screenshot, it looks like maybe a Quotation has many attached files, so you'd do something like:
class Quotation < ApplicationRecord
has_many :attachments
end
Related
We have been using rails 5.2RC1 for a couple weeks on a small number of production apps to test ActiveStorage. We've been able to get everything working with our Heroku instances (including PDF previews), but are now running into some questions around best practices.
Say we have the following model:
class Contract < ApplicationRecord
has_many_attached :documents
end
This works perfectly.
However now we want to add some additional data about each individual document. Perhaps things like the type of document for the contract or some other type of metadata.
Our first thought was to try and stuff this into the metadata attribute of the blob, but that doesn't feel right.
The other thought that we had was to change the design to something like this:
class Contract < ApplicationRecord
has_many :documents
end
class Document < ApplicationRecord
belongs_to :contract
has_many_attached :files
end
Then using the document model to keep information about each attached file. Say in this example a contract has an original document, but then in the future there could be addendum attached to it that have their own unique properties that we would want to keep track of.
Thoughts?
At the time this question was asked, it's probably the case that metadata was getting overwritten as described in this Github issue. While metadata still isn't queryable (come on guys, nearly 5 years has passed), you can definitely add custom attributes.
The weird thing is that the attach() function param metadata says in the docs it is supposed to be a string, but that doesn't work and gets overwritten by the analyzer. If you just change it to an object-literal instead, that works and the custom metadata gets merged with the analyzer metadata.
So this works:
payment.archives.attach(
io: File.open("./archive.zip"),
filename: "archive.zip",
content_type: "application/gzip",
metadata: { customTag: "some value" }
)
But passing the metadata as a stringified object does not:
payment.archives.attach(
io: File.open("./archive.zip"),
filename: "archive.zip",
content_type: "application/gzip",
metadata: "{ \"customTag\": \"some value\" }"
)
Also, you can pass an ActionDispatch::Http::UploadedFile object as a param to io: which the docs also aren't clear on.. In my use case (and I imagine many others) I had an UploadedFile object coming from a multipart form request and needed to add some custom attributes to each file. hopefully this helps someone who is lost in legacy rails hell another 5 years from now.
The metadata on ActiveStorage blobs aren't queryable (https://github.com/rails/rails/issues/31780#issuecomment-360356381) and you cannot add custom attributes to them. Which is slightly limiting I feel.
It seems the solution is your second suggestion, having a new model object to wrap the files. This is explained further here: Rails 5.2 Active Storage add custom attributes
I have a model called User. It has two fields: small_logo and big_logo.
Those are actually different pictures, not just one resized picture.
class User < ActiveRecord::Base
...
mount_uploader :big_logo, UserLogoUploader
mount_uploader :small_logo, UserLogoUploader
...
end
I use UserLogoUploader to upload this pictures.
And I'm running onto a bug - as long as the name of the model is the same, uploaded files get the same route, so if I try to upload two different files with same names - second one overwrites first one.
The obvious solution is to use different uploaders for those fields. But I don't want to create another uploader just to fix this bug - is there anything I can do to modify filename, for example, with something meaningful like the name of a formfield that submitted this file or access name of a model field that is being processed.
Found an answer to my own question after some searching
There is a mounted_as attribute inside an uploader, which, reffering to docs, does exactly what I need:
If a model is given as the first parameter, it will stored in the uploader, and
available throught +#model+. Likewise, mounted_as stores the name of the column
where this instance of the uploader is mounted. These values can then be used inside
your uploader.
So the whole solution looks like this:
def UserLogoUploader < CarrierWave::Uploader::Base
...
def store_dir
File.join [
Settings.carrierwave.store_dir_prefix,
model.class.to_s.underscore,
mounted_as.to_s,
model.id.to_s
].select(&:present?)
end
...
end
This code creates different subfolders for different model fields, which helps preventing names duplication and files overwriting.
I'm using Paperclip with Rails 4 to add attached video files to one of my models. I am able to name of the saved file after its new id like this:
has_attached_file :file, :url=>"/tmp/video_uploads/:id.:extension", :path=>":rails_root/tmp/video_uploads/:id.:extension"
This causes them to get saved to the right place, with the right name + original extension. However, when I look in the database, the file_file_name field for the new record is still the original file name (EX: scooby-dooby-doo.MOV). How do I fix this?
As far as I know, it's just an attribute:
object.file_file_name = 'something_else'
object.save
It seems to be there to retain the original file upload name. Changing that value doesn't really do anything.
edit: You say you're trying to make it easy to find the associated file, are you aware of the .url or .path methods on file?
object.file.path
object.file.url
Have a look at the attachment object on github.
In looking at that, it appears that re-assigning the value of file_file_name will "break" file.original_filename, in that it won't be accurate anymore. If you just want the file portion of the actual stored file, you could instead try something along these lines:
class MyModel < ActiveRecord::Base
has_attached_file :file
def actual_filename
File.basename(file.url)
end
end
I have two scenarios for using Paperclip, but I'm unsure of how to tweak the settings or if it's possible or even necessary. Need the advice of more seasoned professionals on this one.
First up, I have a Document model for uploads such as PDFs, which would be defined:
has_attached_file :document...
This would give me column names like #document.document_file_name. Anyway that I could have #document.file_name instead?
Secondly, I have Gallery.rb which has many Picture.rb. Same scenario here as well. Can I avoid having #picture.picture_file_name? Or is this something that should really be overlooked with the gains that Paperclip affords.
Thanks in advance for any input.
My take on this: The actual document (PDF file) is not the same as a document record (which comprises the physical document plus metadata). Therefore it makes sense to see the Paperclip attachment as an attribute of the model and have its methods be called after the attribute, and not operate on the model record itself.
One of my apps has a Document model with an attached file too, and I simply called the attribute attachment.
If this is too much of an inconvenience for you, you could always implement your own getters in the model:
class Document < ActiveRecord::Base
has_attached_file :attachment # ... or whatever you are calling it
def file_name
self.attachment.file_name
end
def file_size
self.attachment.file_size
end
def file_type
self.attachment.file_type
end
end
The Paperclip gem requires three attributes on the associated object.
attribute_file_name
attribute_file_size
attribute_file_type
attribute of course if the name of your file and it is the has_attached_file :attribute filed commonly called picture, image, whatever.
If you want to change one of those names you will need to edit the gem itself which seems crazy for just changing the attribute name :)
Here are the methods I had to create:
{attribute}_file_name
{attribute}_file_size
{attribute}_content_type
{attribute}_updated_at
I'm currently using the awesome attachment-fu plugin for a Rails app, but as a novice developer, I've never encountered a scenario like the one I've found myself in.
Essentially, I'm using the attachment-fu plugin on two levels.
Is for user avatars in the user class.
Is to allow file attachments (PDFs, etc) in a messaging system.
My question is what the best use practice would be in these situations to remain DRY, clear, and consistent.
Clearly it would make no sense to define and execute the plugin in both classes, but there's something deeply strange to me (possibly unfounded) about just going ahead and setting it all up in the godly Application class.
Is there something in between, or is the parent class the way to go?
Thanks!
What's the DRY issue with defining the attachment_fu settings twice?
Unless the files are of the same type and being stored in the same place, you're not going to be repeating anything in the configuration.
Sure, you'll have two has_attachment declarations, but the options will mostly differ (one declaration for your avatars and the other for your pdf's etc.
99.99% of the code to handle attachment will be buried in the attachment_fu libs, your configuration code should be pretty DRY by default =)
Is "outsourcing" avatar support entirely to Gravatar an option? There are some Rails plugins that will display avatars hosted by Gravatar. You might not need to re-invent the wheel there.
What wfarr is describing would be single table inheritance, which is what I currently do in this situation. I have one table for Assets which contains all the necessary attachment_fu columns, plus an extra column called type, which will hold the actual model name. I have a model for assets and additional models for specific upload types that inherit from assets:
asset.rb:
class Asset < ActiveRecord::Base
... attachment_fu logic ...
end
avatar.rb:
class Avatar < Asset
... avatar specific attachment_fu logic ...
end
pdf.rb:
class PDF < Asset
... PDF specific attachment_fu logic ...
end
I would lean towards using a parent class, with subclassing for the different ways you intend to actually use the attachments in your application. It may not be the DRYest solution available, however, it lends itself to a logical pattern rather well.
Couldn't you use Polymorphic Associations?
I'm about to hit this in my app with attachment_fu, so I'm not exactly sure on attachment_fu, but for the old school File Column plugin, I would use Polymorphic Associations.
My "file" model would be:
class FileUpload < ActiveRecord::Base
belongs_to :fileable, :polymorphic => true
file_column :name
end
and then any models that needed a file attachment would be like:
class Company < ActiveRecord::Base
has_many :file_uploads, :as => :fileable
end
File Column is no good anymore as it borks on Safari 3.x and is no longer maintained. It was nice and simple though... Ah, the good old days...
For what it's worth, I think Patrick Berkeley has done a good job with handling multiple attachments through the Paperclip plugin. He's outlined his work here:
http://gist.github.com/33011