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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +523 -41
- data/LICENSE.txt +1 -1
- data/README.md +83 -979
- data/doc/advantages.md +231 -204
- data/doc/attacher.md +304 -153
- data/doc/carrierwave.md +297 -226
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +103 -21
- data/doc/changing_storage.md +110 -0
- data/doc/creating_persistence_plugins.md +132 -0
- data/doc/creating_plugins.md +43 -23
- data/doc/creating_storages.md +19 -5
- data/doc/design.md +147 -97
- data/doc/direct_s3.md +38 -28
- data/doc/external/articles.md +63 -0
- data/doc/external/extensions.md +53 -0
- data/doc/external/misc.md +32 -0
- data/doc/getting_started.md +1156 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +93 -30
- data/doc/paperclip.md +384 -262
- data/doc/plugins/activerecord.md +177 -46
- data/doc/plugins/add_metadata.md +139 -38
- data/doc/plugins/atomic_helpers.md +217 -0
- data/doc/plugins/backgrounding.md +156 -98
- data/doc/plugins/cached_attachment_data.md +7 -5
- data/doc/plugins/column.md +121 -0
- data/doc/plugins/data_uri.md +23 -22
- data/doc/plugins/default_storage.md +36 -10
- data/doc/plugins/default_url.md +30 -13
- data/doc/plugins/delete_raw.md +4 -2
- data/doc/plugins/derivation_endpoint.md +186 -101
- data/doc/plugins/derivatives.md +839 -0
- data/doc/plugins/determine_mime_type.md +4 -2
- data/doc/plugins/download_endpoint.md +64 -8
- data/doc/plugins/dynamic_storage.md +5 -3
- data/doc/plugins/entity.md +263 -0
- data/doc/plugins/form_assign.md +55 -0
- data/doc/plugins/included.md +31 -8
- data/doc/plugins/infer_extension.md +21 -10
- data/doc/plugins/instrumentation.md +38 -16
- data/doc/plugins/keep_files.md +16 -17
- data/doc/plugins/metadata_attributes.md +42 -13
- data/doc/plugins/mirroring.md +118 -0
- data/doc/plugins/model.md +210 -0
- data/doc/plugins/module_include.md +4 -2
- data/doc/plugins/multi_cache.md +24 -0
- data/doc/plugins/persistence.md +101 -0
- data/doc/plugins/presign_endpoint.md +9 -4
- data/doc/plugins/pretty_location.md +16 -3
- data/doc/plugins/processing.md +4 -2
- data/doc/plugins/rack_file.md +8 -2
- data/doc/plugins/rack_response.md +6 -2
- data/doc/plugins/recache.md +4 -2
- data/doc/plugins/refresh_metadata.md +49 -9
- data/doc/plugins/remote_url.md +84 -47
- data/doc/plugins/remove_attachment.md +27 -6
- data/doc/plugins/remove_invalid.md +21 -6
- data/doc/plugins/restore_cached_data.md +11 -3
- data/doc/plugins/sequel.md +159 -35
- data/doc/plugins/signature.md +16 -5
- data/doc/plugins/store_dimensions.md +14 -2
- data/doc/plugins/tempfile.md +4 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +13 -13
- data/doc/plugins/upload_options.md +6 -4
- data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
- data/doc/plugins/validation.md +97 -0
- data/doc/plugins/validation_helpers.md +16 -13
- data/doc/plugins/versions.md +15 -19
- data/doc/processing.md +438 -221
- data/doc/refile.md +188 -170
- data/doc/release_notes/1.0.0.md +4 -0
- data/doc/release_notes/1.1.0.md +6 -2
- data/doc/release_notes/1.2.0.md +4 -0
- data/doc/release_notes/1.3.0.md +4 -0
- data/doc/release_notes/1.4.0.md +4 -0
- data/doc/release_notes/1.4.1.md +4 -0
- data/doc/release_notes/1.4.2.md +4 -0
- data/doc/release_notes/2.0.0.md +4 -0
- data/doc/release_notes/2.0.1.md +4 -0
- data/doc/release_notes/2.1.0.md +5 -1
- data/doc/release_notes/2.1.1.md +4 -0
- data/doc/release_notes/2.10.0.md +4 -0
- data/doc/release_notes/2.10.1.md +4 -0
- data/doc/release_notes/2.11.0.md +4 -0
- data/doc/release_notes/2.12.0.md +4 -0
- data/doc/release_notes/2.13.0.md +4 -0
- data/doc/release_notes/2.14.0.md +5 -1
- data/doc/release_notes/2.15.0.md +11 -7
- data/doc/release_notes/2.16.0.md +4 -0
- data/doc/release_notes/2.17.0.md +4 -0
- data/doc/release_notes/2.18.0.md +4 -0
- data/doc/release_notes/2.19.0.md +6 -3
- data/doc/release_notes/2.2.0.md +4 -0
- data/doc/release_notes/2.3.0.md +4 -0
- data/doc/release_notes/2.3.1.md +4 -0
- data/doc/release_notes/2.4.0.md +4 -0
- data/doc/release_notes/2.4.1.md +4 -0
- data/doc/release_notes/2.5.0.md +4 -0
- data/doc/release_notes/2.6.0.md +4 -0
- data/doc/release_notes/2.6.1.md +4 -0
- data/doc/release_notes/2.7.0.md +4 -0
- data/doc/release_notes/2.8.0.md +4 -0
- data/doc/release_notes/2.9.0.md +4 -0
- data/doc/release_notes/3.0.0.md +981 -0
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/release_notes/3.5.0.md +63 -0
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/retrieving_uploads.md +5 -2
- data/doc/securing_uploads.md +60 -37
- data/doc/storage/file_system.md +20 -3
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +122 -78
- data/doc/testing.md +141 -133
- data/doc/upgrading_to_3.md +708 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +292 -169
- data/lib/shrine/attachment.rb +13 -46
- data/lib/shrine/plugins/_persistence.rb +93 -0
- data/lib/shrine/plugins/activerecord.rb +77 -34
- data/lib/shrine/plugins/add_metadata.rb +25 -17
- data/lib/shrine/plugins/atomic_helpers.rb +119 -0
- data/lib/shrine/plugins/backgrounding.rb +77 -113
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
- data/lib/shrine/plugins/column.rb +102 -0
- data/lib/shrine/plugins/data_uri.rb +38 -36
- data/lib/shrine/plugins/default_storage.rb +45 -15
- data/lib/shrine/plugins/default_url.rb +12 -24
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +10 -16
- data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
- data/lib/shrine/plugins/derivatives.rb +645 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +118 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +158 -0
- data/lib/shrine/plugins/form_assign.rb +108 -0
- data/lib/shrine/plugins/included.rb +6 -6
- data/lib/shrine/plugins/infer_extension.rb +17 -20
- data/lib/shrine/plugins/instrumentation.rb +59 -43
- data/lib/shrine/plugins/keep_files.rb +3 -15
- data/lib/shrine/plugins/metadata_attributes.rb +28 -19
- data/lib/shrine/plugins/mirroring.rb +142 -0
- data/lib/shrine/plugins/model.rb +160 -0
- data/lib/shrine/plugins/module_include.rb +3 -3
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/presign_endpoint.rb +27 -28
- data/lib/shrine/plugins/pretty_location.rb +15 -9
- data/lib/shrine/plugins/processing.rb +22 -9
- data/lib/shrine/plugins/rack_file.rb +2 -42
- data/lib/shrine/plugins/rack_response.rb +21 -10
- data/lib/shrine/plugins/recache.rb +6 -5
- data/lib/shrine/plugins/refresh_metadata.rb +13 -11
- data/lib/shrine/plugins/remote_url.rb +49 -49
- data/lib/shrine/plugins/remove_attachment.rb +12 -6
- data/lib/shrine/plugins/remove_invalid.rb +19 -8
- data/lib/shrine/plugins/restore_cached_data.rb +13 -7
- data/lib/shrine/plugins/sequel.rb +86 -36
- data/lib/shrine/plugins/signature.rb +10 -16
- data/lib/shrine/plugins/store_dimensions.rb +35 -40
- data/lib/shrine/plugins/tempfile.rb +1 -3
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +28 -24
- data/lib/shrine/plugins/upload_options.rb +14 -15
- data/lib/shrine/plugins/url_options.rb +31 -0
- data/lib/shrine/plugins/validation.rb +80 -0
- data/lib/shrine/plugins/validation_helpers.rb +35 -58
- data/lib/shrine/plugins/versions.rb +107 -87
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/storage/file_system.rb +46 -64
- data/lib/shrine/storage/linter.rb +42 -7
- data/lib/shrine/storage/memory.rb +49 -0
- data/lib/shrine/storage/s3.rb +173 -160
- data/lib/shrine/uploaded_file.rb +32 -32
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +87 -150
- data/shrine.gemspec +11 -12
- metadata +92 -82
- data/doc/migrating_storage.md +0 -76
- data/doc/plugins/backup.md +0 -31
- data/doc/plugins/copy.md +0 -24
- data/doc/plugins/delete_promoted.md +0 -12
- data/doc/plugins/direct_upload.md +0 -172
- data/doc/plugins/hooks.md +0 -58
- data/doc/plugins/logging.md +0 -42
- data/doc/plugins/migration_helpers.md +0 -60
- data/doc/plugins/moving.md +0 -19
- data/doc/plugins/multi_delete.md +0 -20
- data/doc/plugins/parallelize.md +0 -16
- data/doc/plugins/parsed_json.md +0 -23
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/background_helpers.rb +0 -5
- data/lib/shrine/plugins/backup.rb +0 -90
- data/lib/shrine/plugins/copy.rb +0 -50
- data/lib/shrine/plugins/delete_promoted.rb +0 -20
- data/lib/shrine/plugins/direct_upload.rb +0 -217
- data/lib/shrine/plugins/hooks.rb +0 -90
- data/lib/shrine/plugins/logging.rb +0 -142
- data/lib/shrine/plugins/migration_helpers.rb +0 -70
- data/lib/shrine/plugins/moving.rb +0 -57
- data/lib/shrine/plugins/multi_delete.rb +0 -32
- data/lib/shrine/plugins/parallelize.rb +0 -78
- data/lib/shrine/plugins/parsed_json.rb +0 -29
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Atomic Helpers
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The [`atomic_helpers`][atomic_helpers] plugin provides API for retrieving and
|
|
6
|
+
persisting attachments in a concurrency-safe way, which is especially useful
|
|
7
|
+
when using the `backgrounding` plugin. The database plugins (`activerecord`
|
|
8
|
+
and `sequel`) implement atomic promotion and atomic persistence on top of this
|
|
9
|
+
plugin.
|
|
10
|
+
|
|
11
|
+
```rb
|
|
12
|
+
plugin :atomic_helpers
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Problem Statement
|
|
16
|
+
|
|
17
|
+
What happens if two different processors (web workers, background jobs,
|
|
18
|
+
command-line executions, whatever) try to edit a shrine attachment
|
|
19
|
+
concurrently? The kinds of edits typically made include: "promoting a file",
|
|
20
|
+
moving it to a different storage and persisting that change in the model;
|
|
21
|
+
adding or changing a derivative; adding or changing a metadata element.
|
|
22
|
+
|
|
23
|
+
There are two main categories of "race condition":
|
|
24
|
+
|
|
25
|
+
1. The file could be switched out from under you. If you were promoting a file,
|
|
26
|
+
but some other process has *changed* the attachment, you don't want to
|
|
27
|
+
overwrite it with the promomoted version of the *prior* attacchment. Likewise,
|
|
28
|
+
if you were adding metadata or a derivative, they would be corresponding to a
|
|
29
|
+
certain attachment, and you don't want to accidentally add them to a now changed
|
|
30
|
+
attacchment for which they are inappropriate.
|
|
31
|
+
|
|
32
|
+
2. Overwriting each other's edits. Since all shrine (meta)data is stored in a
|
|
33
|
+
single JSON hash, standard implementations will write the entire JSON hash at
|
|
34
|
+
once to a rdbms column or other store. If two processes both read in the hash,
|
|
35
|
+
make a change to different keys in it, and then write it back out, the second
|
|
36
|
+
process to write will 'win' and overwrite changes made by the first.
|
|
37
|
+
|
|
38
|
+
The atomic helpers give you tools to avoid both of these sorts of race
|
|
39
|
+
conditions, under conditions of concurrent editing.
|
|
40
|
+
|
|
41
|
+
## High-level ORM helpers
|
|
42
|
+
|
|
43
|
+
If you are using the `sequel` or `activerecord` plugins, they give you two
|
|
44
|
+
higher-level helpers: `atomic_persist` and `atomic_promote`. See the
|
|
45
|
+
[persistence] documentation for more.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Retrieving
|
|
49
|
+
|
|
50
|
+
The `Attacher.retrieve` method provided by the plugin instantiates an attacher
|
|
51
|
+
from a record instance, attachment name and attachment data, asserting that the
|
|
52
|
+
given attachment data matches the attached file on the record.
|
|
53
|
+
|
|
54
|
+
```rb
|
|
55
|
+
# with a model instance
|
|
56
|
+
Shrine::Attacher.retrieve(
|
|
57
|
+
model: photo,
|
|
58
|
+
name: :image,
|
|
59
|
+
file: { "id" => "abc123", "storage" => "cache" },
|
|
60
|
+
)
|
|
61
|
+
#=> #<Shrine::Attacher ...>
|
|
62
|
+
|
|
63
|
+
# with an entity instance
|
|
64
|
+
Shrine::Attacher.retrieve(
|
|
65
|
+
entity: photo,
|
|
66
|
+
name: :image,
|
|
67
|
+
file: { "id" => "abc123", "storage" => "cache" },
|
|
68
|
+
)
|
|
69
|
+
#=> #<Shrine::Attacher ...>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If the record has `Shrine::Attachment` included, the `#<name>_attacher` method
|
|
73
|
+
will be called on the record, which will return the correct attacher class.
|
|
74
|
+
|
|
75
|
+
```rb
|
|
76
|
+
class Photo
|
|
77
|
+
include ImageUploader::Attachment(:image)
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
```rb
|
|
81
|
+
Shrine::Attacher.retrieve(model: photo, name: :image, file: { ... })
|
|
82
|
+
#=> #<ImageUploader::Attacher ...>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Otherwise it will call `Attacher.from_model`/`Attacher.from_entity` from the
|
|
86
|
+
`model`/`entity` plugin, in which case you need to make sure to call
|
|
87
|
+
`Attacher.retrieve` on the appropriate attacher class.
|
|
88
|
+
|
|
89
|
+
```rb
|
|
90
|
+
ImageUploader::Attacher.retrieve(entity: photo, name: :image, file: { ... })
|
|
91
|
+
#=> #<ImageUploader::Attacher ...>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If the attached file on the record doesn't match the provided attachment data,
|
|
95
|
+
a `Shrine::AttachmentChanged` exception is raised. Note that metadata is
|
|
96
|
+
allowed to differ, Shrine will only compare location and storage of the file.
|
|
97
|
+
|
|
98
|
+
```rb
|
|
99
|
+
photo.image_data #=> '{"id":"foo","storage":"store","metadata":{...}}'
|
|
100
|
+
|
|
101
|
+
Shrine::Attacher.retrieve(
|
|
102
|
+
model: photo,
|
|
103
|
+
name: :image,
|
|
104
|
+
file: { "id" => "bar", "storage" => "store" },
|
|
105
|
+
)
|
|
106
|
+
# ~> Shrine::AttachmentChanged: attachment has changed
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### File data
|
|
110
|
+
|
|
111
|
+
The `Attacher#file_data` method can be used for sending the attached file data
|
|
112
|
+
into a background job. It returns only location and storage of the attached
|
|
113
|
+
file, leaving out any metadata or derivatives data that `Attacher#data` would
|
|
114
|
+
return. This way the background job payload is kept light.
|
|
115
|
+
|
|
116
|
+
```rb
|
|
117
|
+
attacher.file_data #=> { "id" => "abc123", "storage" => "store" }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This value can then be passed as the `:file` argument to
|
|
121
|
+
`Shrine::Attacher.retrieve`.
|
|
122
|
+
|
|
123
|
+
## Promoting
|
|
124
|
+
|
|
125
|
+
The `Attacher#abstract_atomic_promote` method provided by the plugin promotes
|
|
126
|
+
the cached file to permanent storage, reloads the record to check whether the
|
|
127
|
+
attachment hasn't changed, and if not persists the promoted file.
|
|
128
|
+
|
|
129
|
+
Internally it calls `Attacher#abstract_atomic_persist` to do the persistence,
|
|
130
|
+
forwarding `:reload` and `:persist` options as well as a given block to it (see
|
|
131
|
+
the next section for more details).
|
|
132
|
+
|
|
133
|
+
```rb
|
|
134
|
+
# in the controller
|
|
135
|
+
attacher.attach_cached(io)
|
|
136
|
+
attacher.cached? #=> true
|
|
137
|
+
```
|
|
138
|
+
```rb
|
|
139
|
+
# in a background job
|
|
140
|
+
attacher.abstract_atomic_promote(reload: -> (&block) { ... }, persist: -> { ... })
|
|
141
|
+
attacher.stored? #=> true
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
If the attachment has changed during promotion, the promoted file is deleted and
|
|
145
|
+
a `Shrine::AttachmentChanged` exception is raised.
|
|
146
|
+
|
|
147
|
+
If you want to execute some code after the attachment change check but before
|
|
148
|
+
persistence, you can pass a block:
|
|
149
|
+
|
|
150
|
+
```rb
|
|
151
|
+
attacher.abstract_atomic_promote(**options) do |reloaded_attacher|
|
|
152
|
+
# this will be executed before persistence
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Any additional options to `Attacher#abstract_atomic_promote` are forwarded to
|
|
157
|
+
`Attacher#promote`.
|
|
158
|
+
|
|
159
|
+
## Persisting
|
|
160
|
+
|
|
161
|
+
The `Attacher#abstract_atomic_persist` method reloads the record to check
|
|
162
|
+
whether the attachment hasn't changed, and if not persists the attachment.
|
|
163
|
+
|
|
164
|
+
It requires reloader and persister to be passed in, as they will be specific to
|
|
165
|
+
the database library you're using. The reloader needs to call the given block
|
|
166
|
+
with the reloaded record, while the persister needs to persist the promoted
|
|
167
|
+
file.
|
|
168
|
+
|
|
169
|
+
```rb
|
|
170
|
+
attacher.abstract_atomic_persist(
|
|
171
|
+
reload: -> (&block) { ... }, # call the block with reloaded record
|
|
172
|
+
persist: -> { ... }, # persist promoted file
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
To illustrate, this is how the `Attacher#atomic_promote` method provided by the
|
|
177
|
+
`sequel` plugin is implemented:
|
|
178
|
+
|
|
179
|
+
```rb
|
|
180
|
+
attacher.abstract_atomic_persist(
|
|
181
|
+
reload: -> (&block) {
|
|
182
|
+
attacher.record.db.transaction do
|
|
183
|
+
block.call attacher.record.dup.lock! # the DB lock ensures no changes
|
|
184
|
+
end
|
|
185
|
+
},
|
|
186
|
+
persist: -> {
|
|
187
|
+
attacher.record.save_changes(validate: false)
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
By default, the file currently set on the attacher will be considered the
|
|
193
|
+
original file, and will be compared to the reloaded record. You can specify a
|
|
194
|
+
different original file:
|
|
195
|
+
|
|
196
|
+
```rb
|
|
197
|
+
original_file = attacher.file
|
|
198
|
+
|
|
199
|
+
attacher.set(new_file)
|
|
200
|
+
|
|
201
|
+
attacher.abstract_atomic_persist(original_file, **options)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
If you want to execute some code after the attachment change check but before
|
|
205
|
+
persistence, you can pass a block:
|
|
206
|
+
|
|
207
|
+
```rb
|
|
208
|
+
attacher.abstract_atomic_persist(**options) do |reloaded_attacher|
|
|
209
|
+
# this will be executed before persistence
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
[atomic_helpers]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/atomic_helpers.rb
|
|
214
|
+
|
|
215
|
+
[persistence]: https://shrinerb.com/docs/plugins/persistence
|
|
216
|
+
|
|
217
|
+
[backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
|
|
@@ -1,150 +1,208 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Backgrounding
|
|
3
|
+
---
|
|
2
4
|
|
|
3
5
|
The [`backgrounding`][backgrounding] plugin enables you to move promoting and
|
|
4
|
-
deleting of files
|
|
5
|
-
|
|
6
|
-
external storage service.
|
|
6
|
+
deleting of files into background jobs. This is especially useful if you're
|
|
7
|
+
processing [derivatives] and storing files to a remote storage service.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
```rb
|
|
10
|
+
Shrine.plugin :backgrounding # load the plugin globally
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Define background jobs that will promote and destroy attachments:
|
|
11
16
|
|
|
12
17
|
```rb
|
|
13
|
-
|
|
18
|
+
class PromoteJob
|
|
19
|
+
include Sidekiq::Worker
|
|
20
|
+
|
|
21
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
|
22
|
+
attacher_class = Object.const_get(attacher_class)
|
|
23
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Shrine::
|
|
25
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
|
26
|
+
attacher.atomic_promote
|
|
27
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
|
28
|
+
# attachment has changed or record has been deleted, nothing to do
|
|
29
|
+
end
|
|
30
|
+
end
|
|
18
31
|
```
|
|
32
|
+
```rb
|
|
33
|
+
class DestroyJob
|
|
34
|
+
include Sidekiq::Worker
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
the plugin loaded globally).
|
|
36
|
+
def perform(attacher_class, data)
|
|
37
|
+
attacher_class = Object.const_get(attacher_class)
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Attacher.promote { |data| PromoteJob.perform_async(data) }
|
|
28
|
-
Attacher.delete { |data| DeleteJob.perform_async(data) }
|
|
39
|
+
attacher = attacher_class.from_data(data)
|
|
40
|
+
attacher.destroy
|
|
41
|
+
end
|
|
29
42
|
end
|
|
30
43
|
```
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
needed for promotion/deletion. Now you just need to declare the job classes,
|
|
34
|
-
and inside them call `Attacher.promote` or `Attacher.delete`, this time with
|
|
35
|
-
the received data.
|
|
45
|
+
Then, in your initializer, you can configure all uploaders to use these jobs:
|
|
36
46
|
|
|
37
47
|
```rb
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
Shrine::Attacher.promote_block do
|
|
49
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name.to_s, file_data)
|
|
50
|
+
end
|
|
51
|
+
Shrine::Attacher.destroy_block do
|
|
52
|
+
DestroyJob.perform_async(self.class.name, data)
|
|
43
53
|
end
|
|
54
|
+
```
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
Alternatively, you can setup backgrounding only for specific uploaders:
|
|
57
|
+
|
|
58
|
+
```rb
|
|
59
|
+
class MyUploader < Shrine
|
|
60
|
+
Attacher.promote_block do
|
|
61
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name.to_s, file_data)
|
|
62
|
+
end
|
|
63
|
+
Attacher.destroy_block do
|
|
64
|
+
DestroyJob.perform_async(self.class.name, data)
|
|
49
65
|
end
|
|
50
66
|
end
|
|
51
67
|
```
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
If backgrounding blocks are registered, they will be automatically called on
|
|
72
|
+
`Attacher#promote_cached` and `Attacher#destroy_previous` (called by
|
|
73
|
+
`Attacher#finalize`), and `Attacher#destroy_attached`.
|
|
74
|
+
|
|
75
|
+
```rb
|
|
76
|
+
attacher.assign(file)
|
|
77
|
+
attacher.finalize # spawns promote job
|
|
78
|
+
attacher.destroy_attached # spawns destroy job
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
These methods are automatically called as part of the attachment flow if you're
|
|
82
|
+
using `Shrine::Attachment` with a persistence plugin such as `activerecord` or
|
|
83
|
+
`sequel`.
|
|
55
84
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
85
|
+
```rb
|
|
86
|
+
photo = Photo.new
|
|
87
|
+
photo.image = file
|
|
88
|
+
photo.save # spawns promote job
|
|
89
|
+
photo.destroy # spawns destroy job
|
|
90
|
+
```
|
|
59
91
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
### Atomic promotion
|
|
93
|
+
|
|
94
|
+
Inside the promote job, we use `Attacher.retrieve` and
|
|
95
|
+
`Attacher#atomic_promote` for concurrency safety. These methods are provided
|
|
96
|
+
by the [`atomic_helpers`][atomic_helpers] plugin, which is loaded automatically
|
|
97
|
+
by your persistence plugin (`activerecord`, `sequel`).
|
|
63
98
|
|
|
64
99
|
```rb
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
elsif user.avatar_attacher.stored? # background job has finished
|
|
68
|
-
# ...
|
|
69
|
-
end
|
|
100
|
+
attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
|
|
101
|
+
attacher.atomic_promote
|
|
70
102
|
```
|
|
71
103
|
|
|
72
|
-
|
|
104
|
+
Without concurrency safety, promotion would look like this:
|
|
73
105
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
106
|
+
```rb
|
|
107
|
+
attacher = record.send(:"#{name}_attacher")
|
|
108
|
+
attacher.promote
|
|
109
|
+
attacher.persist
|
|
110
|
+
```
|
|
79
111
|
|
|
80
|
-
|
|
81
|
-
* if record is not found, it finishes
|
|
82
|
-
* if record is found but attachment has changed, it finishes
|
|
83
|
-
2. uploads cached file to permanent storage
|
|
84
|
-
3. reloads the database record
|
|
85
|
-
* if record is not found, it deletes the promoted files and finishes
|
|
86
|
-
* if record is found but attachment has changed, it deletes the promoted files and finishes
|
|
87
|
-
4. updates the record with the promoted files
|
|
112
|
+
## Registering backgrounding blocks
|
|
88
113
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
114
|
+
The blocks registered by `Attacher.promote_block` and `Attacher#destroy_block`
|
|
115
|
+
are by default evaluated in context of a `Shrine::Attacher` instance. You can
|
|
116
|
+
also use the explicit version by declaring an attacher argument:
|
|
92
117
|
|
|
93
118
|
```rb
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
119
|
+
Shrine::Attacher.promote_block do |attacher|
|
|
120
|
+
PromoteJob.perform_async(
|
|
121
|
+
attacher.class.name,
|
|
122
|
+
attacher.record.class.name,
|
|
123
|
+
attacher.record.id,
|
|
124
|
+
attacher.name.to_s,
|
|
125
|
+
attacher.file_data,
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
Shrine::Attacher.destroy_block do |attacher|
|
|
130
|
+
DestroyJob.perform_async(
|
|
131
|
+
attacher.class.name,
|
|
132
|
+
attacher.data,
|
|
133
|
+
)
|
|
97
134
|
end
|
|
98
135
|
```
|
|
99
136
|
|
|
100
|
-
|
|
137
|
+
You can also register backgrounding blocks on attacher *instances* for more
|
|
138
|
+
flexibility:
|
|
101
139
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
140
|
+
```rb
|
|
141
|
+
photo.image_attacher.promote_block do |attacher|
|
|
142
|
+
PromoteJob.perform_async(
|
|
143
|
+
attacher.class.name,
|
|
144
|
+
attacher.record.class.name,
|
|
145
|
+
attacher.record.id,
|
|
146
|
+
attacher.name.to_s,
|
|
147
|
+
attacher.file_data,
|
|
148
|
+
current_user.id, # pass arguments known at the controller level
|
|
149
|
+
)
|
|
150
|
+
end
|
|
105
151
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
152
|
+
photo.image = file
|
|
153
|
+
photo.save # executes the promote block above
|
|
154
|
+
```
|
|
109
155
|
|
|
110
|
-
|
|
111
|
-
`Attacher.promote` returns, and do any additional tasks if you need to.
|
|
156
|
+
## Calling backgrounding blocks
|
|
112
157
|
|
|
113
|
-
|
|
158
|
+
If you want to call backgrounding blocks directly, you can do that by calling
|
|
159
|
+
`Attacher#promote_background` and `Attacher#destroy_background`.
|
|
114
160
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
161
|
+
```rb
|
|
162
|
+
attacher.promote_background # calls promote block directly
|
|
163
|
+
attacher.destroy_background # calls destroy block directly
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Any options passed to these methods will be forwarded to the background block:
|
|
119
167
|
|
|
120
168
|
```rb
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
169
|
+
attacher.promote_background(foo: "bar")
|
|
170
|
+
```
|
|
171
|
+
```rb
|
|
172
|
+
# with instance eval
|
|
173
|
+
Shrine::Attacher.promote_block do |**options|
|
|
174
|
+
options #=> { foo: "bar" }
|
|
175
|
+
end
|
|
124
176
|
|
|
125
|
-
#
|
|
126
|
-
attacher
|
|
127
|
-
|
|
177
|
+
# without instance eval
|
|
178
|
+
Shrine::Attacher.promote_block do |attacher, **options|
|
|
179
|
+
options #=> { foo: "bar" }
|
|
180
|
+
end
|
|
128
181
|
```
|
|
129
182
|
|
|
130
|
-
##
|
|
183
|
+
## Disabling backgrounding
|
|
131
184
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
185
|
+
If you've registered backgrounding blocks, but want to temporarily disable them
|
|
186
|
+
and make the execution synchronous, you can override them on the attacher level
|
|
187
|
+
and call the default behaviour:
|
|
135
188
|
|
|
136
189
|
```rb
|
|
137
|
-
|
|
138
|
-
|
|
190
|
+
photo.image_attacher.promote_block { promote } # promote synchronously
|
|
191
|
+
photo.image_attacher.destroy_block { destroy } # destroy synchronously
|
|
139
192
|
|
|
140
|
-
# ...
|
|
193
|
+
# ... now promotion and deletion will be synchronous ...
|
|
194
|
+
```
|
|
141
195
|
|
|
142
|
-
class
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
196
|
+
You can also do this on the class level if you want to disable backgrounding
|
|
197
|
+
that was set up by a superclass:
|
|
198
|
+
|
|
199
|
+
```rb
|
|
200
|
+
class MyUploader < Shrine
|
|
201
|
+
Attacher.promote_block { promote } # promote synchronously
|
|
202
|
+
Attacher.destroy_block { destroy } # destroy synchronously
|
|
147
203
|
end
|
|
148
204
|
```
|
|
149
205
|
|
|
150
|
-
[backgrounding]: /lib/shrine/plugins/backgrounding.rb
|
|
206
|
+
[backgrounding]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/backgrounding.rb
|
|
207
|
+
[derivatives]: https://shrinerb.com/docs/plugins/derivatives
|
|
208
|
+
[atomic_helpers]: https://shrinerb.com/docs/plugins/atomic_helpers
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Cached Attachment Data
|
|
3
|
+
---
|
|
2
4
|
|
|
3
5
|
The [`cached_attachment_data`][cached_attachment_data] plugin adds the ability
|
|
4
6
|
to retain the cached file across form redisplays, which means the file doesn't
|
|
@@ -13,13 +15,13 @@ cached file as JSON, and should be used to set the value of the hidden form
|
|
|
13
15
|
field.
|
|
14
16
|
|
|
15
17
|
```rb
|
|
16
|
-
|
|
18
|
+
photo.cached_image_data #=> '{"id":"38k25.jpg","storage":"cache","metadata":{...}}'
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
This method delegates to `Attacher#
|
|
21
|
+
This method delegates to `Attacher#cached_data`:
|
|
20
22
|
|
|
21
23
|
```rb
|
|
22
|
-
attacher.
|
|
24
|
+
attacher.cached_data #=> '{"id":"38k25.jpg","storage":"cache","metadata":{...}}'
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
[cached_attachment_data]: /lib/shrine/plugins/cached_attachment_data.rb
|
|
27
|
+
[cached_attachment_data]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/cached_attachment_data.rb
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Column
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The [`column`][column] plugin provides interface for serializing and
|
|
6
|
+
deserializing attachment data in format suitable for persisting in a database
|
|
7
|
+
column (JSON by default).
|
|
8
|
+
|
|
9
|
+
```rb
|
|
10
|
+
plugin :column
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Serializing
|
|
14
|
+
|
|
15
|
+
The `Attacher#column_data` method returns attached file data in serialized
|
|
16
|
+
format, ready to be persisted into a database column.
|
|
17
|
+
|
|
18
|
+
```rb
|
|
19
|
+
attacher.attach(io)
|
|
20
|
+
attacher.column_data #=> '{"id":"...","storage":"...","metadata":{...}}'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If there is no attached file, `nil` is returned.
|
|
24
|
+
|
|
25
|
+
```rb
|
|
26
|
+
attacher.column_data #=> nil
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If you want to retrieve this data as a Hash, use `Attacher#data` instead.
|
|
30
|
+
|
|
31
|
+
## Deserializing
|
|
32
|
+
|
|
33
|
+
The `Attacher.from_column` method instantiates the attacher from serialized
|
|
34
|
+
attached file data.
|
|
35
|
+
|
|
36
|
+
```rb
|
|
37
|
+
attacher = Shrine::Attacher.from_column('{"id":"...","storage":"...","metadata":{...}}')
|
|
38
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If `nil` is given, it means no attached file.
|
|
42
|
+
|
|
43
|
+
```rb
|
|
44
|
+
attacher = Shrine::Attacher.from_column(nil)
|
|
45
|
+
attacher.file #=> nil
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Any additional options are forwarded to `Attacher#initialize`.
|
|
49
|
+
|
|
50
|
+
```rb
|
|
51
|
+
attacher = Shrine::Attacher.from_column('{...}', store: :other_store)
|
|
52
|
+
attacher.store_key #=> :other_store
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you want to load attachment data into an existing attacher, use
|
|
56
|
+
`Attacher#load_column`.
|
|
57
|
+
|
|
58
|
+
```rb
|
|
59
|
+
attacher.file #=> nil
|
|
60
|
+
attacher.load_column('{"id":"...","storage":"...","metadata":{...}}')
|
|
61
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If you want to load attachment from a Hash, use `Attacher.from_data` or
|
|
65
|
+
`Attacher#load_data` instead.
|
|
66
|
+
|
|
67
|
+
## Serializer
|
|
68
|
+
|
|
69
|
+
By default, the `JSON` standard library is used for serializing hash data. With
|
|
70
|
+
the [`model`][model] and [`entity`][entity] plugin, the data is serialized
|
|
71
|
+
before writing to and deserialized after reading from the data attribute.
|
|
72
|
+
|
|
73
|
+
You can also use your own serializer via the `:serializer` option. The
|
|
74
|
+
serializer object needs to implement `#dump` and `#load` methods:
|
|
75
|
+
|
|
76
|
+
```rb
|
|
77
|
+
class MyDataSerializer
|
|
78
|
+
def self.dump(data)
|
|
79
|
+
data #=> { "id" => "...", "storage" => "...", "metadata" => { ... } }
|
|
80
|
+
|
|
81
|
+
JSON.generate(data) # serialize data, e.g. into JSON
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.load(data)
|
|
85
|
+
data #=> '{"id":"...", "storage":"...", "metadata": {...}}'
|
|
86
|
+
|
|
87
|
+
JSON.parse(data) # deserialize data, e.g. from JSON
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
plugin :column, serializer: MyDataSerializer
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Some serialization libraries such as [Oj] and [MessagePack] already implement
|
|
95
|
+
this interface, which simplifies the configuration:
|
|
96
|
+
|
|
97
|
+
```rb
|
|
98
|
+
require "oj" # https://github.com/ohler55/oj
|
|
99
|
+
|
|
100
|
+
plugin :column, serializer: Oj
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If you want to disable serialization and work with hashes directly, you can set
|
|
104
|
+
`:serializer` to `nil`:
|
|
105
|
+
|
|
106
|
+
```rb
|
|
107
|
+
plugin :column, serializer: nil # disable serialization
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The serializer can also be changed for a particular attacher instance:
|
|
111
|
+
|
|
112
|
+
```rb
|
|
113
|
+
Shrine::Attacher.new(column_serializer: Oj) # use custom serializer
|
|
114
|
+
Shrine::Attacher.new(column_serializer: nil) # disable serialization
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
[column]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/column.rb
|
|
118
|
+
[model]: https://shrinerb.com/docs/plugins/model
|
|
119
|
+
[entity]: https://shrinerb.com/docs/plugins/entity
|
|
120
|
+
[Oj]: https://github.com/ohler55/oj
|
|
121
|
+
[MessagePack]: https://github.com/msgpack/msgpack-ruby
|