shrine 2.19.3 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +83 -979
  5. data/doc/advantages.md +231 -204
  6. data/doc/attacher.md +304 -153
  7. data/doc/carrierwave.md +297 -226
  8. data/doc/changing_derivatives.md +308 -0
  9. data/doc/changing_location.md +103 -21
  10. data/doc/changing_storage.md +110 -0
  11. data/doc/creating_persistence_plugins.md +132 -0
  12. data/doc/creating_plugins.md +43 -23
  13. data/doc/creating_storages.md +19 -5
  14. data/doc/design.md +147 -97
  15. data/doc/direct_s3.md +38 -28
  16. data/doc/external/articles.md +63 -0
  17. data/doc/external/extensions.md +53 -0
  18. data/doc/external/misc.md +32 -0
  19. data/doc/getting_started.md +1156 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +93 -30
  22. data/doc/paperclip.md +384 -262
  23. data/doc/plugins/activerecord.md +177 -46
  24. data/doc/plugins/add_metadata.md +139 -38
  25. data/doc/plugins/atomic_helpers.md +217 -0
  26. data/doc/plugins/backgrounding.md +156 -98
  27. data/doc/plugins/cached_attachment_data.md +7 -5
  28. data/doc/plugins/column.md +121 -0
  29. data/doc/plugins/data_uri.md +23 -22
  30. data/doc/plugins/default_storage.md +36 -10
  31. data/doc/plugins/default_url.md +30 -13
  32. data/doc/plugins/delete_raw.md +4 -2
  33. data/doc/plugins/derivation_endpoint.md +186 -101
  34. data/doc/plugins/derivatives.md +839 -0
  35. data/doc/plugins/determine_mime_type.md +4 -2
  36. data/doc/plugins/download_endpoint.md +64 -8
  37. data/doc/plugins/dynamic_storage.md +5 -3
  38. data/doc/plugins/entity.md +263 -0
  39. data/doc/plugins/form_assign.md +55 -0
  40. data/doc/plugins/included.md +31 -8
  41. data/doc/plugins/infer_extension.md +21 -10
  42. data/doc/plugins/instrumentation.md +38 -16
  43. data/doc/plugins/keep_files.md +16 -17
  44. data/doc/plugins/metadata_attributes.md +42 -13
  45. data/doc/plugins/mirroring.md +118 -0
  46. data/doc/plugins/model.md +210 -0
  47. data/doc/plugins/module_include.md +4 -2
  48. data/doc/plugins/multi_cache.md +24 -0
  49. data/doc/plugins/persistence.md +101 -0
  50. data/doc/plugins/presign_endpoint.md +9 -4
  51. data/doc/plugins/pretty_location.md +16 -3
  52. data/doc/plugins/processing.md +4 -2
  53. data/doc/plugins/rack_file.md +8 -2
  54. data/doc/plugins/rack_response.md +6 -2
  55. data/doc/plugins/recache.md +4 -2
  56. data/doc/plugins/refresh_metadata.md +49 -9
  57. data/doc/plugins/remote_url.md +84 -47
  58. data/doc/plugins/remove_attachment.md +27 -6
  59. data/doc/plugins/remove_invalid.md +21 -6
  60. data/doc/plugins/restore_cached_data.md +11 -3
  61. data/doc/plugins/sequel.md +159 -35
  62. data/doc/plugins/signature.md +16 -5
  63. data/doc/plugins/store_dimensions.md +14 -2
  64. data/doc/plugins/tempfile.md +4 -2
  65. data/doc/plugins/type_predicates.md +96 -0
  66. data/doc/plugins/upload_endpoint.md +13 -13
  67. data/doc/plugins/upload_options.md +6 -4
  68. data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
  69. data/doc/plugins/validation.md +97 -0
  70. data/doc/plugins/validation_helpers.md +16 -13
  71. data/doc/plugins/versions.md +15 -19
  72. data/doc/processing.md +438 -221
  73. data/doc/refile.md +188 -170
  74. data/doc/release_notes/1.0.0.md +4 -0
  75. data/doc/release_notes/1.1.0.md +6 -2
  76. data/doc/release_notes/1.2.0.md +4 -0
  77. data/doc/release_notes/1.3.0.md +4 -0
  78. data/doc/release_notes/1.4.0.md +4 -0
  79. data/doc/release_notes/1.4.1.md +4 -0
  80. data/doc/release_notes/1.4.2.md +4 -0
  81. data/doc/release_notes/2.0.0.md +4 -0
  82. data/doc/release_notes/2.0.1.md +4 -0
  83. data/doc/release_notes/2.1.0.md +5 -1
  84. data/doc/release_notes/2.1.1.md +4 -0
  85. data/doc/release_notes/2.10.0.md +4 -0
  86. data/doc/release_notes/2.10.1.md +4 -0
  87. data/doc/release_notes/2.11.0.md +4 -0
  88. data/doc/release_notes/2.12.0.md +4 -0
  89. data/doc/release_notes/2.13.0.md +4 -0
  90. data/doc/release_notes/2.14.0.md +5 -1
  91. data/doc/release_notes/2.15.0.md +11 -7
  92. data/doc/release_notes/2.16.0.md +4 -0
  93. data/doc/release_notes/2.17.0.md +4 -0
  94. data/doc/release_notes/2.18.0.md +4 -0
  95. data/doc/release_notes/2.19.0.md +6 -3
  96. data/doc/release_notes/2.2.0.md +4 -0
  97. data/doc/release_notes/2.3.0.md +4 -0
  98. data/doc/release_notes/2.3.1.md +4 -0
  99. data/doc/release_notes/2.4.0.md +4 -0
  100. data/doc/release_notes/2.4.1.md +4 -0
  101. data/doc/release_notes/2.5.0.md +4 -0
  102. data/doc/release_notes/2.6.0.md +4 -0
  103. data/doc/release_notes/2.6.1.md +4 -0
  104. data/doc/release_notes/2.7.0.md +4 -0
  105. data/doc/release_notes/2.8.0.md +4 -0
  106. data/doc/release_notes/2.9.0.md +4 -0
  107. data/doc/release_notes/3.0.0.md +981 -0
  108. data/doc/release_notes/3.0.1.md +22 -0
  109. data/doc/release_notes/3.1.0.md +73 -0
  110. data/doc/release_notes/3.2.0.md +96 -0
  111. data/doc/release_notes/3.2.1.md +31 -0
  112. data/doc/release_notes/3.2.2.md +14 -0
  113. data/doc/release_notes/3.3.0.md +105 -0
  114. data/doc/release_notes/3.4.0.md +35 -0
  115. data/doc/release_notes/3.5.0.md +63 -0
  116. data/doc/release_notes/3.6.0.md +23 -0
  117. data/doc/retrieving_uploads.md +5 -2
  118. data/doc/securing_uploads.md +60 -37
  119. data/doc/storage/file_system.md +20 -3
  120. data/doc/storage/memory.md +19 -0
  121. data/doc/storage/s3.md +122 -78
  122. data/doc/testing.md +141 -133
  123. data/doc/upgrading_to_3.md +708 -0
  124. data/doc/validation.md +54 -90
  125. data/lib/shrine/attacher.rb +292 -169
  126. data/lib/shrine/attachment.rb +13 -46
  127. data/lib/shrine/plugins/_persistence.rb +93 -0
  128. data/lib/shrine/plugins/activerecord.rb +77 -34
  129. data/lib/shrine/plugins/add_metadata.rb +25 -17
  130. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  131. data/lib/shrine/plugins/backgrounding.rb +77 -113
  132. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  133. data/lib/shrine/plugins/column.rb +102 -0
  134. data/lib/shrine/plugins/data_uri.rb +38 -36
  135. data/lib/shrine/plugins/default_storage.rb +45 -15
  136. data/lib/shrine/plugins/default_url.rb +12 -24
  137. data/lib/shrine/plugins/default_url_options.rb +3 -30
  138. data/lib/shrine/plugins/delete_raw.rb +10 -16
  139. data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
  140. data/lib/shrine/plugins/derivatives.rb +645 -0
  141. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  142. data/lib/shrine/plugins/download_endpoint.rb +118 -133
  143. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  144. data/lib/shrine/plugins/entity.rb +158 -0
  145. data/lib/shrine/plugins/form_assign.rb +108 -0
  146. data/lib/shrine/plugins/included.rb +6 -6
  147. data/lib/shrine/plugins/infer_extension.rb +17 -20
  148. data/lib/shrine/plugins/instrumentation.rb +59 -43
  149. data/lib/shrine/plugins/keep_files.rb +3 -15
  150. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  151. data/lib/shrine/plugins/mirroring.rb +142 -0
  152. data/lib/shrine/plugins/model.rb +160 -0
  153. data/lib/shrine/plugins/module_include.rb +3 -3
  154. data/lib/shrine/plugins/multi_cache.rb +27 -0
  155. data/lib/shrine/plugins/presign_endpoint.rb +27 -28
  156. data/lib/shrine/plugins/pretty_location.rb +15 -9
  157. data/lib/shrine/plugins/processing.rb +22 -9
  158. data/lib/shrine/plugins/rack_file.rb +2 -42
  159. data/lib/shrine/plugins/rack_response.rb +21 -10
  160. data/lib/shrine/plugins/recache.rb +6 -5
  161. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  162. data/lib/shrine/plugins/remote_url.rb +49 -49
  163. data/lib/shrine/plugins/remove_attachment.rb +12 -6
  164. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  165. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  166. data/lib/shrine/plugins/sequel.rb +86 -36
  167. data/lib/shrine/plugins/signature.rb +10 -16
  168. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  169. data/lib/shrine/plugins/tempfile.rb +1 -3
  170. data/lib/shrine/plugins/type_predicates.rb +113 -0
  171. data/lib/shrine/plugins/upload_endpoint.rb +28 -24
  172. data/lib/shrine/plugins/upload_options.rb +14 -15
  173. data/lib/shrine/plugins/url_options.rb +31 -0
  174. data/lib/shrine/plugins/validation.rb +80 -0
  175. data/lib/shrine/plugins/validation_helpers.rb +35 -58
  176. data/lib/shrine/plugins/versions.rb +107 -87
  177. data/lib/shrine/plugins.rb +22 -0
  178. data/lib/shrine/storage/file_system.rb +46 -64
  179. data/lib/shrine/storage/linter.rb +42 -7
  180. data/lib/shrine/storage/memory.rb +49 -0
  181. data/lib/shrine/storage/s3.rb +173 -160
  182. data/lib/shrine/uploaded_file.rb +32 -32
  183. data/lib/shrine/version.rb +3 -3
  184. data/lib/shrine.rb +87 -150
  185. data/shrine.gemspec +11 -12
  186. metadata +92 -82
  187. data/doc/migrating_storage.md +0 -76
  188. data/doc/plugins/backup.md +0 -31
  189. data/doc/plugins/copy.md +0 -24
  190. data/doc/plugins/delete_promoted.md +0 -12
  191. data/doc/plugins/direct_upload.md +0 -172
  192. data/doc/plugins/hooks.md +0 -58
  193. data/doc/plugins/logging.md +0 -42
  194. data/doc/plugins/migration_helpers.md +0 -60
  195. data/doc/plugins/moving.md +0 -19
  196. data/doc/plugins/multi_delete.md +0 -20
  197. data/doc/plugins/parallelize.md +0 -16
  198. data/doc/plugins/parsed_json.md +0 -23
  199. data/doc/regenerating_versions.md +0 -143
  200. data/lib/shrine/plugins/background_helpers.rb +0 -5
  201. data/lib/shrine/plugins/backup.rb +0 -90
  202. data/lib/shrine/plugins/copy.rb +0 -50
  203. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  204. data/lib/shrine/plugins/direct_upload.rb +0 -217
  205. data/lib/shrine/plugins/hooks.rb +0 -90
  206. data/lib/shrine/plugins/logging.rb +0 -142
  207. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  208. data/lib/shrine/plugins/moving.rb +0 -57
  209. data/lib/shrine/plugins/multi_delete.rb +0 -32
  210. data/lib/shrine/plugins/parallelize.rb +0 -78
  211. data/lib/shrine/plugins/parsed_json.rb +0 -29
