How to avoid using the base CarrierWave uploader class on accident
If you forget to specify a custom uploader, CarrierWave will generate a class for you, which inherits from
CarrierWave::Uploader::Base. This can cause issues, which you might not catch at first.
The first issue (and the one that caused me the most grief) is that CarrierWave will upload to the uploads directory by default. This means that if you store your files on s3, all of your uploader’s uploads will end up in
your-bucket.s3.amazonaws.com/uploads/. This is almost certainly not what you want.
Other issues, which are a little more obvious, come from the fact that you can’t override the base uploader with customizations. One common customization is including an image processor like CarrierWave-VIPS or
Another one is overriding methods, such as
In my experience, it is very important to be able to make these customizations. Unfortunately, it is difficult to specify an uploader once there is existing data for the implicit uploader (the one CarrierWave created automatically based on the base uploader).
How to Ensure You Don’t Forget to Specify an Uploader
The way you ensure that you don’t fall into this trap, is to force yourself to specify an uploader.
Create a Common Uploader
The first step is to create a custom uploader that all of your other uploaders will inherit from. In my application, I called it
ApplicationUploader. Once you have created it, don't forget to change the parent class for all of your other uploaders.
Next, you should move all of your common code into this parent class. If all of your uploaders have duplicate code, this is where that duplicate code should go so that it can be inherited. In my case, I created a custom
#store_dir method and
CarrierWave::VIPS in that class.
If you have already fallen prey to uploads that landed in “uploads”, you can create a
#store_dir method like mine:
# we don't want to use the old "uploads" default after we fixed
# this issue of uploading images to the wrong place
if (model&.created_at || Time.now) < Time.parse("2019-12-08 22:19:45 UTC")
Any of your uploads that already went to the wrong place will need to continue pointing to that directory. Otherwise, they will all be broken links/URLs. I specified the timestamp when I deployed to production as the dividing line where I started using the correct upload location.
Enforcing the Use of the New Class
Now that we have a new parent uploader, we want to make sure that we always use it. In order to do that, we are going to open the
CarrierWave::Uploader::Base class and make some changes.
Open your CarrierWave configuration file (or else create one). For my Rails application, that is
Next, open the
CarrierWave::Uploader module, like this:
We are going to create a new module, and include that new module in the base uploader class. Our module will have only one method, which is
#initialize method is going to check whether we are using the new uploader or not. If we are, then it will call
super. If we are not, however, it will raise an error. This ensures that you can never accidentally forget to specify an uploader again, because your development will be stopped until you specify an uploader.
Here is what that looks like:
class MustInheritFromApplicationUploaderError < StandardError; end
def initialize(model = nil, mounted_as = nil)
# If this is raised, it is a reminder that the applied
# uploader needs to inherit from ApplicationUploader. We
# likely didn't specify an uploader in the object model
raise MustInheritFromApplicationUploaderError, "Did you forget to specify an uploader?"
The method must have the same signature as the one we are overriding, so that the call to
super does not fail.
Next, we include the module (using prepend, so that our module is placed ahead of the class itself in the lookup chain):
# ... class Base
That’s it! From now on, if your application tries to mount an uploader that does not inherit from
ApplicationUploader, an error will be raised.
Bonus: Dealing With Legacy Data
This is all well and great, but what about places in your code that did not specify an uploader before? We need a safe way to modify them. I went with a new uploader called
LegacyUploader. Here is what it looks like:
# This class is to be used in places where we did not previously
# specify an uploader at all
class LegacyUploader < ApplicationUploader; end
As long as everything in
ApplicationUploader is safe for existing data, then this will work fine. At any time, I can override
ApplicationUploader here with legacy-safe code.
Thank You, Friend
Thanks for taking the time to read (or skim) this article. I hope you found it helpful.
Have a great day!
Originally published at https://brandoncc.dev.