paperclip-multiple
paperclip-multiple is a storage implementation for Paperclip.
It aims to help migrating files from filesystem
storage to fog
storage.
It provides the multiple
storage which instantiates two Attachments
, one using filesystem
storage
and another using fog
storage. From the moment the multiple
storage is enabled, new files
will be stored on both locations while still displaying the files from the filesystem
.
While paperclip-multiple helps you store files in two places, you will also want to sync
existing files. We used s3cmd
and
s3cmd sync
to do this.
paperclip-multiple was prepared to migrate to S3. It was not prepared to:
- Migrate from S3 to filesystem.
- Migrate to use the
S3
backend instead of thefog
.
Paperclip was not prepared to have multiple backends, so this little thing messes with some of the internals of Paperclip.
⚠️ ⚠️ Warning ⚠️ ⚠️
The nature of this software means that once we finished the migration, we aren't using it anymore.
It also messes with the internals of Paperclip, meaning it will possibly break in future releases. We are not going to actively maintain this library, but we wanted to offer this code to everyone that might find a use for it. We struggled to find any useful code in this regard, and even an old version of what you see here, paperclip-multiple, would have been appreciated.
Feel free to ask questions or fork this code. For the reasons stated before, we might not be able to provide with very good support. However, if there's something very obvious or broken and want to contribute with a nice Pull Request, we'll do our best.
Usage
Add paperclip-multiple to your Gemfile.
gem 'paperclip-multiple', github: 'harvesthq/paperclip-multiple'
Make sure you have valid settings for both the filesystem and the fog storages. Let's suppose you have information available in a constant like this:
PAPERCLIP_SETTINGS = {
fog_credentials: {
aws_access_key_id: "whatever",
aws_secret_access_key: "whatever",
provider: "AWS"
},
fog_public: true,
fog_directory: "bucket-name"
}
Use it in your has_attached_file
definition:
class User
has_attached_file :file, PAPERCLIP_SETTINGS.merge(
storage: :multiple,
path: ":compatible_rails_root/users/files/:user_id/:style.:extension",
url: "/uploads/users/files/:user_id/:style.:extension",
multiple_if: lambda { |user| user.company.s3_enabled? },
display_from_s3: lambda { |user| user.company.display_from_s3? }
)
end
Three things might surprise you, let me explain them:
What's that thing in your path?
You must not forget that Paperclip will be used for both fog and filesystem. If your path
option contained some absolute path, you'll have to tweak it to make it work with both storages.
This is what our :compatible_rails_root
interpolation looks like:
Paperclip.interpolates(:compatible_rails_root) do |attachment, _|
if attachment.options[:storage] == :fog
'uploads'
else
"#{rails_root(attachment, _)}/public/uploads"
end
end
Be sure to test this in some staging environment. It's not trivial to get the slashes right!
multiple_if
At Harvest we like to rollout things in slow, metered releases. The multiple_if
option allows
you to define when an instance will indeed start using the multiple attachment. If this block
returns false
, then it will work as if the filesystem
backend was used.
display_from_s3
This is the counterpart to the previous option. Multiple storage mainly delegates around
to the real Attachment
. When you call url
on your attachment, the multiple storage simply
calls the Attachment
with the filesystem
storage configured. If this block returns true
,
it will call url
on the Attachment
with fog
storage. A totally random example:
user.company.display_from_s3? # => false
user.file.url # => '/uploads/users/files/1234/original.jpg'
user.company.display_from_s3? # => true
user.file.url # => 'https://whatever.s3.amazonaws.com/uploads/users/files/1234/original.jpg'
Credits
@mrsimo built this to solve one of the multiple problems we face every day at Harvest. We're quite definitely hiring!