data/doc/carrierwave.md CHANGED
@@ -1,16 +1,52 @@
1
- # Shrine for CarrierWave Users
1
+ ---
2
+ title: Upgrading from CarrierWave
3
+ ---
2
4
 
3
5
  This guide is aimed at helping CarrierWave users transition to Shrine, and it
4
6
  consists of three parts:
5
7
 
6
8
  1. Explanation of the key differences in design between CarrierWave and Shrine
7
- 2. Instructions how to migrate and existing app that uses CarrierWave to Shrine
9
+ 2. Instructions how to migrate an existing app that uses CarrierWave to Shrine
8
10
  3. Extensive reference of CarrierWave's interface with Shrine equivalents
9
11
 
10
- ## Storage
12
+ ## Overview
11
13
 
12
- While in CarrierWave you configure storage in global configuration, in Shrine
13
- storage is a class which you can pass options to during initialization:
14
+ ### Uploader
15
+
16
+ Shrine shares CarrierWave's concept of **uploaders**, classes which encapsulate
17
+ file attachment logic for different file types:
18
+
19
+ ```rb
20
+ class ImageUploader < Shrine
21
+ # attachment logic
22
+ end
23
+ ```
24
+
25
+ However, while CarrierWave uploaders are responsible for most of the
26
+ attachment logic (uploading to temporary/permanent storage, retrieving the
27
+ uploaded file, file validation, processing versions), Shrine distributes
28
+ these responsibilities across several core classes:
29
+
30
+ | Class | Description |
31
+ | :---- | :----------- |
32
+ | `Shrine` | handles uploads, metadata extraction, location generation |
33
+ | `Shrine::UploadedFile` | exposes metadata, implements downloading, URL generation, deletion |
34
+ | `Shrine::Attacher` | handles caching & storing, dirty tracking, persistence, versions |
35
+
36
+ Shrine uploaders themselves are functional: they receive a file on the input
37
+ and return the uploaded file on the output. There are no state changes.
38
+
39
+ ```rb
40
+ uploader = ImageUploader.new(:store)
41
+ uploaded_file = uploader.upload(file, :store)
42
+ uploaded_file #=> #<Shrine::UploadedFile>
43
+ uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/store/kfds0lg9rer.jpg"
44
+ uploaded_file.download #=> #<File:/tmp/path/to/file>
45
+ ```
46
+
47
+ ### Storage
48
+
49
+ In CarrierWave, you configure storage in global configuration:
14
50
 
15
51
  ```rb
16
52
  CarrierWave.configure do |config|
@@ -24,6 +60,9 @@ CarrierWave.configure do |config|
24
60
  config.fog_directory = "my-bucket"
25
61
  end
26
62
  ```
63
+
64
+ In Shrine, the configuration options are passed directly to the storage class:
65
+
27
66
  ```rb
28
67
  Shrine.storages[:store] = Shrine::Storage::S3.new(
29
68
  bucket: "my-bucket",
@@ -33,11 +72,11 @@ Shrine.storages[:store] = Shrine::Storage::S3.new(
33
72
  )
34
73
  ```
35
74
 
36
- In CarrierWave temporary storage cannot be configured; it saves and retrieves
37
- files from the filesystem, you can only set the directory. With Shrine both
38
- temporary (`:cache`) and permanent (`:store`) storage are first-class citizens
39
- and fully configurable, so you can also have files *cached* on S3 (preferrably
40
- via [direct uploads]):
75
+ #### Temporary storage
76
+
77
+ Where CarrierWave's temporary storage is hardcoded to disk, Shrine can use any
78
+ storage for temporary storage. So, if you have multiple servers or want to do
79
+ [direct uploads], you can use AWS S3 as temporary storage:
41
80
 
42
81
  ```rb
43
82
  Shrine.storages = {
@@ -46,97 +85,131 @@ Shrine.storages = {
46
85
  }
47
86
  ```
48
87
 
49
- ## Uploader
88
+ ### Persistence
50
89
 
51
- Shrine shares CarrierWave's concept of *uploaders*, classes which encapsulate
52
- file attachment logic for different file types:
90
+ While CarrierWave persists only the filename of the original uploaded file,
91
+ Shrine persists storage and metadata information as well:
53
92
 
54
93
  ```rb
55
- class ImageUploader < Shrine
56
- # attachment logic
57
- end
94
+ {
95
+ "id": "path/to/image.jpg",
96
+ "storage": "store",
97
+ "metadata": {
98
+ "filename": "nature.jpg",
99
+ "size": 4739472,
100
+ "mime_type": "image/jpeg"
101
+ }
102
+ }
58
103
  ```
59
104
 
60
- However, uploaders in CarrierWave are very broad; in addition to uploading and
61
- deleting files, they also represent the uploaded file. Shrine has a separate
62
- `Shrine::UploadedFile` class which represents the uploaded file.
105
+ This way we have all information about uploaded files, without having to
106
+ retrieve the file from the storage.
63
107
 
64
108
  ```rb
65
- uploaded_file = ImageUploader.upload(file, :store)
66
- uploaded_file #=> #<Shrine::UploadedFile>
67
- uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/store/kfds0lg9rer.jpg"
68
- uploaded_file.download #=> #<Tempfile>
109
+ photo.image.id #=> "path/to/image.jpg"
110
+ photo.image.storage_key #=> :store
111
+ photo.image.metadata #=> { "filename" => "...", "size" => ..., "mime_type" => "..." }
112
+
113
+ photo.image.original_filename #=> "nature.jpg"
114
+ photo.image.size #=> 4739472
115
+ photo.image.mime_type #=> "image/jpeg"
69
116
  ```
70
117
 
118
+ #### Location
119
+
120
+ CarrierWave persists only the filename of the uploaded file, and recalculates
121
+ the full location dynamically based on location configuration. This can be
122
+ dangerous, because if some component of the location happens to change, all
123
+ existing links might become invalid.
124
+
125
+ To avoid this, Shrine persists the full location on attachment, and uses it
126
+ when generating file URL. So, even if you change how file locations are
127
+ generated, existing files that are on old locations will still remain
128
+ accessible.
129
+
71
130
  ### Processing
