CarrierWave use one uploader two times in single model - ruby-on-rails

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.

Related

Reading from Active Storage Attachment Before Save

I have users uploading JSON files as part of a model called Preset, very standard Active Storage stuff. One thing that's somewhat out of the ordinary (I suppose, given my inability to make it work) is that I'd like to grab data from the uploaded JSON file and use it to annotate the Preset record, like so:
class Preset < ApplicationRecord
has_one_attached :hlx_file
before_save :set_name
def set_name
file = JSON.parse(hlx_file.download)
self.name = file['data']['name']
end
end
When I call hlx_file.download I get ActiveStorage::FileNotFoundError: ActiveStorage::FileNotFoundError.
Rails 6 changed the moment of uploading the file to storage to during the actual save of the record.
This means that a before_save or validation cannot access the file the regular way.
If you need to access the newly uploaded file you can get a file reference like this:
record.attachment_changes['<attributename>'].attachable
This will be a tempfile of the to-be-attached file.
NOTE: The above is an undocumented internal api and subject to change (https://github.com/rails/rails/pull/37005)
you use before_save :set_name which call the file before it actually being saved you can use after_save instead
try to use url_for() funtion file = JSON.parse(url_for(hlx_file))
also dont forget to include Rails.application.routes.url_helpers at your model

Active Storage - Adding File Description / Text - Ruby on Rails 5.2

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

Metadata about blobs stored with ActiveStorage with Rails 5+

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

Rails 4 + Paperclip: How to rename file name in the database

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

Rails: Paperclip question regarding column names

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

Resources