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
Related
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
When an image is updated using carrierwave I have it setup to use the model id and original file name
def filename
"#{model.id}-#{original_filename}" if original_filename.present?
end
Its only like this because its the default way I found when I was setting up carrierwave. Recently I realised if you update an image the link to the image is broken because the filename is updated to use the newer files name. So if a page is linked to the old image it isn't updated with the newer image but instead becomes a broken image link.
I want it to keep the old file name when the image is being updated but cant find how to do this in the documentation and from googling around
Edit
I diddnt add a lot of the 'use case' because I diddnt think the problem was this complicated.
Basically on the site there are items that have images assigned to them. I did see the comment on not using only model.id and #struthersneil cleared up why that comment is there. Considering the images records for an items are created when the item is created they will never be new when the image is being added (because its just updating a record that already exists). Because of this I'd say my best bet is to use just model.id as the filename.
My other option is to keep the old image so the links aren't broken. The main problem for using this is that users create posts on the site and link to these images in the posts (the posts are regularly updated so I cant to a once off cache on the post and images in the post). The images become updated from new information on the items they are associated to. Once this happens the image linked in the post will show outdated information and they will have to manually update to the new image each time. So having the image link stay the same with the image being updated is the best option in this case particular case
original_filename in this context is the name of the file that was just received by the uploader. It's the file's original name.
By doing this:
"#{model.id}-#{original_filename}" if original_filename.present?
You're telling CarrierWave to use the new name of the file every time. It sounds like you want a given model to use the same filename every time. So the obvious option is simply to throw away the original_filename altogether and use the model id by itself.
However, the comment above the filename method in the auto-generated uploader code says this:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
Which leads us to this...
# Be careful using record ids as filenames. If the filename is stored in the database
# the record id will be nil when the filename is set. Don't use record ids unless you
# understand this limitation.
That is kind of a hitch. Unless you can guarantee that your model always has an id (which won't be true if it's new) then you shouldn't use model.id to name your file.
I would suggest you generate a unique file_identifier string once for each model on initialize (as long as the file_identifier has not already been set), and just use this unique string as your filename, e.g.
class Something < ActiveRecord::Base
mount_uploader :image, SomeImageUploader
after_initialize do
unless self.file_identifier
self.file_identifier = SecureRandom.hex(32)
end
end
end
...and in your uploader class...
def filename
model.file_identifier if original_filename
end
Now your uploaded image will always overwrite the same file. But notice: you have just lost your image extension. That seems to be fine in my modern version of Firefox (it can guess by the looking at the image header) but it's probably not the most widely compatible solution. You could take the extension of the original_filename and add it on there, but now you're back to your original problem if someone uploads a png and then uploads a jpg (the result would be two different files).
You could perform a conversion to a fixed format of your choice on upload, then the filename + extension would always be the same.
Honestly I think you'd have an easier time if you accept that files get a new name after every upload, and simply leave the old files hanging around for a set period of time to continue supporting users who have the old link. (How old are these links going to get anyway? If it's sitting in a cache, then why cache only the generated HTML without caching the image?) This is especially true if you ever plan to use a CDN service between your application and your users. The usual pattern is to version your files with a new unique filename every time, which the CDN will cache once and forever.
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 trying to create a custom filename for files uploaded via the paperclip gem using Paperclip.interpoles in the initializer. The problem I'm having is this is updating the custom filename on the file system when the file is uploaded, but the database filename remains the name of the source file. Is there a better way then having to reassign the database attribute to handle this?
Try using before_create callback in ActiveRecord. As paperclip will not write the attached resource to disk until ActiveRecord::Base#save is called, this seems to be the right time to create your custom filename.
To do so, just register an ordinary method to create the custom filename. This will change the name of the attached image, which you'll then find on your file system and in your database.
Let's say you have a model, where you want to attach an image with a custom random filename.
In your model:
has_attached_file :image
before_create :randomize_image_file_name
Also in your model:
def randomize_image_file_name
extension = File.extname(image_file_name).downcase
self.image.instance_write(:file_name, "#{ActiveSupport::SecureRandom.hex(8)}#{extension}")
end
You can declare your method anywhere you want, though it's considered good practice to declare callback methods as protected or private.
This will save the attachment with a custom randomized filename.
Hope this helps.
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