Last Update: 2018-04-25 02:09:49 +0200

Multiple Files

There are often times when you want to allow users to attach multiple files to a single resource. Some file attachment libraries provide a special interface for multiple attachments, but Shrine doesn't come with one, because it's much more robust and flexible to implement this using your ORM directly.

The idea is to create a new table, and attach each uploaded file to a separate record on that table, while having a “many-to-one” relationship with the main table. That way a database record from the main table can implicitly have multiple attachments through the associated records.

    - attachment1
    - attachment2
    - attachment3

This design gives you great flexibility, allowing you to support:

  • adding new attachments

  • updating existing attachments

  • removing existing attachments

  • sorting attachments

  • having additional fields on attachments (captions, votes, number of downloads etc.)

If you're using Sequel or ActiveRecord, the easiest way to implement this is via nested attributes, which you would in general use for any dynamic “one-to-many” association. The examples will be using Sequel, but with ActiveRecord it's very similar, here are the docs:

For simplicity, for the rest of this guide we will assume that we have “albums” that can have multiple “photos”.

1. Attachments table

Let's create a table for our attachments, and add a foreign key for the main table:

Sequel.migration do
  change do
    create_table :photos do
      primary_key :id
      foreign_key :album_id, :albums
      column      :image_data, :text

In our new model we can create a Shrine attachment attribute:

class Photo < Sequel::Model

2. Nested attributes

In our main model we can now declare the association to the new table, and allow it to directly accept attributes for the associated records:

class Album < Sequel::Model
  one_to_many :photos

  plugin :nested_attributes # load the plugin
  nested_attributes :photos

3. View

In order to allow the user to select multiple files in the form, we just need to add the multiple attribute to the file field.

f.input :file, name: :file, multiple: true
# <input type="file" name="file" multiple />

On the client side you can then asynchronously upload each of the selected files to a direct upload endpoint. See documenation for the upload_endpoint and presign_endpoint plugins, as well as the Direct Uploads to S3 guide for more details.

After each upload finishes, you can generate a nested hash for the new associated record, and write the uploaded file JSON to the attachment field:

album[photos_attributes][0][image] = '{"id":"38k25.jpg","storage":"cache","metadata":{...}}'
album[photos_attributes][1][image] = '{"id":"sg0fg.jpg","storage":"cache","metadata":{...}}'
album[photos_attributes][2][image] = '{"id":"041jd.jpg","storage":"cache","metadata":{...}}'

Once you submit this to the app, the ORM's nested attributes behaviour will create the associated records, and assign the Shrine attachments.

Now you can manage adding new, or updating and deleting existing attachments, just by using your ORM's nested attributes behaviour, the same way that you would do with any other dynamic one-to-many association. The callbacks that are added by including the Shrine module to the associated model will automatically take care of the attachment management.