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/attacher.md CHANGED
@@ -1,283 +1,434 @@
1
- # Using Attacher
1
+ ---
2
+ title: Using Attacher
3
+ ---
2
4
 
3
- The most convenient way to use Shrine is through the model, using the interface
4
- provided by Shrine's attachment module. This way you can interact with the
5
- attachment just like with any other column attribute, and adding attachment
6
- fields to the form just works.
5
+ This guide explains what is `Shrine::Attacher` and how to use it.
6
+
7
+ ## Introduction
8
+
9
+ The attachment logic is handled by a `Shrine::Attacher` object. The
10
+ `Shrine::Attachment` module simply provides a convenience layer around a
11
+ `Shrine::Attacher` object, which can be accessed via the `#<name>_attacher`
12
+ attribute.
7
13
 
8
14
  ```rb
9
- class Photo < Sequel::Model
10
- include ImageUploader::Attachment.new(:image)
15
+ class Photo
16
+ include ImageUploader::Attachment(:image)
11
17
  end
12
18
  ```
19
+ ```rb
20
+ photo = Photo.new
21
+ photo.image_attacher #=> #<ImageUploader::Attacher>
22
+ ```
13
23
 
14
- However, if you don't want to add additional methods on the model and prefer
15
- explicitness, or you need more control, you can achieve the same behaviour
16
- using the `Shrine::Attacher` object, which is what the attachment interface
17
- uses under the hood.
24
+ We can also instantiate the same `Shrine::Attacher` object directly:
18
25
 
19
26
  ```rb
20
- attacher = ImageUploader::Attacher.new(photo, :image) # equivalent to `photo.image_attacher`
21
- attacher.assign(file) # equivalent to `photo.image = file`
22
- attacher.get # equivalent to `photo.image`
27
+ attacher = ImageUploader::Attacher.from_model(photo, :image)
28
+ attacher.file # called by `photo.image`
29
+ attacher.url # called by `photo.image_url`
23
30
  ```
24
31
 
25
- The attacher will use the `<attachment>_data` attribute for storing information
26
- about the attachment.
32
+ The [`model`][model], [`entity`][entity], and [`column`][column] plugins
33
+ provide additional `Shrine::Attacher` methods (such as
34
+ `Shrine::Attacher.from_model` we see above), but in this guide we'll focus only
35
+ on the core `Shrine::Attacher` methods.
36
+
37
+ So, we'll assume a `Shrine::Attacher` object not backed by any model/entity:
27
38
 
28
39
  ```rb
29
- attacher.data_attribute #=> :image_data
40
+ attacher = Shrine::Attacher.new
30
41
  ```
31
42
 
32
- ## Initializing
43
+ ## Storage
33
44
 
34
- The attacher object exposes the objects it uses:
45
+ By default, an `Attacher` will use the `:cache` storage as the **temporary**
46
+ storage, and the `:store` storage as the **permanent** storage.
35
47
 
36
48
  ```rb
37
- attacher.record #=> #<Photo>
38
- attacher.name #=> :image
39
- attacher.cache #=> #<ImageUploader @storage_key=:cache>
40
- attacher.store #=> #<ImageUploader @storage_key=:store>
49
+ Shrine.storages = {
50
+ cache: Shrine::Storage::Memory.new,
51
+ store: Shrine::Storage::Memory.new,
52
+ }
53
+ ```
54
+ ```rb
55
+ attacher = Shrine::Attacher.new
56
+ attacher.cache_key #=> :cache
57
+ attacher.store_key #=> :store
41
58
  ```
42
59
 
43
- The attacher will automatically use `:cache` and `:store` storages, but you can
44
- also tell it to use different temporary and permanent storage:
60
+ We can also change the default storage:
45
61
 
46
62
  ```rb
47
- ImageUploader::Attacher.new(photo, :image, cache: :other_cache, store: :other_store)
63
+ attacher = Shrine::Attacher.new(cache: :other_cache, store: :other_store)
64
+ attacher.cache_key #=> :other_cache
65
+ attacher.store_key #=> :other_store
66
+ ```
48
67
 
49
- # OR
68
+ You can also change default attacher options on the `Shrine::Attachment`
69
+ module:
50
70
 
