This time I’m making an overview of different approaches to file attaching problem, that we face in most applications. Let’s begin by looking at the popular gems and how this problem is solved in these gems’ codebases.
For non-Rails usage, we should mix in
Paperclip::Glue (for Rails it’s mixed in during autoload), which itself mixes in
Paperclip::Validators, each of which makes available, respectively, callbacks, database_column projection methods, and validators (Github).
we see definition of four columns, that will be used to store attachment’s properties:
Then the main work is done in
Paperclip::HasAttachedFile, which defines all needed methods using lots of metaprogramming.
Our model has now four attributes, that store attached file’s metadata (to actually display attached file, we need only
:file_name). Also, we have meta-programmatically defined methods to add and remove attachments, drop an attached file.
As a backend,
file utilities are used to manipulate images and checking content-types.
This one is the second most popular file attachment solution. It uses the concept of uploaders - special classes, providing overrides of default behavior for each class, using special
In CarrierWave::Mount module, we see a bunch of methods being defined using
The second argument to
mount_uploader is the uploader class name, which maps lots of methods to the model, making available adding and deleting attachments, as well as other operations.
That way, when we refer to our image attribute, we’ve got deserialized uploader class:
Then we use uploader class to override default methods. Just like Paperclip, Carrierwave processes files in the upload callbacks, using ImageMagick as a backend for image processing, but Carrierwave also has an adapter layer, which is represented by RMagick or MiniMagick libraries.
This gem introduces the concept of separate rack app that is used to process file requests.
For Rails app, we can start by adding
dragonfly_accessor :photo macro to model class. As we have seen from previous examples, such macros usually used to define various accessor methods using meta-programming
It also stores attached file attributes in a database, therefore, respective methods are used:
There is no definition for that methods, gem relies on column names. When accessing images, Dragonfly’s backend server processes requests, deserializes model’s attachment attribute and directs further processing to ImageMagick (in case of images).
This library also relies on a separate app for processing uploads. It mounts Sinatra application inside the host app:
Then goes already familiar concept of a macro:
which also serves as accessor method definition helper.
Accessing attachments results in a request to Sinatra app. Request url is constructed by deserializing attachment’s metadata:
token method is used to protect request url from flood attacks by adding a unique hash to every attachment’s url. Hash is simply constructed from combining arbitrary secret key with file path
Refile does not store processed versions of uploaded files - this task is simply outsourced to a content delivery network. Approach to defining custom processing options is also interesting - it is supposed to be done via monkey-patching using initializer files.
This one is my personal favorite, because of its being actively maintained and supporting some ORMs besides ActiveRecord. It also has its own unique concept of plugins, each of which serves some particular purpose. File data is as usual stored in database text field. To make this data accessible in our app, we mix in uploader class into the model:
ImageUploader in this case is a subclass of
Shrine class, which in turn leads us to another meta-programming defined accessor methods.
Shrine plugins are just modules, adding new methods or overriding existing ones. For example, processing plugin.
Here we see that custom entries are being added in
process method calls given action on the io object:
The processing logic is simply wrapped in a block and defined by the user in uploader class.
This solution also relies on dynamically processing uploads. But this also goes further and provides scalability by deploying processor app onto a separate server.
There are no any ORM bindings, neither model accessor methods. Attache simply provides file processing operations and storing files (or promoting them to external storage). Let’s look. for example, at
Received file is simply being written into the cache, which, in this case, is disk.
Attache app just writes the unprocessed file on the disk and then returns its metadata, whether it would be disk or some other storage. Storing of metadata and attaching it to the model is user’s responsibility. Processing itself is performed while the file is being downloaded (Github). In this case even background jobs are involved. Interesting point was that the Attache uses Paperclip internally as image processing proxy to ImageMagick.
So, as a conclusion for this overview, we can outline following points: