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
@@ -1,93 +1,224 @@
1
- # Active Record
1
+ ---
2
+ title: Active Record
3
+ ---
2
4
 
3
- The [`activerecord`][activerecord] plugin extends the "attachment" interface
4
- with support for ActiveRecord.
5
+ The [`activerecord`][activerecord] plugin adds [Active Record] integration to
6
+ the attachment interface. It is built on top of the [`model`][model] plugin.
5
7
 
6
8
  ```rb
7
- plugin :activerecord
9
+ Shrine.plugin :activerecord
8
10
  ```
9
11
 
10
- ## Callbacks
12
+ ## Attachment
11
13
 
12
- Now the attachment module will add additional callbacks to the model:
14
+ Including a `Shrine::Attachment` module into an `ActiveRecord::Base` subclass
15
+ will:
13
16
 
14
- * "before save" Used by the `recache` plugin.
15
- * "after commit" (save) Promotes the attachment, deletes replaced ones.
16
- * "after commit" (destroy) – Deletes the attachment.
17
+ * add [model] attachment methods
18
+ * add [validations](#validations) and [callbacks](#callbacks) to tie attachment
19
+ process to the record lifecycle
17
20
 
18
- Note that ActiveRecord versions 3.x and 4.x have errors automatically silenced
19
- in hooks, which can make debugging more difficult, so it's recommended that you
20
- enable errors:
21
+ ```rb
22
+ class Photo < ActiveRecord::Base # has `image_data` column
23
+ include ImageUploader::Attachment(:image) # adds methods, callbacks & validations
24
+ end
25
+ ```
26
+ ```rb
27
+ photo = Photo.new
28
+
29
+ photo.image = file # cache attachment
30
+
31
+ photo.image #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
32
+ photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
33
+
34
+ photo.save # persist, promote attachment, then persist again
35
+
36
+ photo.image #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
37
+ photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
38
+
39
+ photo.destroy # delete attachment
40
+
41
+ photo.image.exists? #=> false
42
+ ```
43
+
44
+ ### Callbacks
45
+
46
+ #### After Save
47
+
48
+ After a record is saved and the transaction is committed, `Attacher#finalize`
49
+ is called, which promotes cached file to permanent storage and deletes previous
50
+ file if any.
21
51
 
22
52
  ```rb
23
- # This is the default in ActiveRecord 5
24
- ActiveRecord::Base.raise_in_transactional_callbacks = true
53
+ photo = Photo.new
54
+
55
+ photo.image = file
56
+ photo.image.storage_key #=> :cache
57
+
58
+ photo.save
59
+ photo.image.storage_key #=> :store
60
+ ```
61
+
62
+ #### After Destroy
63
+
64
+ After a record is destroyed and the transaction is committed,
65
+ `Attacher#destroy_attached` method is called, which deletes stored attached
66
+ file if any.
67
+
68
+ ```rb
69
+ photo = Photo.find(photo_id)
70
+ photo.image #=> #<Shrine::UploadedFile>
71
+ photo.image.exists? #=> true
72
+
73
+ photo.destroy
74
+ photo.image.exists? #=> false
25
75
  ```
26
76
 
27
- If you want to put promoting/deleting into a background job, see the
28
- `backgrounding` plugin.
77
+ #### Caveats
29
78
 
30
- Since attaching first saves the record with a cached attachment, then saves
31
- again with a stored attachment, you can detect this in callbacks:
79
+ Active Record currently has a [bug with transaction callbacks], so if you have
80
+ any "after commit" callbacks, make sure to include Shrine's attachment module
81
+ *after* they have all been defined.
82
+
83
+ #### Overriding callbacks
84
+
85
+ You can override any of the following attacher methods to modify callback
86
+ behaviour:
87
+
88
+ * `Attacher#activerecord_before_save`
89
+ * `Attacher#activerecord_after_save`
90
+ * `Attacher#activerecord_after_destroy`
32
91
 
33
92
  ```rb
34
- class User < ActiveRecord::Base
35
- include ImageUploader::Attachment.new(:avatar)
36
-
37
- before_save do
38
- if avatar_data_changed? && avatar_attacher.cached?
39
- # cached
40
- elsif avatar_data_changed? && avatar_attacher.stored?
41
- # promoted
42
- end
93
+ class Shrine::Attacher
94
+ def activerecord_after_save
95
+ super
96
+ # ...
43
97
  end
44
98
  end
45
99
  ```
46
100
 
47
- Note that ActiveRecord currently has a [bug with transaction callbacks], so if
48
- you have any "after commit" callbacks, make sure to include Shrine's attachment
49
- module *after* they have all been defined.
101
+ #### Skipping Callbacks
50
102
 
51
- If you don't want the attachment module to add any callbacks to the model, and
52
- would instead prefer to call these actions manually, you can disable callbacks:
103
+ If you don't want the attachment module to add any callbacks to your model, you
104
+ can set `:callbacks` to `false`:
53
105
 
54
106
  ```rb
55
107
  plugin :activerecord, callbacks: false
56
108
  ```
57
109
 
58
- ## Validations
110
+ ### Validations
59
111
 
60
- Additionally, any Shrine validation errors will be added to ActiveRecord's
61
- errors upon validation. Note that Shrine validation messages don't have to be
62
- strings, they can also be symbols or symbols and options, which allows them to
63
- be internationalized together with other ActiveRecord validation messages.
112
+ If you're using the [`validation`][validation] plugin, the attachment module
113
+ will automatically merge attacher errors with model errors.
64
114
 
65
115
  ```rb
66
- class MyUploader < Shrine
116
+ class ImageUploader < Shrine
67
117
  plugin :validation_helpers
68
118
 
69
119
  Attacher.validate do
70
- validate_max_size 256 * 1024**2, message: ->(max) { [:max_size, max: max] }
120
+ validate_max_size 10 * 1024 * 1024
71
121
  end
72
122
  end
73
123
  ```
124
+ ```rb
125
+ photo = Photo.new
126
+ photo.image = file
127
+ photo.valid?
128
+ photo.errors #=> { image: ["size must not be greater than 10.0 MB"] }
129
+ ```
130
+
131
+ #### Attachment Presence
74
132
 
75
- If you want to validate presence of the attachment, you can do it directly on
76
- the model.
133
+ If you want to validate presence of the attachment, you can use Active Record's
134
+ presence validator:
77
135
 
78
136
  ```rb
79
- class User < ActiveRecord::Base
80
- include ImageUploader::Attachment.new(:avatar)
81
- validates_presence_of :avatar
137
+ class Photo < ActiveRecord::Base
138
+ include ImageUploader::Attachment(:image)
139
+
140
+ validates_presence_of :image
82
141
  end
83
142
  ```
84
143
 
144
+ #### I18n
145
+
146
+ If you want Active Record to translate attacher error messages, you can use
147
+ symbols or arrays of symbols and options for validation errors:
148
+
149
+ ```rb
150
+ class ImageUploader < Shrine
151
+ plugin :validation_helpers
152
+
153
+ Attacher.validate do
154
+ validate_max_size 10 * 1024 * 1024, message: -> (max) { [:too_large, max: max] }
155
+ validate_mime_type %w[image/jpeg image/png], message: :not_image
156
+ end
157
+ end
158
+ ```
159
+ ```yml
160
+ en:
161
+ activerecord:
162
+ errors:
163
+ models:
164
+ photo:
165
+ attributes:
166
+ image:
167
+ max_size: "must not be larger than %{max_size} bytes"
168
+ not_image: "must be a common image format"
169
+ ```
170
+
171
+ #### Skipping Validations
172
+
85
173
  If don't want the attachment module to merge file validations errors into
86
- model errors, you can disable it:
174
+ model errors, you can set `:validations` to `false`:
87
175
 
88
176
  ```rb
89
177
  plugin :activerecord, validations: false
90
178
  ```
91
179
 
92
- [activerecord]: /lib/shrine/plugins/activerecord.rb
180
+ ## Attacher
181
+
182
+ You can also use `Shrine::Attacher` directly (with or without the
183
+ `Shrine::Attachment` module):
184
+
185
+ ```rb
186
+ class Photo < ActiveRecord::Base # has `image_data` column
187
+ end
188
+ ```
189
+ ```rb
190
+ photo = Photo.new
191
+ attacher = ImageUploader::Attacher.from_model(photo, :image)
192
+
193
+ attacher.assign(file) # cache
194
+
195
+ attacher.file #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
196
+ photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
197
+
198
+ photo.save # persist
199
+ attacher.finalize # promote
200
+ photo.save # persist
201
+
202
+ attacher.file #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
203
+ photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
204
+ ```
205
+
206
+ ### Persistence
207
+
208
+ The following persistence methods are added to `Shrine::Attacher`:
209
+
210
+ | Method | Description |
211
+ | :----- | :---------- |
212
+ | `Attacher#atomic_promote` | calls `Attacher#promote` and persists if the attachment hasn't changed |
213
+ | `Attacher#atomic_persist` | saves changes if the attachment hasn't changed |
214
+ | `Attacher#persist` | saves any changes to the underlying record |
215
+
216
+ See [persistence] docs for more details.
217
+
218
+ [activerecord]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/activerecord.rb
219
+ [Active Record]: https://guides.rubyonrails.org/active_record_basics.html
220
+ [model]: https://shrinerb.com/docs/plugins/model
221
+ [callbacks]: https://guides.rubyonrails.org/active_record_callbacks.html
93
222
  [bug with transaction callbacks]: https://github.com/rails/rails/issues/14493
223
+ [validation]: https://shrinerb.com/docs/plugins/validation
224
+ [persistence]: https://shrinerb.com/docs/plugins/persistence
@@ -1,75 +1,155 @@
1
- # Add Metadata
1
+ ---
2
+ title: Add Metadata
3
+ ---
2
4
 
3
- The [`add_metadata`][add_metadata] plugin provides a convenient method for
4
- extracting and adding custom metadata values.
5
+ The [`add_metadata`][add_metadata] plugin allows adding custom metadata to
6
+ uploaded files.
5
7
 
6
8
  ```rb
7
- plugin :add_metadata
9
+ Shrine.plugin :add_metadata
10
+ ```
11
+
12
+ ## Metadata block
13
+
14
+ The `Shrine.add_metadata` method allows you to register a block that will get
15
+ executed on upload, where you can return custom metadata:
16
+
17
+ ```rb
18
+ require "pdf-reader" # https://github.com/yob/pdf-reader
8
19
 
9
- add_metadata :exif do |io, context|
10
- begin
11
- Exif::Data.new(io).to_h
12
- rescue Exif::NotReadable # not a valid image
13
- {}
20
+ class PdfUploader < Shrine
21
+ add_metadata :page_count do |io|
22
+ reader = PDF::Reader.new(io)
23
+ reader.page_count
14
24
  end
15
25
  end
16
26
  ```
17
27
 
18
- The above will add "exif" to the metadata hash, and also create the `#exif`
19
- reader method on Shrine::UploadedFile.
28
+ The above will add `page_count` key to the metadata hash, and also create the
29
+ `#page_count` reader method on the `Shrine::UploadedFile`.
20
30
 
21
31
  ```rb
22
- image.metadata["exif"]
32
+ uploaded_file.metadata["page_count"] #=> 30
23
33
  # or
24
- image.exif
34
+ uploaded_file.page_count #=> 30
35
+ ```
36
+
37
+ ### Skipping nil values
38
+
39
+ By default, if your block returns `nil` then the `nil` value will be stored into
40
+ metadata. If you do not want to store anything when your block returns nil, you
41
+ can use the `skip_nil: true` option:
42
+
43
+ ```rb
44
+ class PdfUploader < Shrine
45
+ add_metadata :pages, skip_nil: true do |io|
46
+ if is_pdf?(io)
47
+ reader = PDF::Reader.new(io)
48
+ reader.page_count
49
+ else
50
+ # If this is not a PDF, then the pages metadata will not be stored
51
+ nil
52
+ end
53
+ end
54
+ end
25
55
  ```
26
56
 
57
+ ### Multiple values
58
+
27
59
  You can also extract multiple metadata values at once, by using `add_metadata`
28
60
  without an argument and returning a hash of metadata.
29
61
 
30
62
  ```rb
31
- add_metadata do |io, context|
32
- begin
33
- data = Exif::Data.new(io)
34
- rescue Exif::NotReadable # not a valid image
35
- next {}
63
+ require "exif" # https://github.com/tonytonyjan/exif
64
+
65
+ class ImageUploader < Shrine
66
+ add_metadata do |io|
67
+ begin
68
+ data = Exif::Data.new(io)
69
+ rescue Exif::NotReadable # not a valid image
70
+ next {}
71
+ end
72
+
73
+ { "date_time" => data.date_time,
74
+ "flash" => data.flash,
75
+ "focal_length" => data.focal_length,
76
+ "exposure_time" => data.exposure_time }
36
77
  end
37
-
38
- { date_time: data.date_time,
39
- flash: data.flash,
40
- focal_length: data.focal_length,
41
- exposure_time: data.exposure_time }
42
78
  end
43
79
  ```
80
+ ```rb
81
+ uploaded_file.metadata #=>
82
+ # {
83
+ # ...
84
+ # "date_time" => "2019:07:20 16:16:08",
85
+ # "flash" => 16,
86
+ # "focal_length" => 26/1,
87
+ # "exposure_time" => 1/500,
88
+ # }
89
+ ```
44
90
 
45
91
  In this case Shrine won't automatically create reader methods for the extracted
46
- metadata on Shrine::UploadedFile, but you can create them via
47
- `#metadata_method`.
92
+ metadata, but you can create them via `Shrine.metadata_method`:
48
93
 
49
94
  ```rb
50
- metadata_method :date_time, :flash
95
+ class ImageUploader < Shrine
96
+ # ...
97
+ metadata_method :date_time, :flash
98
+ end
51
99
  ```
100
+ ```rb
101
+ uploaded_file.date_time #=> "2019:07:20 16:16:08"
102
+ uploaded_file.flash #=> 16
103
+ ```
104
+
105
+ ### Ensuring file
52
106
 
53
107
  The `io` might not always be a file object, so if you're using an analyzer
54
108
  which requires the source file to be on disk, you can use `Shrine.with_file` to
55
109
  ensure you have a file object.
56
110
 
57
111
  ```rb
58
- add_metadata do |io, context|
59
- movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) }
112
+ require "streamio-ffmpeg" # https://github.com/streamio/streamio-ffmpeg
113
+
114
+ class VideoUploader < Shrine
115
+ add_metadata do |io|
116
+ movie = Shrine.with_file(io) do |file|
117
+ FFMPEG::Movie.new(file.path)
118
+ end
119
+
120
+ { "duration" => movie.duration,
121
+ "bitrate" => movie.bitrate,
122
+ "resolution" => movie.resolution,
123
+ "frame_rate" => movie.frame_rate }
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Uploader options
129
+
130
+ Uploader options are also yielded to the block, you can access them for more
131
+ context:
60
132
 
61
- { "duration" => movie.duration,
62
- "bitrate" => movie.bitrate,
63
- "resolution" => movie.resolution,
64
- "frame_rate" => movie.frame_rate }
133
+ ```rb
134
+ add_metadata do |io, **options|
135
+ options #=>
136
+ # {
137
+ # record: #<Photo>,
138
+ # name: :image,
139
+ # action: :store,
140
+ # metadata: { ... },
141
+ # ...
142
+ # }
65
143
  end
66
144
  ```
67
145
 
68
- Any previously extracted metadata can be accessed via `context[:metadata]`:
146
+ #### Metadata
147
+
148
+ The `:metadata` option holds metadata that was extracted so far:
69
149
 
70
150
  ```rb
71
- add_metadata :foo do |io, context|
72
- context[:metadata] #=>
151
+ add_metadata :foo do |io, metadata:, **|
152
+ metadata #=>
73
153
  # {
74
154
  # "size" => 239823,
75
155
  # "filename" => "nature.jpg",
@@ -79,8 +159,8 @@ add_metadata :foo do |io, context|
79
159
  "foo"
80
160
  end
81
161
 
82
- add_metadata :bar do |io, context|
83
- context[:metadata] #=>
162
+ add_metadata :bar do |io, metadata:, **|
163
+ metadata #=>
84
164
  # {
85
165
  # "size" => 239823,
86
166
  # "filename" => "nature.jpg",
@@ -92,4 +172,25 @@ add_metadata :bar do |io, context|
92
172
  end
93
173
  ```
94
174
 
95
- [add_metadata]: /lib/shrine/plugins/add_metadata.rb
175
+ ## Updating metadata
176
+
177
+ If you just wish to add some custom metadata to existing uploads, you can do it
178
+ with `UploadedFile#add_metadata` (and write the changes back to the model):
179
+
180
+ ```rb
181
+ attacher.file.add_metadata("foo" => "bar")
182
+ attacher.write # write changes to the model attribute
183
+
184
+ attacher.file.metadata #=> { ..., "foo" => "bar" }
185
+ ```
186
+
187
+ You can also use the `Attacher#add_metadata` shorthand, which also takes care
188
+ of syncing the model:
189
+
190
+ ```rb
191
+ attacher.add_metadata("foo" => "bar")
192
+
193
+ attacher.file.metadata #=> { ..., "foo" => "bar" }
194
+ ```
195
+
196
+ [add_metadata]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/add_metadata.rb