51
- photo.image_attacher(cache: :other_cache, store: :other_store)
52
- photo.image = file # uploads to :other_cache storage
53
- photo.save # promotes to :other_store storage
71
+ ```rb
72
+ class Photo
73
+ include ImageUploader::Attachment(:image, cache: :other_cache, store: :other_store)
74
+ end
54
75
  ```
55
76
 
56
- You can pass the `:cache` and `:store` options via `Attachment.new` too:
77
+ The `Attacher#cache` and `Attacher#store` methods will retrieve corresponding
78
+ uploaders:
57
79
 
58
80
  ```rb
59
- class Photo < Sequel::Model
60
- include ImageUploader::Attachment.new(:image, cache: :other_cache, store: :other_store)
61
- end
81
+ attacher.cache #=> #<MyUploader @storage_key=:cache>
82
+ attacher.store #=> #<MyUploader @storage_key=:store>
62
83
  ```
63
84
 
64
- Note that it's not necessary to use the temporary storage, see the next section
65
- for more details.
85
+ ## Attaching
66
86
 
67
- ## Assignment
87
+ ### Attaching cached
68
88
 
69
- The `#assign` method accepts either an IO object to be cached, or an already
70
- cached file in form of a JSON string, and assigns the cached result to record's
71
- `<attachment>_data` attribute.
89
+ For attaching files submitted via a web form, `Attacher#assign` can be used:
72
90
 
73
91
  ```rb
74
- # uploads the `io` object to temporary storage, and writes to the data column
75
- attacher.assign(io)
92
+ attacher.assign(file)
93
+ ```
94
+
95
+ If given a raw file, it will upload it to temporary storage:
76
96
 
77
- # writes the given cached file to the data column
78
- attacher.assign('{"id":"9260ea09d8effd.jpg","storage":"cache","metadata":{ ... }}')
97
+ ```rb
98
+ attacher.assign(file)
99
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:cache ...>
79
100
  ```
80
101
 
81
- When assigning an IO object, any additional options passed to `#assign` will be
82
- forwarded to `Shrine#upload`. This allows you to do things like overriding
83
- metadata, setting upload location, or passing upload options:
102
+ If given cached file data (JSON or Hash), it will set the cached file:
84
103
 
85
104
  ```rb
86
- attacher.assign io,
87
- metadata: { "filename" => "myfile.txt" },
88
- location: "custom/location",
89
- upload_options: { acl: "public-read" }
105
+ attacher.assign('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
106
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:cache ...>
90
107
  ```
91
108
 
92
- If you're attaching a cached file and want to override its metadata before
93
- assignment, you can do it like so:
109
+ If given an empty string, it will no-op:
94
110
 
95
111
  ```rb
96
- cached_file = Shrine.uploaded_file('{"id":"9260ea09d8effd.jpg","storage":"cache","metadata":{ ... }}')
97
- cached_file.metadata["filename"] = "myfile.txt"
112
+ attacher.assign("") # no-op
113
+ ```
114
+
115
+ If given `nil`, it will clear the attached file:
98
116
 
99
- attacher.assign(cached_file.to_json)
117
+ ```rb
118
+ attacher.file #=> <Shrine::UploadedFile>
119
+ attacher.assign(nil)
120
+ attacher.file #=> nil
100
121
  ```
101
122
 
102
- For security reasons `#assign` doesn't accept files uploaded to permanent
103
- storage, but you can use `#set` to attach any `Shrine::UploadedFile` object.
104
- You can use this to skip temporary storage altogether and upload files directly
105
- to permanent storage:
123
+ This plays nicely with the recommended HTML form fields for the attachment. If
124
+ you're not using the `hidden` form field (and therefore don't need empty
125
+ strings to be handled), you can also use `Attacher#attach_cached`:
106
126
 
107
127
  ```rb
108
- uploaded_file = attacher.store!(file) # upload a file directly to permanent storage
109
- attacher.set(uploaded_file) # attach the uploaded file
128
+ # uploads file to cache
129
+ attacher.attach_cached(file)
130
+
131
+ # sets cached file
132
+ attacher.attach_cached('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
133
+ attacher.attach_cached({ "id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... } })
134
+ attacher.attach_cached({ id: "asdf.jpg", storage: "cache", metadata: { ... } })
135
+
136
+ # unsets attached file
137
+ attacher.attach_cached(nil)
110
138
  ```