72
131
 
73
- In contrast to CarrierWave's class-level DSL, in Shrine processing is defined
74
- and performed on the instance-level. The result of processing can be a single
75
- file or a hash of versions:
132
+ CarrierWave uses a class-level DSL for generating versions, which internally
133
+ uses uploader subclassing and does in-place processing.
76
134
 
77
135
  ```rb
78
136
  class ImageUploader < CarrierWave::Uploader::Base
79
137
  include CarrierWave::MiniMagick
80
138
 
81
- process resize_to_limit: [800, 800]
139
+ version :large do
140
+ process resize_to_limit: [800, 800]
141
+ end
82
142
 
83
143
  version :medium do
84
144
  process resize_to_limit: [500, 500]
85
145
  end
86
146
 
87
- version :small, from_version: :medium do
147
+ version :small do
88
148
  process resize_to_limit: [300, 300]
89
149
  end
90
150
  end
91
151
  ```
92
152
 
153
+ In contrast, in Shrine you perform processing on the instance level as a
154
+ functional transformation, which is a lot simpler and more flexible:
155
+
93
156
  ```rb
94
157
  require "image_processing/mini_magick"
95
158
 
96
159
  class ImageUploader < Shrine
97
- plugin :processing
98
- plugin :versions
99
-
100
- process(:store) do |io, context|
101
- versions = {}
160
+ plugin :derivatives
102
161
 
103
- io.download do |original|
104
- pipeline = ImageProcessing::MiniMagick.source(original)
162
+ Attacher.derivatives do |original|
163
+ magick = ImageProcessing::MiniMagick.source(original)
105
164
 
106
- versions[:original] = pipeline.resize_to_limit!(800, 800)
107
- versions[:medium] = pipeline.resize_to_limit!(500, 500)
108
- versions[:small] = pipeline.resize_to_limit!(300, 300)
109
- end
110
-
111
- versions # return the hash of processed files
165
+ {
166
+ large: magick.resize_to_limit!(800, 800),
167
+ medium: magick.resize_to_limit!(500, 500),
168
+ small: magick.resize_to_limit!(300, 300),
169
+ }
112
170
  end
113
171
  end
114
172
  ```
115
173
 
116
- This allows you to fully optimize processing, because you can easily specify
117
- which files are processed from which, and even add parallelization.
174
+ #### Retrieving versions
118
175
 
119
- CarrierWave performs processing before validations, which is a huge security
120
- issue, as it allows users to give arbitrary files to your processing tool, even
121
- if you have validations. Shrine performs processing after validations.
176
+ When retrieving versions, CarrierWave returns a list of declared versions which
177
+ may or may not have been generated. In contrast, Shrine persists data of
178
+ uploaded processed files into the database (including any extracted metadata),
179
+ which then becomes the source of truth on which versions have been generated.
180
+
181
+ ```rb
182
+ photo.image #=> #<Shrine::UploadedFile id="original.jpg" ...>
183
+ photo.image_derivatives #=> {}
184
+
185
+ photo.image_derivatives! # triggers processing
186
+ photo.image_derivatives #=>
187
+ # {
188
+ # large: #<Shrine::UploadedFile id="large.jpg" metadata={"size"=>873232, ...} ...>,
189
+ # medium: #<Shrine::UploadedFile id="medium.jpg" metadata={"size"=>94823, ...} ...>,
190
+ # small: #<Shrine::UploadedFile id="small.jpg" metadata={"size"=>37322, ...} ...>,
191
+ # }
192
+ ```
122
193
 
123
194
  #### Reprocessing versions
124
195
 
125
196
  Shrine doesn't have a built-in way of regenerating versions, because that has
126
- to be written and optimized differently depending on whether you're adding or
127
- removing a version, what ORM are you using, how many records there are in the
128
- database etc. The [Reprocessing versions] guide provides some useful tips on
129
- this task.
197
+ to be written and optimized differently depending on what versions have changed
198
+ which persistence library you're using, how many records there are in the table
199
+ etc.
200
+
201
+ However, there is an extensive guide for [Managing Derivatives], which provides
202
+ instructions on how to make these changes safely and with zero downtime.
130
203
 
131
- ### Validations
204
+ ### Validation
132
205
 
133
- Like with processing, validations in Shrine are also defined and performed on
134
- instance-level:
206
+ File validation in Shrine is also instance-level, which allows using
207
+ conditionals:
135
208
 
136
209
  ```rb
137
210
  class ImageUploader < CarrierWave::Uploader::Base
138
211
  def extension_whitelist
139
- %w[jpg jpeg gif png]
212
+ %w[jpg jpeg png webp]
140
213
  end
141
214
 
142
215
  def content_type_whitelist
@@ -154,107 +227,75 @@ class ImageUploader < Shrine
154
227
  plugin :validation_helpers
155
228
 
156
229
  Attacher.validate do
157
- validate_extension_inclusion %w[jpg jpeg gif png]
158
- validate_mime_type_inclusion %w[image/jpeg image/gif image/png]
159
- validate_max_size 10*1024*1024 unless record.admin?
230
+ validate_max_size 10*1024*1024
231
+ validate_extension %w[jpg jpeg png webp]
232
+
233
+ if validate_mime_type %w[image/jpeg image/png image/webp]
234
+ validate_max_dimensions [5000, 5000]
235
+ end
160
236
  end
161
237
  end
162
238
  ```
163
239
 
164
- ## Attachments
240
+ #### Custom metadata
165
241
 
166
- Like CarrierWave, Shrine also provides integrations with ORMs. It ships with
167
- plugins for both Sequel and ActiveRecord, but can also be used with just PORO
168
- models.
242
+ With Shrine you can also extract and validate any custom metadata:
169
243
 
170
244
  ```rb
171
- Shrine.plugin :sequel # if you're using Sequel
172
- Shrine.plugin :activerecord # if you're using ActiveRecord
173
- ```
245
+ class VideoUploader < Shrine
246
+ plugin :add_metadata
247
+ plugin :validation
174
248
 
