Version inheritance in Carrierwave

С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 to type of images into the application. Under the hood the only difference between both of them was the target directory on the server. Sounds pretty simple?

To follow DRY principle, I decided to not create different uploader classes and use inheritance structure.

I’ve created simple ImageUploader class:

Then I created PurchasedImageUploader that completely inherits from previous one:

Then I’ve uploaded several files and get 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 inherited class would not be applied to versions. We should define `storage_dir` inside each block. It sounds confusing to me.

When you create inheritance structure with carriervave classes you should remember next: Versions of subclasses respects methods that defined only inside original class and previous version blocks.

original class” means the first class in chain that inherits from the “CarrierWave::Uploader::Base”

Lets review that behaviour:

To provide more deep ancestor chain lets 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" — That is class that was created specially for version :small for PurchasedImageUploader.

“ImageUploader::Uploader70329129758160” — Class for ImageUploader :small version ( that has the same name ).

Each version that was defined inside of the `PurchasedImageUploader` creates 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

or just do not use an inheritance for uploader files, you can achieve 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

Like what you read? Give Dmytro Vasin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.