111
139
 
112
- ## Retrieval
140
+ ### Attaching stored
113
141
 
114
- The `#get` method reads record's `<attachment>_data` attribute, and constructs
115
- a `Shrine::UploadedFile` object from it.
142
+ The `Attacher#attach` method uploads a given file to permanent storage:
116
143
 
117
144
  ```rb
118
- attacher.get #=> #<Shrine::UploadedFile>
145
+ attacher.attach(file)
146
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:store ...>
119
147
  ```
120
148
 
121
- The `#read` method will just return the value of the underlying
122
- `<attachment>_data` attribute.
149
+ This method is useful when attaching files from scripts, where validation
150
+ doesn't need to be performed, and where temporary storage can be skipped.
151
+
152
+ You can specify a different destination storage with the `:storage` option:
123
153
 
124
154
  ```rb
125
- attacher.read #=> '{"id":"dsg024lfs.jpg","storage":"cache","metadata":{...}}'
155
+ attacher.attach(file, storage: :other_store)
156
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:other_store ...>
126
157
  ```
127
158
 
128
- In general you can use `#uploaded_file` to contruct a `Shrine::UploadedFile`
129
- from a JSON string.
159
+ Any additional options passed to `Attacher#attach`, `Attacher#attach_cached`
160
+ and `Attacher#assign` are forwarded to the uploader:
130
161
 
131
162
  ```rb
132
- attachment_data = '{"id":"dsg024lfs.jpg","storage":"cache","metadata":{...}}'
133
- attacher.uploaded_file(attachment_data) #=> #<Shrine::UploadedFile>
163
+ attacher.attach(file, metadata: { "foo" => "bar" }) # adding metadata
164
+ attacher.attach(file, upload_options: { acl: "private" }) # setting upload options
165
+ attacher.attach(file, location: "path/to/file") # setting upload location
134
166
  ```
135
167
 
136
- ## URL
168
+ ### Uploading
137
169
 
138
- The `#url` method returns the URL to the attached file, and returns `nil` if
139
- no file is attached.
170
+ If you want to upload a file to without attaching it, you can use
171
+ `Attacher#upload`:
140
172
 
141
173
  ```rb
142
- attacher.url # calls `attacher.get.url`
174
+ attacher.upload(file) #=> #<Shrine::UploadedFile storage=:store ...>
175
+ attacher.upload(file, :cache) #=> #<Shrine::UploadedFile storage=:cache ...>
176
+ attacher.upload(file, :other_store) #=> #<Shrine::UploadedFile storage=:other_store ...>
143
177
  ```
144
178
 