175
- Instead of giving you class methods for "mounting" uploaders, in Shrine you
176
- generate attachment modules which you simply include in your models, which
177
- gives your models similar set of methods that CarrierWave gives:
249
+ add_metadata :duration do |io|
250
+ FFMPEG::Movie.new(io.path).duration
251
+ end
178
252
 
179
- ```rb
180
- class Photo < ActiveRecord::Base
181
- extend CarrierWave::ActiveRecord # done automatically by CarrierWave
182
- mount_uploader :image, ImageUploader
183
- end
184
- ```
185
- ```rb
186
- class Photo < ActiveRecord::Base
187
- include ImageUploader::Attachment.new(:avatar)
253
+ Attacher.validate do
254
+ if file.duration > 5*60*60
255
+ errors << "must not be longer than 5 hours"
256
+ end
257
+ end
188
258
  end
189
259
  ```
190
260
 
191
- ### Attachment column
261
+ ### Multiple uploads
192
262
 
193
- You models are required to have the `<attachment>_data` column, which Shrine
194
- uses to save storage, location, and metadata of the uploaded file.
263
+ Shrine doesn't have support for multiple uploads out-of-the-box like
264
+ CarrierWave does. Instead, you can implement them using a separate table with a
265
+ one-to-many relationship to which the files will be attached. The [Multiple
266
+ Files] guide explains this setup in more detail.
195
267
 
196
- ```rb
197
- photo.image_data #=>
198
- # {
199
- # "storage" => "store",
200
- # "id" => "photo/1/image/0d9o8dk42.png",
201
- # "metadata" => {
202
- # "filename" => "nature.png",
203
- # "size" => 49349138,
204
- # "mime_type" => "image/png"
205
- # }
206
- # }
268
+ ## Migrating from CarrierWave
207
269
 
208
- photo.image.original_filename #=> "nature.png"
209
- photo.image.size #=> 49349138
210
- photo.image.mime_type #=> "image/png"
211
- ```
270
+ You have an existing app using CarrierWave and you want to transfer it to
271
+ Shrine. Let's assume we have a `Photo` model with the "image" attachment.
212
272
 
213
- This is much more powerful than storing only the filename like CarrierWave
214
- does, as it allows you to also store any additional metadata that you might
215
- want to extract.
273
+ ### 1. Add Shrine column
216
274
 
217
- Unlike CarrierWave, Shrine will store this information for each processed
218
- version, making them first-class citizens:
275
+ First we need to create the `image_data` column for Shrine:
219
276
 
220
277
  ```rb
221
- photo.image[:original] #=> #<Shrine::UploadedFile>
222
- photo.image[:original].width #=> 800
223
-
224
- photo.image[:thumb] #=> #<Shrine::UploadedFile>
225
- photo.image[:thumb].width #=> 300
278
+ add_column :photos, :image_data, :text # or :json or :jsonb if supported
226
279
  ```
227
280
 
228
- Also, since CarrierWave stores only the filename, it has to recalculate the
229
- full location each time it wants to generate the URL. That makes it really
230
- difficult to move files to a new location, because changing how the location is
231
- generated will now cause incorrect URLs to be generated for all existing files.
232
- Shrine calculates the whole location only once and saves it to the column.
233
-
234
- ### Multiple uploads
281
+ ### 2. Dual write
235
282
 
236
- Shrine doesn't have support for multiple uploads like CarrierWave does, instead
237
- it expects that you will attach each file to a separate database record. This
238
- is a good thing, because the implementation is specific to the ORM you're
239
- using, and it's analogous to how you would implement any nested one-to-many
240
- associations. Take a look at the [demo app] which shows how easy it is to
241
- implement multiple uploads.
242
-
243
- ## Migrating from CarrierWave
244
-
245
- You have an existing app using CarrierWave and you want to transfer it to
246
- Shrine. Let's assume we have a `Photo` model with the "image" attachment. First
247
- we need to create the `image_data` column for Shrine:
283
+ Next, we need to make new CarrierWave attachments write to the
284
+ `image_data` column. This can be done by including the below module to all
285
+ models that have CarrierWave attachments:
248
286
 
249
287
  ```rb
250
- add_column :photos, :image_data, :text # or :json or :jsonb if supported
251
- ```
288
+ # config/initializers/shrine.rb (Rails)
289
+ require "shrine"
290
+
291
+ Shrine.storages = {
292
+ cache: ...,
293
+ store: ...,
294
+ }
252
295
 
253
- Afterwards we need to make new uploads write to the `image_data` column. This
254
- can be done by including the below module to all models that have CarrierWave
255
- attachments:
296
+ Shrine.plugin :model
297
+ Shrine.plugin :derivatives
256
298
 
257
- ```rb
258
299
  module CarrierwaveShrineSynchronization
259
300
  def self.included(model)
260
301
  model.before_save do
@@ -266,38 +307,36 @@ module CarrierwaveShrineSynchronization
266
307
 
267
308
  def write_shrine_data(name)
268
309
  uploader = send(name)
310
+ attacher = Shrine::Attacher.from_model(self, name)
269
311
 
270
312
  if read_attribute(name).present?
271
- data = uploader_to_shrine_data(uploader)
313
+ attacher.set shrine_file(uploader)
272
314
 
273
- if uploader.versions.any?
274
- data = {original: data}
275
- uploader.versions.each do |name, version|
276
- data[name] = uploader_to_shrine_data(version)
277
- end
315
+ uploader.versions.each do |version_name, version|
316
+ attacher.merge_derivatives(version_name => shrine_file(version))
278
317
  end
279
-
280
- # Remove the `.to_json` if you're using a JSON column, otherwise the JSON
281
- # object will be saved as an escaped string.
282
- write_attribute(:"#{name}_data", data.to_json)
283
318
  else
284
- write_attribute(:"#{name}_data", nil)
319
+ attacher.set nil
285
320
  end
286
321
  end
287
322
 
288
323
  private
289
324
 
290
- # If you'll be using `:prefix` on your Shrine storage, make sure to
291
- # subtract it from the path assigned as `:id`.
292
- def uploader_to_shrine_data(uploader)
293
- filename = read_attribute(uploader.mounted_as)
294
- path = uploader.store_path(filename)
325
+ def shrine_file(uploader)
326
+ name = uploader.mounted_as
327
+ filename = read_attribute(name)
328
+ location = uploader.store_path(filename)
329
+ location = location.sub(%r{^#{storage.prefix}/}, "") if storage.prefix
330
+
331
+ Shrine.uploaded_file(
332
+ storage: :store,
333
+ id: location,
334
+ metadata: { "filename" => filename },
335
+ )
336
+ end
295
337
 
296
- {
297
- storage: :store,
298
- id: path,
299
- metadata: { filename: filename }
300
- }
338
+ def storage
339
+ Shrine.storages[:store]
301
340
  end
302
341
  end
303
342
  ```
