shrine 2.11.0 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e062b548ed3da3330604b991144e1fa8bb44c547be9d68f04994056e123c3d7f
4
- data.tar.gz: e4c52f36474a8a66ed3270d70b91ac4302ef0a1c60fc88381bcdfa037ef8c434
3
+ metadata.gz: e4876f91bcf2a7fc1c06f6eba68fda62e450a6363f0948387e91f48988a0b295
4
+ data.tar.gz: 514cd9b743ae1d574ba60f16d842a9c3dd14190b56091bd6df1f45d244c9ad7e
5
5
  SHA512:
6
- metadata.gz: 3ab3db221bd573ef0d7fcdd3a766a9e93b7b384cadb37668fc6406977875c584caae77eaae01ba8e20902a70e75d2d7218c90f54215db6b47288fe43b7a3857a
7
- data.tar.gz: 64198d22c129df0cc41e602108fca768d141f4223b806e2272b52e15991e0895b14ae9dbb555e7f3e391840b4c0cbd0dc515d1e8a747d8572c7181af4077bda9
6
+ metadata.gz: 6132a5c6c94a7913b1093af118870ac3b9e7e7a35fffabc6e2aec7d58884cad5b3020a7500d755907b7e456e73ce4ba618443bd3a3013ffb0e9ed57149f500f3
7
+ data.tar.gz: 2e788af689a199e722eec66caedf6eb263fe6f2854fa039cc3c55b20d937faf18ee0bd758cb1e1b9ecb774a09c8e9f7dbe366ea143f4992129e500cc9f10a9ba
@@ -1,3 +1,31 @@
1
+ ## 2.12.0 (2018-08-22)
2
+
3
+ * Ignore nil values when assigning files from a remote URL (@janko-m)
4
+
5
+ * Ignore nil values when assigning files from a data URI (@GeekOnCoffee)
6
+
7
+ * Raise `Shrine::Error` when child process failed to be spawned in `:file` MIME type analyzer (@hmistry)
8
+
9
+ * Use the appropriate unit in error messages of filesize validators in `validation_helpers` plugin (@hmistry)
10
+
11
+ * Fix subclassing not inheriting storage resolvers from superclass in `dynamic_storage` plugin (@janko-m)
12
+
13
+ * Un-deprecate assigning cached versions (@janko-m)
14
+
15
+ * Add `Attacher#assign_remote_url` which allows dynamically passing downloader options (@janko-m)
16
+
17
+ * Deprecate `:storages` option in `download_endpoint` plugin in favour of `UploadedFile#download_url` (@janko-m)
18
+
19
+ * Add `:redirect` option to `download_endpoint` plugin for redirecting to the uploaded file (@janko-m)
20
+
21
+ * Fix encoding issues when uploading IO object with unknown size to S3 (@janko-m)
22
+
23
+ * Accept additional `File.open` arguments in `FileSystem#open` (@janko-m)
24
+
25
+ * Add `:rewindable` option to `S3#open` for disabling caching of read content to disk (@janko-m)
26
+
27
+ * Make `UploadedFile#open` always open a new IO object and close the previous one (@janko-m)
28
+
1
29
  ## 2.11.0 (2018-04-28)
2
30
 
3
31
  * Add `Shrine.with_file` for temporarily converting an IO-like object into a file (@janko-m)
data/README.md CHANGED
@@ -9,7 +9,7 @@ Shrine is a toolkit for file attachments in Ruby applications. Some highlights:
9
9
  * **Flexible processing** – generate thumbnails with [ImageMagick] or [libvips] using the [ImageProcessing][image_processing] gem
10
10
  * **Metadata validation** – [validate files][validation_helpers plugin] based on [extracted metadata][Extracting Metadata]
11
11
  * **Direct uploads** – upload asynchronously [to your app][upload_endpoint plugin] or [to the cloud][presign_endpoint plugin] using [Uppy]
12
- * **Resumable uploads** – make large file uploads [resumable][tus] by pointing [Uppy][uppy tus plugin] to a [resumable endpoint][tus-ruby-server]
12
+ * **Resumable uploads** – make large file uploads [resumable][tus] by pointing [Uppy][uppy tus] to a [resumable endpoint][tus-ruby-server]
13
13
  * **Background jobs** – built-in support for [background processing][backgrounding plugin] that supports [any backgrounding library][backgrounding libraries]
