This is the second part of the Shrine series of posts. The purpose of this series of articles is to show the advantages of Shrine over existing file loaders.
In a 
previous post, I talked about what motivated me to create a Shrine. In this article I want to show you the foundation on which Shrine is based - the repository, the loader and the uploaded files.
Storage
Shrine “storage” is a plain Ruby object that encapsulates file management in a specific storage service (file system, S3, etc.). The repository should contain the following 5 methods:
class MyStorage def upload(io, id, **options)  
Shrine storages are configured directly by passing options to the constructor (borrowed from Refile) and must be registered with 
Shrine.storages :
')
 Shrine.storages[:s3] = Shrine::Storage::S3.new( access_key_id: "abc", secret_access_key: "xyz", region: "eu-west-1", bucket: "my-bucket", ) 
Currently, Shrine has 
file system support, 
S3 , 
Fog , 
Flickr , 
Cloudary , 
Transloadit , 
Uploadcare , 
Imgix , 
GridFS, and 
SQL , so choose.
You can also easily write your own repository, for this there is a 
manual and a 
linter that will automatically check whether everything works correctly.
Loader
Loaders are subclasses of Shrine, they encapsulate the logic of unloading a specific attachment (as in CarrierWave).
 class ImageUploader < Shrine  
Uploader objects act as wrappers around the repository, they run all the logic for loading, which is common to any repository:
- Treatment
- Extract Metadata
- Generating location for file
- Download (at this stage the storage is accessed)
- Close the downloaded file
Creating a boot loader instance with the storage option set:
 Shrine.storages[:disk] = Shrine::Storage::FileSystem.new(...) uploader = ImageUploader.new(:disk) uploader.upload(image)  
Loaders do not know about the models; They only work on the file that will be uploaded to the input, and return a view of the uploaded file on the output. Since this assumes that the loaders are stateless, this makes their behavior very predictable.
Uploaded files
When the file is uploaded through the loader, the 
#upload method returns a 
Shrine::UploadedFile object. This object is a complete representation of the file that was uploaded to the repository.
 uploaded_file = uploader.upload(image)  
Since this object knows where it was loaded, it can provide many useful methods:
 uploaded_file.url  
This object is determined only by the hash. Since the repository can be referenced by its set parameter, this hash can now be serialized into JSON and stored in a database column.
 uploaded_file.data  
Shrine::UploadedFile objects 
Shrine::UploadedFile do not depend on loaders. This is a significant difference from CarrierWave and Paperclip, which have this dependency on the classes 
CarrierWave::Uploader::Base and 
Paperclip::Attachment .
Abstraction io
Shrine can load any 
IO object that responds to 
#read , 
#size , 
#rewind , 
#eof? And 
#close (like Refile). Defining this strict interface, each Shrine function now knows that they can only rely on these methods, which means that they will work correctly regardless of whether you load the 
File , 
StringIO , 
ActionDispatch::Http::UploadedFile , 
Rack types. or deleted files that are 
downloaded by stream .
In addition, 
Shrine::UploadedFile is itself an 
IO object, wrapping any downloaded file under the same unified interface. This makes moving a file from one repository to another really convenient. In addition, it allows you to optimize some downloads by skipping the download process and re-downloading, for example, to use a copy of S3 if both files are from S3, or just send a URL request if the repository supports it.
 cache = ImageUploader.new(:s3_temporary) cached_file = cache.upload(image) store = ImageUploader.new(:s3_permanent) store.upload(cached_file)  
Plugin system
Shrine comes with a small 
kernel (<500 lines of code) that provides the necessary functionality. Any additional features can be downloaded via 
plugins . This gives you the flexibility to choose exactly what the Shrine should do for you, and download the code only for the functionality that you use.
 
Shrine comes with over 35 plugins, and you can easily write your own. The Shrine plugin system is an adaptation of 
Roda that I 
wrote about in the past.
In addition, Shrine loaders can be inherited ( 
unlike CarrierWave ).
 Shrine.plugin :logging  
Dependencies
Most libraries for downloading files have pretty monstrous dependencies.
CarrierWave
- ActiveSupport - I really don’t want all those monkey patches
- ActiveModel - Why not perform validation without libraries ?
- MIME :: Types - It is better to determine the MIME type from the file contents
Paperclip
- ActiveSupport - Again, I want to be able to not have any monkey patches
- ActiveModel - Okay, anyway, both ActiveModel and ActiveSupport are required for ActiveRecord
- Cocaine - Open3 monstrous library to run the command shell
- MIME :: Types - MIME type substitution detection has problems
- MimeMagic - enough file utility
Refile
- RestClient - A monstrous solution for easy file uploading.
- Sinatra - This is normal, although Roda is a lighter alternative.
- MIME :: Types - It is better to determine the MIME type from the file contents
Shrine, on the other hand, has only one mandatory but light dependency - 
Down . Down is a net / http wrapper for downloading files, which 
improves open-uri and supports streaming, and is used by almost all Shrine repositories.
In addition, Shrine as a whole loads very quickly, because you load the code only for the functionality that you use. For other downloaders, you need to download the code for all the functionality that you may not need. For example, Shrine loads 35 times faster than CarrierWave without loaded plug-ins and 7 times faster with all loaded ( 
source ) plug-ins.
Results
Every high-level interface should have a good foundation. Thus, no matter what level of abstraction you are working on, you can always understand what is happening. The basis of Shrine consists of the classes 
Storage , 
Shrine and 
Shrine::UploadedFile , each of which has a well-defined interface and duty.
Original: https://twin.imtqy.com/better-file-uploads-with-shrine-uploader/ 
Articles from the original series in the blog of the author of the library: