shrine 2.19.4 → 3.0.0.alpha

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

Potentially problematic release.


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

Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -11
  3. data/README.md +9 -3
  4. data/doc/advantages.md +1 -1
  5. data/doc/carrierwave.md +4 -4
  6. data/doc/creating_persistence_plugins.md +172 -0
  7. data/doc/creating_plugins.md +1 -1
  8. data/doc/creating_storages.md +3 -1
  9. data/doc/design.md +2 -2
  10. data/doc/direct_s3.md +0 -22
  11. data/doc/paperclip.md +3 -3
  12. data/doc/plugins/activerecord.md +211 -42
  13. data/doc/plugins/atomic_helpers.md +153 -0
  14. data/doc/plugins/column.md +90 -0
  15. data/doc/plugins/derivation_endpoint.md +54 -62
  16. data/doc/plugins/derivatives.md +752 -0
  17. data/doc/plugins/entity.md +204 -0
  18. data/doc/plugins/infer_extension.md +8 -8
  19. data/doc/plugins/instrumentation.md +33 -13
  20. data/doc/plugins/keep_files.md +5 -15
  21. data/doc/plugins/model.md +157 -0
  22. data/doc/plugins/presign_endpoint.md +2 -1
  23. data/doc/plugins/refresh_metadata.md +44 -7
  24. data/doc/plugins/sequel.md +190 -33
  25. data/doc/plugins/{default_url_options.md → url_options.md} +5 -5
  26. data/doc/processing.md +1 -1
  27. data/doc/release_notes/1.1.0.md +2 -2
  28. data/doc/release_notes/2.15.0.md +1 -1
  29. data/doc/storage/s3.md +2 -2
  30. data/doc/testing.md +1 -1
  31. data/lib/shrine.rb +72 -138
  32. data/lib/shrine/attacher.rb +272 -176
  33. data/lib/shrine/attachment.rb +2 -42
  34. data/lib/shrine/plugins/activerecord.rb +103 -26
  35. data/lib/shrine/plugins/add_metadata.rb +9 -10
  36. data/lib/shrine/plugins/atomic_helpers.rb +111 -0
  37. data/lib/shrine/plugins/attacher_options.rb +55 -0
  38. data/lib/shrine/plugins/backgrounding.rb +147 -115
  39. data/lib/shrine/plugins/cached_attachment_data.rb +6 -9
  40. data/lib/shrine/plugins/column.rb +104 -0
  41. data/lib/shrine/plugins/data_uri.rb +35 -38
  42. data/lib/shrine/plugins/default_storage.rb +18 -12
  43. data/lib/shrine/plugins/default_url.rb +11 -21
  44. data/lib/shrine/plugins/default_url_options.rb +3 -30
  45. data/lib/shrine/plugins/delete_raw.rb +9 -13
  46. data/lib/shrine/plugins/derivation_endpoint.rb +75 -114
  47. data/lib/shrine/plugins/derivatives.rb +576 -0
  48. data/lib/shrine/plugins/determine_mime_type.rb +3 -15
  49. data/lib/shrine/plugins/download_endpoint.rb +83 -131
  50. data/lib/shrine/plugins/dynamic_storage.rb +4 -8
  51. data/lib/shrine/plugins/entity.rb +128 -0
  52. data/lib/shrine/plugins/form_assign.rb +107 -0
  53. data/lib/shrine/plugins/included.rb +4 -3
  54. data/lib/shrine/plugins/infer_extension.rb +10 -17
  55. data/lib/shrine/plugins/instrumentation.rb +45 -25
  56. data/lib/shrine/plugins/keep_files.rb +2 -12
  57. data/lib/shrine/plugins/metadata_attributes.rb +15 -14
  58. data/lib/shrine/plugins/model.rb +137 -0
  59. data/lib/shrine/plugins/module_include.rb +2 -0
  60. data/lib/shrine/plugins/presign_endpoint.rb +1 -15
  61. data/lib/shrine/plugins/pretty_location.rb +5 -5
  62. data/lib/shrine/plugins/processing.rb +21 -6
  63. data/lib/shrine/plugins/rack_file.rb +1 -39
  64. data/lib/shrine/plugins/rack_response.rb +14 -7
  65. data/lib/shrine/plugins/recache.rb +5 -2
  66. data/lib/shrine/plugins/refresh_metadata.rb +12 -8
  67. data/lib/shrine/plugins/remote_url.rb +44 -53
  68. data/lib/shrine/plugins/remove_attachment.rb +7 -2
  69. data/lib/shrine/plugins/remove_invalid.rb +8 -4
  70. data/lib/shrine/plugins/restore_cached_data.rb +12 -4
  71. data/lib/shrine/plugins/sequel.rb +115 -27
  72. data/lib/shrine/plugins/signature.rb +2 -7
  73. data/lib/shrine/plugins/store_dimensions.rb +13 -27
  74. data/lib/shrine/plugins/upload_endpoint.rb +14 -15
  75. data/lib/shrine/plugins/upload_options.rb +9 -8
  76. data/lib/shrine/plugins/url_options.rb +33 -0
  77. data/lib/shrine/plugins/validation.rb +87 -0
  78. data/lib/shrine/plugins/validation_helpers.rb +33 -54
  79. data/lib/shrine/plugins/versions.rb +106 -84
  80. data/lib/shrine/storage/file_system.rb +32 -57
  81. data/lib/shrine/storage/linter.rb +9 -1
  82. data/lib/shrine/storage/memory.rb +42 -0
  83. data/lib/shrine/storage/s3.rb +38 -146
  84. data/lib/shrine/uploaded_file.rb +22 -29
  85. data/lib/shrine/version.rb +4 -4
  86. data/shrine.gemspec +2 -3
  87. metadata +27 -54
  88. data/doc/plugins/backup.md +0 -31
  89. data/doc/plugins/copy.md +0 -24
  90. data/doc/plugins/delete_promoted.md +0 -12
  91. data/doc/plugins/direct_upload.md +0 -172
  92. data/doc/plugins/hooks.md +0 -58
  93. data/doc/plugins/logging.md +0 -42
  94. data/doc/plugins/migration_helpers.md +0 -60
  95. data/doc/plugins/moving.md +0 -19
  96. data/doc/plugins/multi_delete.md +0 -20
  97. data/doc/plugins/parallelize.md +0 -16
  98. data/doc/plugins/parsed_json.md +0 -23
  99. data/lib/shrine/plugins/background_helpers.rb +0 -5
  100. data/lib/shrine/plugins/backup.rb +0 -90
  101. data/lib/shrine/plugins/copy.rb +0 -50
  102. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  103. data/lib/shrine/plugins/direct_upload.rb +0 -217
  104. data/lib/shrine/plugins/hooks.rb +0 -90
  105. data/lib/shrine/plugins/logging.rb +0 -142
  106. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  107. data/lib/shrine/plugins/moving.rb +0 -57
  108. data/lib/shrine/plugins/multi_delete.rb +0 -32
  109. data/lib/shrine/plugins/parallelize.rb +0 -78
  110. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -0,0 +1,204 @@
1
+ # Entity
2
+
3
+ The [`entity`][entity] plugin provides integration for handling attachments on
4
+ immutable structs. It is built on top of the [`column`][column] plugin.
5
+
6
+ ```rb
7
+ plugin :entity
8
+ ```
9
+
10
+ ## Attachment
11
+
12
+ Including a `Shrine::Attachment` module into an entity class will add the
13
+ following instance methods:
14
+
15
+ * `#<name>` – returns the attached file
16
+ * `#<name>_url` – returns the attached file URL
17
+ * `#<name>_attacher` – returns a `Shrine::Attacher` instance
18
+
19
+ These methods read attachment data from the `#<name>_data` attribute on the
20
+ entity instance.
21
+
22
+ ```rb
23
+ class Photo < Entity(:image_data)
24
+ include ImageUploader::Attachment(:image)
25
+ end
26
+ ```
27
+ ```rb
28
+ photo = Photo.new(image_data: '{"id":"...","storage":"...","metadata":{...}}')
29
+ photo.image #=> #<ImageUploader::UploadedFile>
30
+ photo.image_url #=> "https://example.com/image.jpg"
31
+ photo.image_attacher #=> #<ImageUploader::Attacher>
32
+ ```
33
+
34
+ #### `#<name>`
35
+
36
+ Calls `Attacher#get`, which returns an `UploadedFile` object instantiated from
37
+ attachment data.
38
+
39
+ ```rb
40
+ photo = Photo.new(image_data: '{"id":"foo.jpg","storage":"store","metadata":{...}}')
41
+ photo.image #=> #<ImageUploader::UploadedFile>
42
+ photo.image.id #=> "foo.jpg"
43
+ photo.image.storage_key #=> :store
44
+ photo.image.metadata #=> { ... }
45
+ ```
46
+
47
+ If no file is attached, `nil` is returned.
48
+
49
+ ```rb
50
+ photo = Photo.new(image_data: nil)
51
+ photo.image #=> nil
52
+ ```
53
+
54
+ #### `#<name>_url`
55
+
56
+ Calls `Attacher#url`, which returns the URL to the attached file.
57
+
58
+ ```rb
59
+ photo = Photo.new(image_data: {"id":"foo.jpg","storage":"...","metadata":{...}})
60
+ photo.image_url #=> "https://example.com/foo.jpg"
61
+ ```
62
+
63
+ If no file is attached, `nil` is returned.
64
+
65
+ ```rb
66
+ photo = Photo.new(image_data: nil)
67
+ photo.image_url #=> nil
68
+ ```
69
+
70
+ #### `#<name>_attacher`
71
+
72
+ Calls `Attacher.from_entity`, which returns an `Attacher` instance backed by
73
+ the entity object.
74
+
75
+ ```rb
76
+ photo = Photo.new
77
+ photo.image_attacher #=> #<ImageUploader::Attacher>
78
+ photo.image_attacher.record #=> #<Photo>
79
+ photo.image_attacher.name #=> :image
80
+ photo.image_attacher.attribute #=> :image_data
81
+ ```
82
+
83
+ Any additional options will be forwarded to `Attacher#initialize`.
84
+
85
+ ```rb
86
+ photo = Photo.new
87
+ attacher = photo.image_attacher(cache: :other_cache)
88
+ attacher.cache_key #=> :other_cache
89
+ ```
90
+
91
+ You can also specify default attacher options when including
92
+ `Shrine::Attachment`:
93
+
94
+ ```rb
95
+ class Photo < Entity(:image_data)
96
+ include ImageUploader::Attachment(:image, store: :other_store)
97
+ end
98
+ ```
99
+ ```rb
100
+ photo = Photo.new
101
+ attacher = photo.image_attacher
102
+ attacher.store_key #=> :other_store
103
+ ```
104
+
105
+ ## Attacher
106
+
107
+ ### Loading entity
108
+
109
+ The `Attacher.from_entity` method can be used for creating an `Attacher`
110
+ instance backed by an entity object.
111
+
112
+ ```rb
113
+ photo = Photo.new(image_data: '{"id":"...","storage":"...","metadata":{...}}')
114
+ attacher = ImageUploader::Attacher.from_entity(photo, :image)
115
+
116
+ attacher.record #=> #<Photo>
117
+ attacher.name #=> :image
118
+ attacher.attribute #=> :image_data
119
+
120
+ attacher.file #=> #<ImageUploader::UploadedFile>
121
+ ```
122
+
123
+ Any additional options are forwarded to `Attacher#initialize`.
124
+
125
+ ```rb
126
+ attacher = ImageUploader::Attacher.from_entity(photo, :image, cache: :other_cache)
127
+ attacher.cache_key #=> :other_cache
128
+ ```
129
+
130
+ You can also load an entity into an existing attacher with
131
+ `Attacher#load_entity`.
132
+
133
+ ```rb
134
+ photo = Photo.new(image_data: '{"id":"...","storage":"...","metadata":{...}}')
135
+
136
+ attacher.file #=> nil
137
+ attacher.load_entity(photo, :image)
138
+ attacher.file #=> #<ImageUploader::UploadedFile>
139
+ ```
140
+
141
+ ### Reloading
142
+
143
+ The `Attacher#reload` method reloads attached file from the attachment data on
144
+ the entity attribute.
145
+
146
+ ```rb
147
+ photo = Photo.new
148
+
149
+ attacher = ImageUploader::Attacher.from_entity(photo, :image)
150
+ attacher.file #=> nil
151
+
152
+ photo.image_data = '{"id":"...","storage":"...","metadata":{...}}'
153
+
154
+ attacher.file #=> nil
155
+ attacher.reload
156
+ attacher.file #=> #<ImageUploader::UploadedFile>
157
+ ```
158
+
159
+ ### Column values
160
+
161
+ The `Attacher#column_values` method returns a hash with the entity attribute as
162
+ key and current attachment data as value.
163
+
164
+ ```rb
165
+ attacher = ImageUploader::Attacher.from_entity(Photo.new, :image)
166
+ attacher.attach(io)
167
+
168
+ attacher.column_values #=> { :image_data => '{"id":"...","storage":"...","metadata":{...}}' }
169
+ ```
170
+
171
+ The `Attacher#attribute` method returns just the entity attribute from which
172
+ attached file data is read.
173
+
174
+ ```rb
175
+ attacher = ImageUploader::Attacher.from_entity(Photo.new, :image)
176
+ attacher.attribute #=> :image_data
177
+ ```
178
+
179
+ ### Entity data
180
+
181
+ The `Attacher#record` method returns the entity instance from which the
182
+ attacher was loaded.
183
+
184
+ ```rb
185
+ attacher = ImageUploader::Attacher.from_entity(Photo.new, :image)
186
+ attacher.record #=> #<Photo>
187
+ ```
188
+
189
+ The `Attacher#name` method returns the name of the attachment from which the
190
+ attacher was loaded.
191
+
192
+ ```rb
193
+ attacher = ImageUploader::Attacher.from_entity(Photo.new, :image)
194
+ attacher.name #=> :image
195
+ ```
196
+
197
+ ## Serialization
198
+
199
+ By default, attachment data is serialized into JSON using the `JSON` standard
200
+ library. If you want to change how data is serialized, see the
201
+ [`column`][column serializer] plugin docs.
202
+
203
+ [entity]: /lib/shrine/plugins/entity.rb
204
+ [column serializer]: /doc/plugins/column.md#serializer
@@ -11,19 +11,19 @@ plugin :infer_extension
11
11
 
