Carrierwave: How to Avoid Issues with Version Inheritance

Сarrierwave is a popular image upload gem used by the Rails community to upload files to the server.
Recently I had a task to upload two types of images into the application. Under the hood,the only difference between them was the target directory on the server. Sounds pretty simple, right?
To follow DRY principle, I decided to not create different uploader classes and use inheritance structure.
I created simple ImageUploader class:
| class ImageUploader < CarrierWave::Uploader::Base | |
| include CarrierWave::MiniMagick | |
| storage :file | |
| def store_dir | |
| "image/#{model.id}" | |
| end | |
| version :small do | |
| process resize_to_fill: [120, 120] | |
| end | |
| end |
Then I created PurchasedImageUploader that completely inherits from the previous one:
| class PurchasedImageUploader < ImageUploader | |
| def store_dir | |
| "purchased_image/#{model.id}" | |
| end | |
| end | |
Then I uploaded several files and got unexpected result:
PurchasedImage.last.file.small.url
=> "/image/9/small_file_name.png"
PurchasedImage.last.file.url
=> "/purchased_image/9/file_name.png"

Hmm…
What is going on?
After some research I found out that existing behaviour was correct by design. storage_dir, that was defined inside the inherited class, would not be applied to versions. We should define storage_dir inside each block. It sounds confusing to me.
“original class” means the first class in chain that inherits from the “CarrierWave::Uploader::Base”.
Let’s review that behaviour.
To provide more deep ancestor chain let’s add:
# app/uploaders/purchased_image_uploader.rb
version :small do
process resize_to_fill: [120, 120]
end
If we pick direct version we can find out ancestors chain:
PurchasedImage.last.file.versions[:small].class
#=> PurchasedImageUploader::Uploader70329100898520
PurchasedImage.last.file.versions[:small].class.ancestors
#=>[
PurchasedImageUploader::Uploader70329100898520,
ImageUploader::Uploader70329129758160,
ImageUploader,
CarrierWave::MiniMagick,
CarrierWave::Uploader::Base,
...
]
“PurchasedImageUploader::Uploader70329100898520” — This is a class that was created specially for version :small for PurchasedImageUploader.
“ImageUploader::Uploader70329129758160” — A class for ImageUploader :small version (that has the same name).
Each version that was defined inside of the PurchasedImageUploader creates its own special class. That version class is inherited from the same special class that was created by the version with the same name in the parent class. And so on to ImageUploader — the original class that inherits from CarrierWave::Uploader::Base.
Simply, by design, each version block inherits from another version block with the same name that was defined in parent class.
About version creation you can find out here.
Solution
| class PurchasedImageUploader < ImageUploader | |
| def store_dir | |
| "purchased_image/#{model.id}" | |
| end | |
| version :small do | |
| def store_dir | |
| "purchased_image/#{model.id}" | |
| end | |
| process resize_to_fill: [120, 120] | |
| end | |
| end |
Or just do not use an inheritance for uploader files, you can achieve the same goal with “includes” :)
BTW: fog_public method will have the same issue if you would use a fog storage.
Solution is not perfect, but it works.
Also, if you find another solution — please let me know.
Link to dummy app: Demo App.
Don't want to miss anything?
Subscribe and get stories like these right into your inbox.
Keep reading

Speed Up Tests with Build Stubbed: Best Practices
Rspec is a great tool for the Ruby community, but tests can become slow in large projects. When test suites take over 30 minutes, something has gone wrong.

How to Decrease the Complexity of Routes in Rails
A large `routes.rb` file can quickly become cluttered due to customizations like Devise, additional routes from gems, Sidekiq, and namespaces for APP or API.

Metrics Collector: How to Optimize Your Business Insights
This article will introduce you the Ruby tool that we have built at Anadea to automate some repetitive processes.
Contact us
Let's explore how our expertise can help you achieve your goals! Drop us a line, and we'll get back to you shortly.