14
14
 
15
15
  If you're curious how it compares to other file attachment libraries, see the [Advantages of Shrine].
@@ -17,6 +17,7 @@ If you're curious how it compares to other file attachment libraries, see the [A
17
17
  ## Resources
18
18
 
19
19
  - Documentation: [shrinerb.com](https://shrinerb.com)
20
+ - Demo code: [Roda][roda demo] / [Rails][rails demo]
20
21
  - Source: [github.com/shrinerb/shrine](https://github.com/shrinerb/shrine)
21
22
  - Bugs: [github.com/shrinerb/shrine/issues](https://github.com/shrinerb/shrine/issues)
22
23
  - Help & Discussion: [groups.google.com/group/ruby-shrine](https://groups.google.com/forum/#!forum/ruby-shrine)
@@ -81,7 +82,7 @@ uploads][direct S3 uploads guide].
81
82
 
82
83
  ```rb
83
84
  # with Forme:
84
- Forme.form(@photo, action: "/photos", method: "post", enctype: "multipart/form-data") do |f|
85
+ form @photo, action: "/photos", enctype: "multipart/form-data" do |f|
85
86
  f.input :image, type: :hidden, value: @photo.cached_image_data
86
87
  f.input :image, type: :file
87
88
  f.button "Create"
@@ -140,10 +141,10 @@ gem "aws-sdk-s3", "~> 1.2" # for AWS S3 storage
140
141
  require "shrine/storage/s3"
141
142
 
142
143
  s3_options = {
144
+ bucket: "my-bucket", # required
143
145
  access_key_id: "abc",
144
146
  secret_access_key: "xyz",
145
147
  region: "my-region",
146
- bucket: "my-bucket",
147
148
  }
148
149
 
149
150
  Shrine.storages = {
@@ -503,16 +504,17 @@ class ImageUploader < Shrine
503
504
  plugin :delete_raw # delete processed files after uploading
504
505
 
505
506
  process(:store) do |io, context|
506
- original = io.download
507
- pipeline = ImageProcessing::MiniMagick.source(original)
507
+ versions = { original: io } # retain original
508
508
 
509
- size_800 = pipeline.resize_to_limit!(800, 800)
510
- size_500 = pipeline.resize_to_limit!(500, 500)
511
- size_300 = pipeline.resize_to_limit!(300, 300)
509
+ io.download do |original|
510
+ pipeline = ImageProcessing::MiniMagick.source(original)
512
511
 
513
- original.close!
512
+ versions[:large] = pipeline.resize_to_limit!(800, 800)
513
+ versions[:medium] = pipeline.resize_to_limit!(500, 500)
514
+ versions[:small] = pipeline.resize_to_limit!(300, 300)
515
+ end
514
516
 
515
- { original: io, large: size_800, medium: size_500, small: size_300 }
517
+ versions # return the hash of processed files
516
518
  end
517
519
  end
518
520
  ```
@@ -696,9 +698,10 @@ demo app which implements multiple uploads directly to S3.
696
698
  ### Resumable uploads
697
699
 
698
700
  When your app is dealing with large uploads (e.g. videos), keep in mind that it
699
- can be challening for your users to upload these large files to your app,
700
- depending on their internet connection. If the connection breaks at any point
701
- during uploading, the upload needs to be restarted from the beginning.
701
+ can be challening for your users to upload these large files to your app. Many
702
+ users might not have a great internet connection, and if it happens to break at
703
+ any point during uploading, they would need to restart the upload from the
704
+ beginning.
702
705
 
703
706
  Luckily, there is a solution for this. **[Tus.io][tus]** is an open protocol
704
707
  for resumable file uploads, which enables the client and the server to achieve
@@ -706,11 +709,11 @@ reliable file uploads even on unstable connections, by enabling the upload to
706
709
  be resumed in case of interruptions, even after the browser was closed or the
707
710
  device was shut down.
708
711
 
709
- On the client side you can use [Uppy][uppy tus plugin] with [tus-js-client],
710
- have it upload files to a [tus-ruby-server], and finally attach the uploaded
711
- files with the help of [shrine-tus]. See [this walkthrough][resumable uploads
712
- walkthrough] that adds resumable uploads from scratch, as well as the [Roda
713
- demo][resumable demo] for a complete example.
712
+ On the client side you can use [Uppy][uppy tus] with [tus-js-client], have it
713
+ upload files to a [tus-ruby-server], and finally attach the uploaded files with
714
+ the help of [shrine-tus]. See [this walkthrough][resumable uploads walkthrough]
715
+ that adds resumable uploads from scratch, as well as the [Roda demo][resumable
716
+ demo] for a complete example.
714
717
 
715
718
  ## Backgrounding
716
719
 
@@ -872,7 +875,7 @@ The gem is available as open source under the terms of the [MIT License].
872
875
  [presign_endpoint plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/PresignEndpoint.html
873
876
  [Uppy]: https://uppy.io
874
877
  [tus]: https://tus.io
875
- [uppy tus plugin]: https://uppy.io/docs/tus/
878
+ [uppy tus]: https://uppy.io/docs/tus/
876
879
  [tus-ruby-server]: https://github.com/janko-m/tus-ruby-server
877
880
  [backgrounding plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/Backgrounding.html
878
881
  [Advantages of Shrine]: https://shrinerb.com/rdoc/files/doc/advantages_md.html
@@ -885,14 +888,14 @@ The gem is available as open source under the terms of the [MIT License].
885
888
  [Extracting Metadata]: https://shrinerb.com/rdoc/files/doc/metadata_md.html
886
889
  [File Processing]: https://shrinerb.com/rdoc/files/doc/processing_md.html
887
890
  [File Validation]: https://shrinerb.com/rdoc/files/doc/validation_md.html
888
- [direct uploads walkthrough]: https://gist.github.com/janko-m/9aea154d72eb85b1fbfa16e1d77946e5#adding-direct-uploads-to-a-roda--sequel-app-with-shrine
889
- [direct S3 uploads walkthrough]: https://gist.github.com/janko-m/9aea154d72eb85b1fbfa16e1d77946e5#adding-direct-s3-uploads-to-a-roda--sequel-app-with-shrine
891
+ [direct uploads walkthrough]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads
892
+ [direct S3 uploads walkthrough]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads<Paste>
890
893
  [direct S3 uploads guide]: https://shrinerb.com/rdoc/files/doc/direct_s3_md.html
891
894
  [roda demo]: https://github.com/shrinerb/shrine/tree/master/demo
892
895
  [rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
893
896
  [tus-js-client]: https://github.com/tus/tus-js-client
894
897
  [shrine-tus]: https://github.com/shrinerb/shrine-tus
895
- [resumable uploads walkthrough]: https://gist.github.com/janko-m/f05188205cb9af75a27ead78d068b5d3#adding-resumable-uploads-to-a-roda--sequel-app-with-shrine
898
+ [resumable uploads walkthrough]: https://github.com/shrinerb/shrine/wiki/Adding-Resumable-Uploads
896
899
  [resumable demo]: https://github.com/shrinerb/shrine-tus-demo
897
900
  [backgrounding libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-libraries
898
901
  [S3 lifecycle Console]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
@@ -185,18 +185,23 @@ end
185
185
  ### On-the-fly processing
186
186
 
187
187
  Shrine is primarily designed for processing files on upload, since that's
188
- applicable to all types of files, while on-the-fly processing that [Dragonfly],
189
- [Refile], and [Active Storage] offer makes sense only for images.
188
+ applicable to all types of files, though on-the-fly processing that [Dragonfly],
189
+ [Refile], and [Active Storage] can make managing image thumbnails a lot easier.
190
190
 
191
191
  However, there are many specialized solutions that provide on-the-fly
192
192
  processing functionality, both open source and commercial, and it's fairly easy
193
193
  to apply them to files uploaded by Shrine.
194
194
 
195
195
  ```rb
196
- Dragonfly.app
197
- .fetch_url(photo.image_url) # image uploaded by Shrine
198
- .thumb("800x800")
199
- .url #=> "/attachments/W1siZnUiLCJodHRwOi8vd3d3LnB1YmxpY2RvbWFpbn..."
196
+ def thumbnail_url(uploaded_file, dimensions)
197
+ Dragonfly.app
198
+ .fetch(uploaded_file.url)
199
+ .thumb(dimensions)
200
+ .url
201
+ end
202
+ ```
203
+ ```rb
204
+ thumbnail_url(photo.image, "500x400") #=> "/attachments/W1siZnUiLCJodHRwOi8vd3d3LnB1YmxpY2RvbWFpbn..."
200
205
  ```
201
206
 
202
207
  ## Metadata
@@ -265,10 +270,13 @@ to upload them to your app, especially on flaky internet connections. With
265
270
  a simple HTTP request, should there be any interruption during the execution,
266
271
  the whole upload needs to be retried from the beginning.
267
272
 
268
- To fix this problem, the community has created an open HTTP-based protocol for
269
- resumable uploads – **[tus]**. There even exists a [Ruby server
270
- implementation][tus-ruby-server] for this protocol ready to use. Finally, for
271
- attaching files uploaded via the tus protocol you can use the [shrine-tus] gem.
273
+ To fix this problem, [Transloadit] company has created an open HTTP-based
274
+ protocol for resumable uploads – **[tus]**. To use it, you can choose from
275
+ numerous client and server [implementations][tus implementations] of the
276
+ protocol. In this case you would typically have a [JavaScript
277
+ client][tus-js-client] (via [Uppy][uppy tus]) upload to a [Ruby
278
+ server][tus-ruby-server], and then attach uploaded files using the handy
279
+ [Shrine integration][shrine-tus].
272
280
 
273
281
  ## Security
274
282
 
@@ -340,7 +348,11 @@ limit.
340
348
  [background job]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/Backgrounding.html
341
349
  [backgrounding libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-libraries
342
350
  [Down streaming]: https://github.com/janko-m/down#streaming
351
+ [Transloadit]: https://transloadit.com
343
352
  [tus]: https://tus.io
353
+ [tus implementations]: https://tus.io/implementations.html
354
+ [tus-js-client]: https://github.com/tus/tus-js-client
355
+ [uppy tus]: https://uppy.io/docs/tus/
344
356
  [tus-ruby-server]: https://github.com/janko-m/tus-ruby-server
345
357
  [shrine-tus]: https://github.com/shrinerb/shrine-tus
346
358
  [ImageTragick]: https://imagetragick.com
@@ -19,15 +19,17 @@ CarrierWave.configure do |config|
19
19
  provider: "AWS",
20
20
  aws_access_key_id: "abc",
21
21
  aws_secret_access_key: "xyz",
22
+ region: "eu-west-1",
22
23
  }
23
24
  config.fog_directory = "my-bucket"
24
25
  end
25
26
  ```
26
27
  ```rb
27
28
  Shrine.storages[:store] = Shrine::Storage::S3.new(
28
- bucket: "my-bucket",
29
- aws_access_key_id: "abc",
30
- aws_secret_access_key: "xyz",
29
+ bucket: "my-bucket",
30
+ access_key_id: "abc",
31
+ secret_access_key: "xyz",
32
+ region: "eu-west-1",
31
33
  )
32
34
  ```
33
35
 
@@ -97,16 +99,17 @@ class ImageUploader < Shrine
97
99
  plugin :versions
98
100
 
99
101
  process(:store) do |io, context|
100
- original = io.download
101
- pipeline = ImageProcessing::MiniMagick.source(original)
102
+ versions = {}
102
103
 
103
- size_800 = pipeline.resize_to_limit!(800, 800)
104
- size_500 = pipeline.resize_to_limit!(500, 500)
105
- size_300 = pipeline.resize_to_limit!(300, 300)
104
+ io.download do |original|
105
+ pipeline = ImageProcessing::MiniMagick.source(original)
106
106
 
107
- original.close!
107
+ versions[:original] = pipeline.resize_to_limit!(800, 800)
108
+ versions[:medium] = pipeline.resize_to_limit!(500, 500)
109
+ versions[:small] = pipeline.resize_to_limit!(300, 300)
110
+ end
108
111
 
109
- { original: size_800, medium: size_500, small: size_300 }
112
+ versions # return the hash of processed files
110
113
  end
111
114
  end
112
115
  ```
@@ -52,7 +52,7 @@ end
52
52
  ```
53
53
 
54
54
  Unless you're already using a Ruby SDK, it's recommended to use [HTTP.rb] for
55
- uploading. It accepts any IO object that responds to `#read` (not just file
55
+ uploading. It accepts any IO object that implements `IO#read` (not just file
56
56
  objects), and it streams the request body directly to the TCP socket, both for
57
57
  raw and multipart uploads, making it suitable for large uploads.
58
58
 
@@ -35,9 +35,9 @@ gem "aws-sdk-s3", "~> 1.2"
35
35
  require "shrine/storage/s3"
36
36
 
37
37
  s3_options = {
38
+ bucket: "<YOUR BUCKET>", # required
38
39
  access_key_id: "<YOUR KEY>",
39
40
  secret_access_key: "<YOUR SECRET>",
40
- bucket: "<YOUR BUCKET>",
41
41
  region: "<REGION>",
42
42
  }
43
43
 
@@ -171,12 +171,12 @@ presigned_data = Shrine.storages[:cache].presign(
171
171
  success_action_redirect: new_album_url
172
172
  )
173
173
 
174
- Forme.form(action: presigned_data[:url], method: "post", enctype: "multipart/form-data") do |f|
174
+ form action: presigned_data[:url], method: "post", enctype: "multipart/form-data" do |f|
175
175
  presigned_data[:fields].each do |name, value|
176
176
  f.input :hidden, name: name, value: value
177
177
  end
178
178
  f.input :file, name: "file"
179
- f.input :submit, value: "Upload"
179
+ f.button "Submit"
180
180
  end
181
181
  ```
182
182
 
@@ -206,7 +206,7 @@ cached_file = {
206
206
  metadata: {},
207
207
  }
208
208
 
209
- Forme.form(@album, action: "/albums", method: "post") do |f|
209
+ form @album, action: "/albums" do |f|
210
210
  f.input :image, type: :hidden, value: cached_file.to_json
211
211
  f.button "Save"
212
212
  end
@@ -362,8 +362,8 @@ setup] guide.
362
362
 
363
363
  [`Shrine::Storage::S3#presign`]: https://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html#method-i-presign
364
364
  [`Aws::S3::PresignedPost`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#presigned_post-instance_method
365
- [direct S3 upload walkthrough]: https://gist.github.com/janko-m/9aea154d72eb85b1fbfa16e1d77946e5#adding-direct-s3-uploads-to-a-roda--sequel-app-with-shrine
366
- [checksum walkthrough]: https://gist.github.com/janko-m/4470b5fb0737c5c1f8bcfe8cdc3fd296#using-checksums-to-verify-integrity-of-direct-uploads-with-shrine--uppy
365
+ [direct S3 upload walkthrough]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
366
+ [checksum walkthrough]: https://github.com/shrinerb/shrine/wiki/Using-Checksums-in-Direct-Uploads
367
367
  [roda demo]: https://github.com/shrinerb/shrine/tree/master/demo
368
368
  [rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
369
369
  [Uppy]: https://uppy.io
@@ -1,52 +1,78 @@
1
1
  # Multiple Files
2
2
 
3
- There are often times when you want to allow users to attach multiple files to
4
- a single resource. Some file attachment libraries provide a special interface
5
- for multiple attachments, but Shrine doesn't come with one, because it's much
6
- more robust and flexible to implement this using your ORM directly.
7
-
8
- The idea is to create a new table, and attach each uploaded file to a separate
9
- record on that table, while having a "many-to-one" relationship with the main
10
- table. That way a database record from the main table can implicitly have
3
+ There are times when you want to allow users to attach multiple files to a
4
+ single resource, like an album having many photos or a playlist having many
5
+ songs. Some file attachment libraries provide a special interface for multiple
6
+ attachments, but Shrine doesn't have one, because it's more flexible to use
7
+ the "nested attributes" feature of your ORM directly to implement this.
8
+
9
+ The basic idea is to create a separate table that will have a many-to-one
10
+ relationship with the main table, and files will be attached on the records in
11
+ the new table. That way each record from the main table can implicitly have
11
12
  multiple attachments through the associated records.
12
13
 
13
14
  ```
14
- album
15
+ album1
15
16
  photo1
16
17
  - attachment1
17
18
  photo2
18
19
  - attachment2
19
20
  photo3
20
21
  - attachment3
22
+
23
+ album2
24
+ photo4
25
+ - attachment4
26
+ photo5
27
+ - attachment5
28
+
29
+ ...
30
+ ```
31
+
32
+ To illustrate, this code will create an album with three photos using nested
33
+ attributes:
34
+
35
+ ```rb
36
+ Album.create(
37
+ title: "My Album",
38
+ photos_attributes: [
39
+ { image: File.open("image1.jpg", "rb") },
40
+ { image: File.open("image2.jpg", "rb") },
41
+ { image: File.open("image3.jpg", "rb") },
42
+ ]
43
+ )
21
44
  ```
22
45
 
23
- This design gives you great flexibility, allowing you to support:
46
+ This design gives you the greatest flexibility, allowing you to support:
24
47
 
25
48
  * adding new attachments
26
49
  * updating existing attachments
27
50
  * removing existing attachments
28
- * sorting attachments
29
- * having additional fields on attachments (captions, votes, number of downloads etc.)
51
+ * sorting attachments (via a separate position column)
52
+ * having additional fields on attachments (e.g. captions, votes, number of downloads etc.)
53
+ * expanding this to be "many-to-many" relation (e.g. create different playlists from a list of songs, etc)
30
54
  * ...
31
55
 
32
- If you're using Sequel or ActiveRecord, the easiest way to implement this is
33
- via nested attributes, which you would in general use for any dynamic
34
- "one-to-many" association. The examples will be using Sequel, but with
35
- ActiveRecord it's very similar, here are the docs:
56
+ ## How to Implement
36
57
 
37
- * [`Sequel::Model.nested_attributes`]
38
- * [`ActiveRecord::Base.accepts_nested_attributes_for`]
39
-
40
- For simplicity, for the rest of this guide we will assume that we have "albums"
41
- that can have multiple "photos".
58
+ For the rest of this guide, we will use the example where we have "albums" that
59
+ can have multiple "photos" in it. The main table is the albums table and the
60
+ files (or attachments) table will be the photos table.
42
61
 
43
- ## 1. Attachments table
62
+ ### 1. Create the main resource and attachment table
44
63
 
45
- Let's create a table for our attachments, and add a foreign key for the main table:
64
+ Let's create a table for the main resource and attachments, and add a foreign
65
+ key in the attachment table for the main table:
46
66
 
47
67
  ```rb
68
+ # Sequel
48
69
  Sequel.migration do
49
70
  change do
71
+ create_table :albums do
72
+ primary_key :id
73
+ column :title, :text
74
+ end
75
+
50
76
  create_table :photos do
51
77
  primary_key :id
52
78
  foreign_key :album_id, :albums
@@ -54,63 +80,181 @@ Sequel.migration do
54
80
  end
55
81
  end
56
82
  end
83
+
84
+ # Active Record
85
+ class CreateAlbumsAndPhotos < ActiveRecord::Migration
86
+ def change
87
+ create_table :albums do |t|
88
+ t.string :title
89
+ t.timestamps
90
+ end
91
+
92
+ create_table :photos do |t|
93
+ t.references :album, foreign_key: true
94
+ t.text :image_data
95
+ t.timestamps
96
+ end
97
+ end
98
+ end
57
99
  ```
58
100
 
59
- In our new model we can create a Shrine attachment attribute:
101
+ In the Photo model, create a Shrine attachment attribute named `image`
102
+ (`:image` matches the `_data` column prefix above):
60
103
 
61
104
  ```rb
105
+ # Sequel
62
106
  class Photo < Sequel::Model
63
107
  include ImageUploader::Attachment.new(:image)
64
108
  end
109
+
110
+ # Active Record
111
+ class Photo < ActiveRecord::Base
112
+ include ImageUploader::Attachment.new(:image)
113
+ end
65
114
  ```
66
115
 
67
- ## 2. Nested attributes
116
+ ### 2. Declare nested attributes
68
117
 
69
- In our main model we can now declare the association to the new table, and
70
- allow it to directly accept attributes for the associated records:
118
+ Using nested attributes is the easiest way to implement any dynamic
119
+ "one-to-many" association. In the Album model we'll declare a one-to-many
120
+ relationship to the photos table, and allow it to directly accept attributes
121
+ for the associated photo records by enabling nested attributes:
71
122
 
72
123
  ```rb
124
+ # Sequel
73
125
  class Album < Sequel::Model
74
126
  one_to_many :photos
127
+ plugin :association_dependencies, photos: :destroy # destroy photos when album is destroyed
128
+
129
+ plugin :nested_attributes
130
+ nested_attributes :photos, destroy: true
131
+ end
132
+
133
+ # Active Record
134
+ class Album < ActiveRecord::Base
135
+ has_many :photos, dependent: :destroy
136
+ accepts_nested_attributes_for :photos, allow_destroy: true
137
+ end
138
+ ```
139
+
140
+ Documentation on nested attributes:
141
+
142
+ * [`Sequel::Model.nested_attributes`]
143
+ * [`ActiveRecord::Base.accepts_nested_attributes_for`]
144
+
145
+ ### 3. Create the View
75
146
 
76
- plugin :nested_attributes # load the plugin
77
- nested_attributes :photos
147
+ Create a form like you normally do to create the album. To this form we'll add
148
+ a file field for selecting photos, which will have `multiple` attribute to
149
+ allow the user to select multiple files. We'll also display nested fields for
150
+ already created photos, so that the same form can be used for updating the
151
+ album/photos as well (they will be submitted under the
152
+ `album[photos_attributes]` parameter).
153
+
154
+ ```rb
155
+ # with Forme:
156
+ form @album, action: "/photos", enctype: "multipart/form-data" do |f|
157
+ f.input :title
158
+ f.subform :photos do # adds new `album[photos_attributes]` parameter
159
+ f.input :image, type: :hidden, value: f.obj.cached_image_data
160
+ f.input :image, type: :file
161
+ f.input :_delete, type: :checkbox unless f.obj.new?
162
+ end
163
+ f.input "files[]", type: :file, attr: { multiple: true }, obj: nil
164
+ f.button "Create"
165
+ end
166
+
167
+ # with Rails form builder:
168
+ form_for @album do |f|
169
+ f.text_field :title
170
+ f.fields_for :photos do |p| # adds new `album[photos_attributes]` parameter
171
+ p.hidden_field :image, value: p.object.cached_image_data
172
+ p.file_field :image
173
+ p.check_box :_destroy unless p.object.new_record?
174
+ end
175
+ file_field_tag "files[]", multiple: true
176
+ f.submit "Create"
78
177
  end
79
178
  ```
80
179
 
81
- ## 3. View
180
+ In your controller you should still be able to assign all the attributes to the
181
+ album, just remember to whitelist the new parameter for the nested attributes,
182
+ in this case `photos_attributes`.
82
183
 
83
- In order to allow the user to select multiple files in the form, we just need
84
- to add the `multiple` attribute to the file field.
184
+ ### 4. Direct Upload
185
+
186
+ On the client side, you can asynchronously upload each of the files to a direct
187
+ upload endpoint as soon as they're selected. There are two methods of
188
+ implementing direct uploads: upload to your app using the [`upload_endpoint`]
189
+ plugin or upload to directly to storage like S3 using the [`presign_endpoint`]
190
+ plugin. It's recommended to use [Uppy] to handle the uploads on the client side.
191
+
192
+ Once files are uploaded asynchronously, you can dynamically insert photo
193
+ attachment fields for the `image` attribute to the form filled with uploaded
194
+ file data, so that the corresponding photo records are automatically created
195
+ after form submission. The hidden attachment fields should contain uploaded
196
+ file data in JSON format, just like when doing single direct uploads. The
197
+ attachment field names should be namespaced according to the convention that
198
+ the nested attributes feature expects. In this case it should be
199
+ `album[photos_attributes][<idx>]`, where `<idx>` is any incrementing integer
200
+ (e.g. you can use the current UNIX timestamp).
85
201
 
86
202
  ```rb
87
- f.input :file, name: :file, multiple: true
88
- # <input type="file" name="file" multiple />
203
+ # naming format in which photos fields should be generated and submitted
204
+ album[photos_attributes][11111][image] = '{"id":"38k25.jpg","storage":"cache","metadata":{...}}'
205
+ album[photos_attributes][29323][image] = '{"id":"sg0fg.jpg","storage":"cache","metadata":{...}}'
206
+ album[photos_attributes][34820][image] = '{"id":"041jd.jpg","storage":"cache","metadata":{...}}'
207
+ # ...
89
208
  ```
90
209
 
91
- On the client side you can then asynchronously upload each of the selected
92
- files to a direct upload endpoint. See documenation for the `upload_endpoint`
93
- and `presign_endpoint` plugins, as well as the [Direct Uploads to S3] guide for
94
- more details.
210
+ See the walkthroughs for setting up simple [direct app uploads] and
211
+ [direct S3 uploads], and the [Roda][roda demo] or [Rails][rails demo] demo app
212
+ which implements multiple file uploads.
213
+
214
+ ### 5. Adding Validations
95
215
 
96
- After each upload finishes, you can generate a nested hash for the new
97
- associated record, and write the uploaded file JSON to the attachment field:
216
+ You can add file validations to the `Photo` model using the
217
+ `validation_helpers` plugin. You just need to make sure that your ORM is
218
+ configured to automatically validate associated records.
98
219
 
99
220
  ```rb
100
- album[photos_attributes][0][image] = '{"id":"38k25.jpg","storage":"cache","metadata":{...}}'
101
- album[photos_attributes][1][image] = '{"id":"sg0fg.jpg","storage":"cache","metadata":{...}}'
102
- album[photos_attributes][2][image] = '{"id":"041jd.jpg","storage":"cache","metadata":{...}}'
221
+ class ImageUploader < Shrine
222
+ plugin :validation_helpers
223
+ plugin :determine_mime_type
224
+
225
+ Attacher.validate do
226
+ validate_max_size 10*1024*1024
227
+ validate_mime_type_inclusion %w[image/jpeg image/png]
228
+ end
229
+ end
230
+ ```
231
+ ```rb
232
+ # Sequel
233
+ class Album < Sequel::Model
234
+ # ... (nested_attributes already enables validating associated photos) ...
235
+ end
236
+
237
+ # ActiveRecord
238
+ class Album < ActiveRecord::Base
239
+ # ...
240
+ validates_associated :photos
241
+ end
103
242
  ```
104
243
 
105
- Once you submit this to the app, the ORM's nested attributes behaviour will
106
- create the associated records, and assign the Shrine attachments.
244
+ ### 6. Conclusion
107
245
 
108
- Now you can manage adding new, or updating and deleting existing attachments,
109
- just by using your ORM's nested attributes behaviour, the same way that you
110
- would do with any other dynamic one-to-many association. The callbacks that
111
- are added by including the Shrine module to the associated model will
112
- automatically take care of the attachment management.
246
+ Now we have a simple interface for accepting multiple attachments, which
247
+ internally uses nested attributes to create multiple associated records, each
248
+ with a single attachment. After creation you can also add new attachments, or
249
+ update and delete existing ones, which the nested attributes feature gives you
250
+ for free.
113
251
 
114
252
  [`Sequel::Model.nested_attributes`]: http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html
115
253
  [`ActiveRecord::Base.accepts_nested_attributes_for`]: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
116
- [Direct Uploads to S3]: https://shrinerb.com/rdoc/files/doc/direct_s3_md.html
254
+ [`upload_endpoint`]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/UploadEndpoint.html
255
+ [`presign_endpoint`]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/PresignEndpoint.html
256
+ [Uppy]: https://uppy.io
257
+ [direct app uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads
258
+ [direct S3 uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
259
+ [roda demo]: https://github.com/shrinerb/shrine/tree/master/demo
260
+ [rails demo]: https://github.com/erikdahlstrand/shrine-rails-example