12
12
  ## Inferrers
13
13
 
14
- By default `MIME::Types` will be used for inferring the extension, but you can
15
- also choose a different inferrer:
14
+ By default, the [mini_mime] gem will be used for inferring the extension, but
15
+ you can also choose a different inferrer:
16
16
 
17
17
  ```rb
18
- plugin :infer_extension, inferrer: :mini_mime
18
+ plugin :infer_extension, inferrer: :mime_types
19
19
  ```
20
20
 
21
21
  The following inferrers are accepted:
22
22
 
23
- | Name | Description |
24
- | :------------ | :----------- |
25
- | `:mime_types` | (Default). Uses the [mime-types] gem to infer the appropriate extension from MIME type. |
26
- | `:mini_mime` | Uses the [mini_mime] gem to infer the appropriate extension from MIME type. |
23
+ | Name | Description |
24
+ | :------------ | :----------- |
25
+ | `:mini_mime` | (Default). Uses the [mini_mime] gem to infer the appropriate extension from MIME type. |
26
+ | `:mime_types` | Uses the [mime-types] gem to infer the appropriate extension from MIME type. |
27
27
 
28
28
  You can also define your own inferrer, with the possibility to call the
29
29
  built-in inferrers:
@@ -31,7 +31,7 @@ built-in inferrers:
31
31
  ```rb
32
32
  plugin :infer_extension, inferrer: -> (mime_type, inferrers) do
33
33
  # don't add extension if the file is a text file
34
- inferrers[:rack_mime].call(mime_type) unless mime_type == "text/plain"
34
+ inferrers[:mini_mime].call(mime_type) unless mime_type == "text/plain"
35
35
  end
36
36
  ```