@@ -309,20 +348,27 @@ end
309
348
  ```
310
349
 
311
350
  After you deploy this code, the `image_data` column should now be successfully
312
- synchronized with new attachments. Next step is to run a script which writes
313
- all existing CarrierWave attachments to `image_data`:
351
+ synchronized with new attachments.
352
+
353
+ ### 3. Data migration
354
+
355
+ Next step is to run a script which writes all existing CarrierWave attachments
356
+ to `image_data`:
314
357
 
315
358
  ```rb
316
359
  Photo.find_each do |photo|
317
- Photo.uploaders.each_key { |name| photo.write_shrine_data(name) }
360
+ photo.write_shrine_data(:image)
318
361
  photo.save!
319
362
  end
320
363
  ```
321
364
 
365
+ ### 4. Rewrite code
366
+
322
367
  Now you should be able to rewrite your application so that it uses Shrine
323
- instead of CarrierWave, using equivalent Shrine storages. For help with
324
- translating the code from CarrierWave to Shrine, you can consult the reference
325
- below.
368
+ instead of CarrierWave (you can consult the reference in the next section). You
369
+ can remove the `CarrierwaveShrineSynchronization` module as well.
370
+
371
+ ### 5. Backfill metadata
326
372
 
327
373
  You'll notice that Shrine metadata will be absent from the migrated files'
328
374
  data. You can run a script that will fill in any missing metadata defined in
@@ -332,11 +378,20 @@ your Shrine uploader:
332
378
  Shrine.plugin :refresh_metadata
333
379
 
334
380
  Photo.find_each do |photo|
335
- attachment = ImageUploader.uploaded_file(photo.image, &:refresh_metadata!)
336
- photo.update(image_data: attachment.to_json)
381
+ attacher = photo.image_attacher
382
+ attacher.refresh_metadata!
383
+ attacher.atomic_persist
337
384
  end
338
385
  ```
339
386
 
387
+ ### 6. Remove CarrierWave column
388
+
389
+ If everything is looking good, we can remove the CarrierWave column:
390
+
391
+ ```rb
392
+ remove_column :photos, :image
393
+ ```
394
+
340
395
  ## CarrierWave to Shrine direct mapping
341
396
 
342
397
  ### `CarrierWave::Uploader::Base`
@@ -358,26 +413,28 @@ end
358
413
 
359
414
  #### `.process`, `.version`
360
415
 
361
- As explained in the "Processing" section, processing is done by overriding the
362
- `Shrine#process` method.
363
-
364
- #### `.before`, `.after`
365
-
366
- In Shrine you can get callbacks by loading the `hooks` plugin. Unlike
367
- CarrierWave, and much like Sequel, Shrine implements callbacks by overriding
368
- instance methods:
416
+ Processing is defined by using the `derivatives` plugin:
369
417
 
370
418
  ```rb
371
419
  class ImageUploader < Shrine
372
- plugin :hooks
420
+ plugin :derivatives
421
+
422
+ Attacher.derivatives do |original|
423
+ magick = ImageProcessing::MiniMagick.source(image)
373
424
 
374
- def after_upload(io, context)
375
- super
376
- # do something
425
+ {
426
+ large: magick.resize_to_limit!(800, 800),
427
+ medium: magick.resize_to_limit!(500, 500),
428
+ small: magick.resize_to_limit!(300, 300),
429
+ }
377
430
  end
378
431
  end
379
432
  ```
380
433
 
434
+ #### `.before`, `.after`
435
+
436
+ There is no Shrine equivalent for CarrierWave's callbacks.
437
+
381
438
  #### `#store!`, `#cache!`
382
439
 
383
440
  In Shrine you store and cache files by passing the corresponding storage to
@@ -406,8 +463,9 @@ uploaded_file.download #=> #<Tempfile:/path/to/file>
406
463
  In Shrine you call `#url` on uploaded files:
407
464
 
408
465
  ```rb
409
- user.avatar #=> #<Shrine::UploadedFile>
410
- user.avatar.url #=> "/uploads/398454ujedfggf.jpg"
466
+ photo.image #=> #<Shrine::UploadedFile>
467
+ photo.image.url #=> "/uploads/398454ujedfggf.jpg"
468
+ photo.image_url #=> "/uploads/398454ujedfggf.jpg" (shorthand)
411
469
  ```
412
470
 
413
471
  #### `#identifier`
@@ -415,26 +473,34 @@ user.avatar.url #=> "/uploads/398454ujedfggf.jpg"
415
473
  This method corresponds to `#original_filename` on the uploaded file:
416
474
 
417
475
  ```rb
418
- user.avatar #=> #<Shrine::UploadedFile>
419
- user.avatar.original_filename #=> "avatar.jpg"
476
+ photo.image #=> #<Shrine::UploadedFile>
477
+ photo.image.original_filename #=> "avatar.jpg"
420
478
  ```
421
479
 
422
480
  #### `#store_dir`, `#cache_dir`
423
481
 
424
- Shrine here provides a `#generate_location` method, which is triggered for all
425
- storages:
482
+ Shrine here provides a single `#generate_location` method that's triggered for
483
+ all storages:
426
484
 