145
- ## State
179
+ This is useful if you want to attacher [context](#context) such as `:record`
180
+ and `:name` to be automatically passed to the uploader.
146
181
 
147
- You can ask the attacher whether the currently attached file is cached or
148
- stored.
182
+ You can also pass additional options for `Shrine#upload`:
149
183
 
150
184
  ```rb
151
- attacher.cached?
152
- attacher.stored?
185
+ attacher.upload(file, metadata: { "foo" => "bar" }) # adding metadata
186
+ attacher.upload(file, upload_options: { acl: "private" }) # setting upload options
187
+ attacher.upload(file, location: "path/to/file") # setting upload location
153
188
  ```
154
189
 
155
- ## Validations
190
+ ### Changes
156
191
 
157
- Whenever a file is assigned via `#assign` or `#set`, the file validations are
158
- automatically run, and you can access the validation errors through `#errors`:
192
+ When a new file is attached, calling [`Attacher#finalize`](#finalization) will
193
+ perform additional actions such as promotion and deleting any previous file.
194
+ It will also trigger [validation].
195
+
196
+ You can check whether a new file has been attached with `Attacher#changed?`:
159
197
 
160
198
  ```rb
161
- attacher.assign(large_file)
162
- attacher.errors #=> ["is larger than 10 MB"]
199
+ attacher.changed? #=> true
163
200
  ```
164
201
 
165
- ## Promoting
202
+ You can use `Attacher#change` to attach an `UploadedFile` object as is:
166
203
 
167
- After the attachment is assigned and you run validations, it should be promoted
168
- to permanent storage after the record is saved. You can use `#finalize` for
169
- that, since that will also automatically delete any previously attached files.
204
+ ```rb
205
+ uploaded_file #=> #<Shrine::UploadedFile id="foo" ...>
206
+ attacher.change(uploaded_file)
207
+ attacher.file #=> #<Shrine::UploadedFile id="foo" ...>
208
+ attacher.changed? #=> true
209
+ ```
210
+
211
+ If you want to attach a file without triggering dirty tracking or validation,
212
+ you can use `Attacher#set`:
213
+
214
+ ```rb
215
+ uploaded_file #=> #<Shrine::UploadedFile id="foo" ...>
216
+ attacher.set(uploaded_file)
217
+ attacher.file #=> #<Shrine::UploadedFile id="foo" ...>
218
+ attacher.changed? #=> false
219
+ ```
220
+
221
+ ## Finalizing
222
+
223
+ After the file is attached (with `Attacher#assign`, `Attacher#attach_cached`,
224
+ or `Attacher#attach`), and data has been validated, the attachment can be
225
+ "finalized":
170
226
 
171
227
  ```rb
172
- # Replaces previous attachment and replaces new
173
228
  attacher.finalize
174
229
  ```
175
230
 
176
- This is normally automatically added to a callback by the ORM plugin when going
177
- through the model. Internally this calls `#promote`, which uploads a given
178
- `Shrine::UploadedFile` to permanent storage, and swaps it with the current
179
- attachment, unless a new file was attached in the meanwhile.
231
+ The `Attacher#finalize` method performs [promoting](#promoting) and
232
+ [replacing](#replacing). It also clears dirty tracking:
180
233
 
181
234
  ```rb
182
- # uploads cached file to permanent storage and replaces the current one
183
- attacher.promote(cached_file, action: :custom_name)
235
+ attacher.changed? #=> true
236
+ attacher.finalize
237
+ attacher.changed? #=> false
184
238
  ```
185
239
 
186
- The `:action` parameter is optional; it can be used for triggering a certain
187
- processing block, or for additional context during instrumentation.
240
+ ### Promoting
241
+
242
+ `Attacher#finalize` checks if the attached file has been uploaded to temporary
243
+ storage, and in this case uploads it to permanent storage.
244
+
245
+ ```rb
246
+ attacher.attach_cached(io)
247
+ attacher.finalize # uploads attached file to permanent storage
248
+ attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
249
+ ```
188
250
 
189
- As a matter of fact, all additional options passed to `#promote` will be
190
- forwarded to `Shrine#upload`. So unless you're generating versions, you can do
191
- things like override metadata, set upload location, or pass upload options:
251
+ Internally it calls `Attacher#promote_cached`, which you can call directly if
252
+ you want to pass any promote options:
192
253
 
193
254
  ```rb
194
- attacher.promote cached_file,
195
- metadata: { "filename" => "myfile.txt" },
196
- location: "custom/location",
197
- upload_options: { acl: "public-read" }
255
+ attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
256
+ attacher.promote_cached # uploads attached file to permanent storage if new and cached
257
+ attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
198
258
  ```
199
259
 
200
- Internally `#promote` calls `#swap`, which will update the record with any
201
- uploaded file, but will reload the record to check if the current attachment
202
- hasn't changed (if the `backgrounding` plugin is loaded).
260
+ You can also call `Attacher#promote` if you want to upload attached file to
261
+ permanent storage, regardless of whether it's cached or newly attached:
203
262
 
204
263
  ```rb
205
- attacher.swap(uploaded_file)
264
+ attacher.promote
206
265
  ```
207
266
 
208
- Both `#promote` and `#swap` are useful for [file migrations].
267
+ Any options passed to `Attacher#promote_cached` or `Attacher#promote` will be
268
+ forwarded to `Shrine#upload`.
209
269
 
210
- ## Backgrounding
270
+ ### Replacing
211
271
 
212
- When the `backgrounding` plugin is loaded, it allows you to promote and delete
213
- files in the background, and the corresponding methods are prefixed with `_`:
272
+ `Attacher#finalize` also deletes the previous attached file if any:
214
273
 
215
274
  ```rb
216
- Shrine.plugin :backgrounding
217
- Shrine::Attacher.promote { |data| PromoteJob.perform_async(data) }
218
- Shrine::Attacher.delete { |data| DeleteJob.perform_async(data) }
275
+ previous_file = attacher.file
276
+
277
+ attacher.attach(io)
278
+ attacher.finalize
279
+
280
+ previous_file.exists? #=> false
219
281
  ```
282
+
283
+ Internally it calls `Attacher#destroy_previous` to do this:
284
+
220
285
  ```rb
221
- attacher._promote(cached_file) # calls the registered `Attacher.promote` block
222
- attacher._delete(uploaded_file) # calls the registered `Attacher.delete` block
286
+ attacher.destroy_previous
223
287
  ```
224
288
 
225
- These are automatically used when using Shrine through models.
289
+ ## Retrieving
226
290
 
227
- ## Context
291
+ ### File
292
+
293
+ The `Attacher#file` is used to retrieve the attached file:
294
+
295
+ ```rb
296
+ attacher.file #=> #<Shrine::UploadedFile>
297
+ ```
228
298
 
229
- The attacher sends `#context` to each upload/delete call to the uploader. By
230
- default it will hold `:record` and `:name`:
299
+ If no file is attached, `Attacher#file` returns nil:
231
300
 
232
301
  ```rb
233
- attacher.context #=>
302
+ attacher.file #=> nil
303
+ ```
304
+
305
+ If you want to assert a file is attached, you can use `Attacher#file!`:
306
+
307
+ ```rb
308
+ attacher.file! #~> Shrine::Error: no file is attached
309
+ ```
310
+
311
+ ### Attached
312
+
313
+ You can also check whether a file is attached with `Attacher#attached?`:
314
+
315
+ ```rb
316
+ attacher.attached? # returns whether file is attached
317
+ ```
318
+
319
+ If you want to check to which storage a file is uploaded to, you can use
320
+ `Attacher#cached?` and `Attacher#stored?`:
321
+
322
+ ```rb
323
+ attacher.attach(io)
324
+ attacher.stored? #=> true (checks current file)
325
+ attacher.stored?(attacher.file) #=> true (checks given file)
326
+ ```
327
+ ```rb
328
+ attacher.attach_cached(io)
329
+ attacher.cached? #=> true (checks current file)
330
+ attacher.cached?(attacher.file) #=> true (checks given file)
331
+ ```
332
+
333
+ ### URL
334
+
335
+ The attached file URL can be retrieved with `Attacher#url`:
336
+
337
+ ```rb
338
+ attacher.url #=> "https://example.com/file.jpg"
339
+ ```
340
+
341
+ If no file is attached, `Attacher#url` returns `nil`:
342
+
343
+ ```rb
344
+ attacher.url #=> nil
345
+ ```
346
+
347
+ ### Data
348
+
349
+ You can retrieve plain attached file data with `Attacher#data`:
350
+
351
+ ```rb
352
+ attacher.data #=>
234
353
  # {
235
- # record: #<Photo...>,
236
- # name: :image,
354
+ # "id" => "abc123.jpg",
355
+ # "storage" => "store",
356
+ # "metadata" => {
357
+ # "size" => 223984,
358
+ # "filename" => "nature.jpg",
359
+ # "mime_type" => "image/jpeg",
360
+ # }
237
361
  # }
238
362
  ```
239
363
 
240
- However, you can change/add additional context to be sent when calling the
241
- uploaders:
364
+ This data can be stored somewhere, and later the attached file can be loaded
365
+ from it:
242
366
 
243
367
  ```rb
244
- attacher.context[:foo] = "bar"
368
+ # new attacher
369
+ attacher = Shrine::Attacher.from_data(data)
370
+ attacher.file #=> #<Shrine::UploadedFile>
371
+
372
+ # existing attacher
373
+ attacher.file #=> nil
374
+ attacher.load_data(data)
375
+ attacher.file #=> #<Shrine::UploadedFile>
376
+ ```
377
+
378
+ Internally `Attacher#uploaded_file` is used to convert uploaded file data into
379
+ a `Shrine::UploadedFile` object:
380
+
381
+ ```rb
382
+ attacher.uploaded_file("id" => "...", "storage" => "...", "metadata" => { ... }) #=> #<Shrine::UploadedFile>
383
+ attacher.uploaded_file(id: "...", storage: "...", metadata: { ... }) #=> #<Shrine::UploadedFile>
384
+ attacher.uploaded_file('{"id":"...","storage":"...","metadata":{...}}') #=> #<Shrine::UploadedFile>
245
385
  ```
246
386
 
247
- This is useful for example if you have immutable model instances, and you want
248
- to assign a new updated instance. For example both foreground and background
249
- `#promote` requires that the record is persisted (and its `#id` is present).
387
+ You will likely want to use a higher level abstraction for saving and loading
388
+ this data, see [`column`][column], [`entity`][entity] and [`model`][model]
389
+ plugins for more details.
250
390
 
251
- ## Uploading and deleting
391
+ ## Deleting
252
392
 
253
- Normally you can upload and delete directly by using the uploader.
393
+ The attached file can be deleted via `Attacher#destroy_attached`:
254
394
 
255
395
  ```rb
256
- uploader = ImageUploader.new(:store)
257
- uploaded_file = uploader.upload(image) # uploads the file to `:store` storage
258
- uploader.delete(uploaded_file) # deletes the uploaded file from `:store`
396
+ attacher.destroy_attached
259
397
  ```
260
398
 
261
- But the attacher also has wrapper methods for uploading and deleting, which
262
- also automatically pass in the attacher `#context` (which includes `:record`
263
- and `:name`):
399
+ This will not delete cached files, to not interrupt any potential
400
+ [backgrounding] that might be in process.
401
+
402
+ If you want to delete the attached file regardless of storage it's uploaded to,
403
+ you can use `Attacher#destroy`:
264
404
 
265
405
  ```rb
266
- attacher.cache!(file) # uploads file to temporary storage
267
- # => #<Shrine::UploadedFile: @data={"storage" => "cache", ...}>
268
- attacher.store!(file) # uploads file to permanent storage
269
- # => #<Shrine::UploadedFile: @data={"storage" => "store", ...}>
270
- attacher.delete!(uploaded_file) # deletes uploaded file from storage
406
+ attacher.destroy
271
407
  ```
272
408
 
273
- These methods only upload/delete files, they don't write to record's data
274
- column. You can also pass additional options for `Shrine#upload` and
275
- `Shrine#delete`:
409
+ ## Context
410
+
411
+ The `Attacher#context` hash is automatically forwarded to the uploader on
412
+ `Attacher#upload`. When [`model`][model] or [`entity`][model] plugin is loaded,
413
+ this will include `:record` and `:name` values:
276
414
 
277
415
  ```rb
278
- attacher.cache!(file, upload_options: { acl: "public-read" })
279
- attacher.store!(file, location: "custom/location")
280
- attacher.delete!(uploaded_file, foo: "bar")
416
+ attacher = Shrine::Attacher.from_model(photo, :image)
417
+ attacher.context #=> { record: #<Photo>, name: :image }
281
418
  ```
282
419
 
283
- [file migrations]: /doc/migrating_storage.md#readme
420
+ You can add here any other parameters you want to forward to the uploader:
421
+
422
+ ```rb
423
+ attacher.context[:foo] = "bar"
424
+ ```
425
+
426
+ However, it's generally better practice to pass uploader options directly to
427
+ `Attacher#assign`, `Attacher#attach`, `Attacher#promote` or any other method
428
+ that's calling `Attacher#upload`.
429
+
430
+ [validation]: https://shrinerb.com/docs/plugins/validation
431
+ [column]: https://shrinerb.com/docs/plugins/column
432
+ [entity]: https://shrinerb.com/docs/plugins/entity
433
+ [model]: https://shrinerb.com/docs/plugins/model
434
+ [backgrounding]: https://shrinerb.com/docs/plugins/backgrounding