37
37
 
@@ -1,7 +1,7 @@
1
1
  # Instrumentation
2
2
 
3
- The [`instrumentation`][instrumentation] plugin sends events for various
4
- operations to a centralized notification component. In addition to that it
3
+ The [`instrumentation`][instrumentation] plugin publishes events for various
4
+ operations to a centralized notification component. In addition to that, it
5
5
  provides default logging for these events.
6
6
 
7
7
  ```rb
@@ -40,6 +40,13 @@ Download (1002ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1
40
40
  Delete (700ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
41
41
  ```
42
42
 
43
+ It uses `Shrine.logger` for logging, which allows you to change where and how
44
+ are the logs going to be written:
45
+
46
+ ```rb
47
+ Shrine.logger = Rails.logger # in Rails apps
48
+ ```
49
+
43
50
  You can choose to log only certain events, e.g. we can exclude metadata
44
51
  extraction:
45
52
 
@@ -79,6 +86,7 @@ The following events are instrumented by the `instrumentation` plugin:
79
86
 
80
87
  * [`upload.shrine`](#uploadshrine)
81
88
  * [`download.shrine`](#downloadshrine)
89
+ * [`open.shrine`](#openshrine)
82
90
  * [`exists.shrine`](#existsshrine)
83
91
  * [`delete.shrine`](#deleteshrine)
84
92
  * [`metadata.shrine`](#metadatashrine)
@@ -94,21 +102,33 @@ following payload:
94
102
  | `:location` | The location of the uploaded file |
95
103
  | `:io` | The uploaded IO object |
96
104
  | `:upload_options` | Any upload options that were specified |
97
- | `:options` | Some additional context information |
105
+ | `:metadata` | Metadata extracted during upload |
106
+ | `:options` | Any additional uploader options |
98
107
  | `:uploader` | The uploader class that sent the event |
99
108
 
100
109
  ### download.shrine
101
110
 
102
- The `download.shrine` event is logged on `UploadedFile#open` (which includes
103
- `UploadedFile#download` and `UploadedFile#stream` methods as well), and
104
- contains the following payload:
111
+ The `download.shrine` event is logged on `UploadedFile#stream` (which includes
112
+ `UploadedFile#download`), and contains the following payload:
105
113
 
106
- | Key | Description |
107
- | :-- | :---- |
108
- | `:storage` | The storage identifier |
109
- | `:location` | The location of the uploaded file |
110
- | `:download_options` | Any upload options that were specified |
111
- | `:uploader` | The uploader class that sent the event |
114
+ | Key | Description |
115
+ | :-- | :---- |
116
+ | `:storage` | The storage identifier |
117
+ | `:location` | The location of the uploaded file |
118
+ | `:download_options` | Any download options that were specified |
119
+ | `:uploader` | The uploader class that sent the event |
120
+
121
+ ### open.shrine
122
+
123
+ The `download.shrine` event is logged on `UploadedFile#open` or when uploaded
124
+ file is implicitly opened on calling an IO method.
125
+
126
+ | Key | Description |
127
+ | :-- | :---- |
128
+ | `:storage` | The storage identifier |
129
+ | `:location` | The location of the uploaded file |
130
+ | `:download_options` | Any download options that were specified |
131
+ | `:uploader` | The uploader class that sent the event |
112
132
 
113
133
  ### exists.shrine
114
134
 
@@ -141,7 +161,7 @@ following payload:
141
161
  | :-- | :---- |
142
162
  | `:storage` | The storage identifier |
143
163
  | `:io` | The uploaded IO object |
144
- | `:options` | Some additional context information |
164
+ | `:options` | Any options sent to the uploader |
145
165
  | `:uploader` | The uploader class that sent the event |
146
166
 
147
167
  ## API
@@ -1,22 +1,12 @@
1
1
  # Keep Files
2
2
 
3
- The [`keep_files`][keep_files] plugin gives you the ability to prevent files
4
- from being deleted. This functionality is useful when implementing soft
5
- deletes, or when implementing some kind of [event store] where you need to
6
- track history.
7
-
8
- The plugin accepts the following options:
9
-
10
- | Option | Description |
11
- | :------ | :---------- |
12
- | `:destroyed` | If set to `true`, destroying the record won't delete the associated attachment. |
13
- | `:replaced` | If set to `true`, uploading a new attachment won't delete the old one. |
14
-
15
- For example, the following will keep destroyed and replaced files:
3
+ The [`keep_files`][keep_files] plugin prevents file deletion when the attacher
4
+ is about to destroy currently attached or previously attached file. This
5
+ functionality is useful when implementing soft deletes, versioning, or in
6
+ general any scenario where you need to track history.
16
7
 
17
8
  ```rb
18
- plugin :keep_files, destroyed: true, replaced: true
9
+ plugin :keep_files
19
10
  ```
20
11
 
21
12
  [keep_files]: /lib/shrine/plugins/keep_files.rb
22
- [event store]: http://docs.geteventstore.com/introduction/event-sourcing-basics/
@@ -0,0 +1,157 @@
1
+ # Model
2
+
3
+ The [`model`][model] plugin provides integration for handling atatchments on
4
+ mutable structs. It is built on top of the [`entity`][entity] plugin.
5
+
6
+ ```rb
7
+ plugin :model
8
+ ```
9
+
10
+ ## Attachment
11
+
12
+ Including a `Shrine::Attachment` module into a model class will, in addition to
13
+ methods from the `entity` plugin, add the `#<name>=` method for attaching
14
+ files.
15
+
16
+ These methods read and write attachment data to the `#<name>_data` attribute on
17
+ the model instance.
18
+
19
+ ```rb
20
+ class Photo < Model(:image_data)
21
+ include ImageUploader::Attachment(:image)
22
+ end
23
+ ```
24
+ ```rb
25
+ photo = Photo.new
26
+ photo.image = file
27
+ photo.image #=> #<ImageUploader::UploadedFile>
28
+ photo.image_url #=> "https://example.com/foo.jpg"
29
+ photo.image_attacher #=> #<ImageUploader::Attacher>
30
+ ```
31
+
32
+ #### `#<name>=`
33
+
34
+ Calls `Attacher#assign` by default, which uploads the file to temporary storage
35
+ and attaches it, updating the model attribute.
36
+
37
+ ```rb
38
+ photo = Photo.new
39
+ photo.image = file
40
+ photo.image.storage_key #=> :cache
41
+ photo.image_data #=> '{"id":"...","storage":"cache","metadata":{...}}'
42
+ ```
43
+
44
+ #### Disabling caching
45
+
46
+ If you don't want to use temporary storage, you can have `#<name>=` upload
47
+ directly to permanent storage.
48
+
49
+ ```rb
50
+ plugin :model, cache: false
51
+ ```
52
+ ```rb
53
+ photo = Photo.new
54
+ photo.image = file
55
+ photo.image.storage_key #=> :store
56
+ photo.image_data #=> '{"id":"...","storage":"store","metadata":{...}}'
57
+ ```
58
+
59
+ This can be configured on the attacher level as well:
60
+
61
+ ```rb
62
+ photo = Photo.new
63
+ photo.image_attacher(model_cache: false)
64
+ photo.image = file
65
+ photo.image.storage_key #=> :store
66
+ ```
67
+
68
+ #### `#<name>_attacher`
69
+
70
+ Returns an `Attacher` instance backed by the model instance, memoized in an
71
+ instance variable.
72
+
73
+ ```rb
74
+ photo = Photo.new
75
+ photo.image_attacher #=> #<ImageUploader::Attacher> (memoizes the instance)
76
+ photo.image_attacher #=> #<ImageUploader::Attacher> (returns memoized instance)
77
+ ```
78
+
79
+ When attacher options are passed, the attacher instance is refreshed:
80
+
81
+ ```rb
82
+ photo = Photo.new
83
+ photo.image_attacher(cache: :other_cache)
84
+ photo.image_attacher.cache_key #=> :other_cache
85
+ ```
86
+
87
+ ### Entity
88
+
89
+ If you still want to include `Shrine::Attachment` modules to immutable
90
+ entities, you can disable "model" behaviour by passing `type: :entity`:
91
+
92
+ ```rb
93
+ class Photo < Entity(:image_data)
94
+ include ImageUploader::Attachment(:image, type: :entity)
95
+ end
96
+ ```
97
+
98
+ ## Attacher
99
+
100
+ ### Loading model
101
+
102
+ The `Attacher.from_model` method can be used for creating an `Attacher`
103
+ instance backed by a model object.
104
+
105
+ ```rb
106
+ photo = Photo.new
107
+ attacher = ImageUploader::Attacher.from_model(photo, :image)
108
+
109
+ attacher.record #=> #<Photo>
110
+ attacher.name #=> :image
111
+ attacher.attribute #=> :image_data
112
+
113
+ attacher.attach(io)
114
+ photo.image_data #=> '{"id":"...","storage":"...","metadata":{...}}'
115
+ ```
116
+
117
+ You can also load an entity into an existing attacher with
118
+ `Attacher#load_model`.
119
+
120
+ ```rb
121
+ photo = Photo.new(image_data: '{"id":"...","storage":"...","metadata":{...}}')
122
+ attacher = ImageUploader::Attacher.from_model(photo, :image)
123
+
124
+ attacher.file #=> nil
125
+ attacher.load_model(photo, :image)
126
+ attacher.file #=> #<ImageUploader::UploadedFile>
127
+ ```
128
+
129
+ ### Writing attachment data
130
+
131
+ The `Attacher#write` method writes attachment data to the `#<name>_data`
132
+ attribute on the model instance.
133
+
134
+ ```rb
135
+ photo = Photo.new
136
+ attacher = ImageUploader::Attacher.from_model(photo, :image)
137
+
138
+ attacher.file = uploaded_file
139
+ photo.image_data #=> nil
140
+
141
+ attacher.write
142
+ photo.image_data #=> '{"id":"...","storage":"...","metadata":{...}}'
143
+ ```
144
+
145
+ The `Attacher#write` method is automatically called on `Attacher#set`, as well
146
+ as `Attacher#assign`, `Attacher#attach_cached`, `Attacher#attach`,
147
+ `Attacher#promote` and any other attacher method that calls `Attacher#set`.
148
+
149
+ ## Serialization
150
+
151
+ By default, attachment data is serialized into JSON using the `JSON` standard
152
+ library. If you want to change how data is serialized, see the
153
+ [`column`][column serializer] plugin docs.
154
+
155
+ [model]: /lib/shrine/plugins/model.rb
156
+ [entity]: /doc/plugins/entity.md#readme
157
+ [column serializer]: /doc/plugins/column.md#serializer