427
485
  ```rb
428
486
  class ImageUploader < Shrine
429
- def generate_location(io, context)
430
- "#{context[:record].class}/#{context[:record].id}/#{io.original_filename}"
487
+ def generate_location(io, record: nil, name: nil, **)
488
+ [ storage_key,
489
+ record && record.class.name.underscore,
490
+ record && record.id,
491
+ super,
492
+ io.original_filename ].compact.join("/")
431
493
  end
432
494
  end
433
495
  ```
496
+ ```
497
+ cache/user/123/2feff8c724e7ce17/nature.jpg
498
+ store/user/456/7f99669fde1e01fc/kitten.jpg
499
+ ...
500
+ ```
434
501
 
435
- The `context` variable holds the additional data, like the attacment name and
436
- the record instance. You might also want to use the `pretty_location` plugin
437
- for automatically generating an organized folder structure.
502
+ You might also want to use the `pretty_location` plugin for automatically
503
+ generating an organized folder structure.
438
504
 
439
505
  #### `#default_url`
440
506
 
@@ -444,18 +510,15 @@ For default URLs you can use the `default_url` plugin:
444
510
  class ImageUploader < Shrine
445
511
  plugin :default_url
446
512
 
447
- Attacher.default_url do |options|
448
- "/attachments/#{name}/default.jpg"
513
+ Attacher.default_url do |derivative: nil, **|
514
+ "/fallbacks/#{derivative || "original"}.jpg"
449
515
  end
450
516
  end
451
517
  ```
452
518
 
453
- The `context` variable holds the name of the attachment, record instance and
454
- in some cases the `:version`.
455
-
456
519
  #### `#extension_white_list`, `#extension_black_list`
457
520
 
458
- In Shrine extension whitelisting/blacklisting is a part of validations, and is
521
+ In Shrine, extension whitelisting/blacklisting is a part of validations, and is
459
522
  provided by the `validation_helpers` plugin:
460
523
 
461
524
  ```rb
@@ -469,9 +532,9 @@ class ImageUploader < Shrine
469
532
  end
470
533
  ```
471
534
 
472
- #### `#blacklist_mime_type_pattern`, `#whitelist_mime_type_pattern`, `#content_type_whitelist`, `#content_type_blacklist`
535
+ #### `#content_type_whitelist`, `#content_type_blacklist`
473
536
 
474
- In Shrine MIME type whitelisting/blacklisting is part of validations, and is
537
+ In Shrine, MIME type whitelisting/blacklisting is part of validations, and is
475
538
  provided by the `validation_helpers` plugin, though it doesn't support regexes:
476
539
 
477
540
  ```rb
@@ -485,19 +548,28 @@ class ImageUploader < Shrine
485
548
  end
486
549
  ```
487
550
 
551
+ Make sure to also load the `determine_mime_type` plugin to detect MIME type
552
+ from file content.
553
+
554
+ ```rb
555
+ # Gemfile
556
+ gem "mimemagic"
557
+ ```
558
+ ```rb
559
+ Shrine.plugin :determine_mime_type, analyzer: :mimemagic
560
+ ```
561
+
488
562
  #### `#size_range`
489
563
 
490
564
  In Shrine file size validations are typically done using the
491
565
  `validation_helpers` plugin:
492
566
 
493
-
494
567
  ```rb
495
568
  class ImageUploader < Shrine
496
569
  plugin :validation_helpers
497
570
 
498
571
  Attacher.validate do
499
- validate_min_size 0
500
- validate_max_size 5*1024*1024 # 5 MB
572
+ validate_size 0..5*1024*1024 # 5 MB
501
573
  end
502
574
  end
503
575
  ```
@@ -506,13 +578,13 @@ end
506
578
 
507
579
  Shrine doesn't have a built-in way of regenerating versions, because that's
508
580
  very individual and depends on what versions you want regenerated, what ORM are
509
- you using, how many records there are in your database etc. The [Regenerating
510
- versions] guide provides some useful tips on this task.
581
+ you using, how many records there are in your database etc. The [Managing
582
+ Derivatives] guide provides some useful tips on this task.
511
583
 
512
584
  ### Models
513
585
 
514
586
  The only thing that Shrine requires from your models is a `<attachment>_data`
515
- column (e.g. if your attachment is "avatar", you need the `avatar_data` column).
587
+ column (e.g. if your attachment is "image", you need the `image_data` column).
516
588
 
517
589
  #### `.mount_uploader`
518
590
 
@@ -523,7 +595,7 @@ Shrine.plugin :sequel
523
595
  ```
524
596
  ```rb
525
597
  class User < Sequel::Model
526
- include ImageUploader::Attachment.new(:avatar)
598
+ include ImageUploader::Attachment(:avatar)
527
599
  end
528
600
  ```
529
601
 
@@ -532,7 +604,7 @@ end
532
604
  The attachment module adds an attachment setter:
533
605
 
534
606
  ```rb
535
- user.avatar = File.open("avatar.jpg")
607
+ photo.image = File.open("avatar.jpg", "rb")
536
608
  ```
537
609
 
538
610
  Note that unlike CarrierWave, you cannot pass in file paths, the input needs to
@@ -544,8 +616,8 @@ CarrierWave returns the uploader, but Shrine returns a `Shrine::UploadedFile`,
544
616
  a representation of the file uploaded to the storage:
545
617
 
546
618
  ```rb
547
- user.avatar #=> #<Shrine::UploadedFile>
548
- user.avatar.methods #=> [:url, :download, :read, :exists?, :delete, ...]
619
+ photo.image #=> #<Shrine::UploadedFile>
620
+ photo.image.methods #=> [:url, :download, :read, :exists?, :delete, ...]
549
621
  ```
550
622
 
551
623
  If attachment is missing, nil is returned.
@@ -556,13 +628,13 @@ This method is simply a shorthand for "if attachment is present, call `#url`
556
628
  on it, otherwise return nil":
557
629
 
558
630
  ```rb
559
- user.avatar_url #=> nil
560
- user.avatar = File.open("avatar.jpg")
561
- user.avatar_url #=> "/uploads/ksdf934rt.jpg"
631
+ photo.image_url #=> nil
632
+ photo.image = File.open("avatar.jpg", "rb")
633
+ photo.image_url #=> "/uploads/ksdf934rt.jpg"
562
634
  ```
563
635
 
564
- The `versions` plugin extends this method to also accept a version name as the
565
- argument (`user.avatar_url(:thumb)`).
636
+ The `derivatives` plugin extends this method to also accept a version name as
637
+ the argument (`photo.image_url(:thumb)`).
566
638
 
567
639
  #### `#<attachment>_cache`
568
640
 
@@ -573,9 +645,9 @@ that you can use for retaining the cached file:
573
645
  Shrine.plugin :cached_attachment_data
574
646
  ```
575
647
  ```rb
576
- form_for @user do |f|
577
- f.hidden_field :avatar, value: @user.cached_avatar_data
578
- f.file_field :avatar
648
+ form_for @photo do |f|
649
+ f.hidden_field :image, value: @photo.cached_image_data, id: nil
650
+ f.file_field :image
579
651
  end
580
652
  ```
581
653
 
@@ -594,7 +666,7 @@ shows what are Shrine's equivalents.
594
666
 
595
667
  #### `root`, `base_path`, `permissions`, `directory_permissions`
596
668
 
597
- In Shrine these are configured on the FileSystem storage directly.
669
+ In Shrine these are configured on the `FileSystem` storage directly.
598
670
 
599
671
  #### `storage`, `storage_engines`
600
672
 
@@ -608,7 +680,7 @@ By default Shrine deletes cached and replaced files, but you can choose to keep
608
680
  those files by loading the `keep_files` plugin:
609
681
 
610
682
  ```rb
611
- Shrine.plugin :keep_files, cached: true, replaced: true
683
+ Shrine.plugin :keep_files
612
684
  ```
613
685
 
614
686
  #### `move_to_cache`, `move_to_store`
@@ -632,8 +704,8 @@ class ImageUploader < Shrine
632
704
  Attacher.validate do
633
705
  # Evaluated inside an instance of Shrine::Attacher.
634
706
  if record.guest?
635
- validate_max_size 2*1024*1024, message: "is too large (max is 2 MB)"
636
- validate_mime_type_inclusion %w[image/jpg image/png image/gif]
707
+ validate_max_size 2*1024*1024, message: "must not be larger than 2 MB"
708
+ validate_mime_type %w[image/jpg image/png image/webp]
637
709
  end
638
710
  end
639
711
  end
@@ -661,9 +733,10 @@ multipart or not.
661
733
 
662
734
  ### `CarrierWave::Storage::Fog`
663
735
 
664
- You can use [`Shrine::Storage::S3`] \(built-in\),
665
- [`Shrine::Storage::GoogleCloudStorage`], or generic [`Shrine::Storage::Fog`]
666
- storage. The reference will assume you're using S3 storage.
736
+ You can use [`Shrine::Storage::S3`][S3] (built-in),
737
+ [`Shrine::Storage::GoogleCloudStorage`][shrine-gcs], or generic
738
+ [`Shrine::Storage::Fog`][shrine-fog] storage. The reference will assume you're
739
+ using S3 storage.
667
740
 
668
741
  #### `:fog_credentials`, `:fog_directory`
669
742
 
@@ -684,7 +757,7 @@ Shrine::Storage::S3.new(
684
757
  The object data can be configured via the `:upload_options` hash:
685
758
 
686
759
  ```rb
687
- Shrine::Storage::S3.new(upload_options: {content_disposition: "attachment"}, **options)
760
+ Shrine::Storage::S3.new(upload_options: { content_disposition: "attachment" }, **options)
688
761
  ```
689
762
 
690
763
  #### `:fog_public`
@@ -692,16 +765,16 @@ Shrine::Storage::S3.new(upload_options: {content_disposition: "attachment"}, **o
692
765
  The object permissions can be configured with the `:acl` upload option:
693
766
 
694
767
  ```rb
695
- Shrine::Storage::S3.new(upload_options: {acl: "private"}, **options)
768
+ Shrine::Storage::S3.new(upload_options: { acl: "private" }, **options)
696
769
  ```
697
770
 
698
771
  #### `:fog_authenticated_url_expiration`
699
772
 
700
773
  The `#url` method accepts the `:expires_in` option, you can set the default
701
- expiration with the `default_url_options` plugin:
774
+ expiration with the `url_options` plugin:
702
775
 
703
776
  ```rb
704
- plugin :default_url_options, store: {expires_in: 600}
777
+ plugin :url_options, store: { expires_in: 600 }
705
778
  ```
706
779
 
707
780
  #### `:fog_use_ssl_for_aws`, `:fog_aws_accelerate`
@@ -709,14 +782,12 @@ plugin :default_url_options, store: {expires_in: 600}
709
782
  Shrine allows you to override the S3 endpoint:
710
783
 
711
784
  ```rb
712
- Shrine::Storage::S3.new(endpoint: "https://s3-accelerate.amazonaws.com", **options)
785
+ Shrine::Storage::S3.new(use_accelerate_endpoint: true, **options)
713
786
  ```
714
787
 
715
- [image_processing]: https://github.com/janko/image_processing
716
- [demo app]: https://github.com/shrinerb/shrine/tree/master/demo
717
- [Reprocessing versions]: /doc/regenerating_versions.md#readme
788
+ [Managing Derivatives]: https://shrinerb.com/docs/changing-derivatives
789
+ [direct uploads]: https://shrinerb.com/docs/getting-started#direct-uploads
790
+ [S3]: https://shrinerb.com/docs/storage/s3
791
+ [shrine-gcs]: https://github.com/renchap/shrine-google_cloud_storage
718
792
  [shrine-fog]: https://github.com/shrinerb/shrine-fog
719
- [direct uploads]: /doc/direct_s3.md#readme
720
- [`Shrine::Storage::S3`]: /doc/storage/s3.md#readme
721
- [`Shrine::Storage::GoogleCloudStorage`]: https://github.com/renchap/shrine-google_cloud_storage
722
- [`Shrine::Storage::Fog`]: https://github.com/shrinerb/shrine-fog
793
+ [Multiple Files]: https://shrinerb.com/docs/multiple-files