shrine 2.19.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +485 -43
  3. data/LICENSE.txt +1 -1
  4. data/README.md +81 -977
  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 +102 -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 +1115 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +62 -34
  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 +162 -101
  34. data/doc/plugins/derivatives.md +829 -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 +14 -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 +185 -167
  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 +4 -0
  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/retrieving_uploads.md +4 -1
  116. data/doc/securing_uploads.md +60 -37
  117. data/doc/storage/file_system.md +20 -3
  118. data/doc/storage/memory.md +19 -0
  119. data/doc/storage/s3.md +117 -83
  120. data/doc/testing.md +124 -144
  121. data/doc/upgrading_to_3.md +710 -0
  122. data/doc/validation.md +54 -90
  123. data/lib/shrine/attacher.rb +287 -171
  124. data/lib/shrine/attachment.rb +13 -46
  125. data/lib/shrine/plugins/_persistence.rb +93 -0
  126. data/lib/shrine/plugins/activerecord.rb +77 -34
  127. data/lib/shrine/plugins/add_metadata.rb +25 -17
  128. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  129. data/lib/shrine/plugins/backgrounding.rb +77 -113
  130. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  131. data/lib/shrine/plugins/column.rb +102 -0
  132. data/lib/shrine/plugins/data_uri.rb +38 -36
  133. data/lib/shrine/plugins/default_storage.rb +45 -15
  134. data/lib/shrine/plugins/default_url.rb +12 -24
  135. data/lib/shrine/plugins/default_url_options.rb +3 -30
  136. data/lib/shrine/plugins/delete_raw.rb +10 -16
  137. data/lib/shrine/plugins/derivation_endpoint.rb +89 -134
  138. data/lib/shrine/plugins/derivatives.rb +637 -0
  139. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  140. data/lib/shrine/plugins/download_endpoint.rb +109 -133
  141. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  142. data/lib/shrine/plugins/entity.rb +152 -0
  143. data/lib/shrine/plugins/form_assign.rb +108 -0
  144. data/lib/shrine/plugins/included.rb +6 -6
  145. data/lib/shrine/plugins/infer_extension.rb +13 -20
  146. data/lib/shrine/plugins/instrumentation.rb +54 -42
  147. data/lib/shrine/plugins/keep_files.rb +3 -15
  148. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  149. data/lib/shrine/plugins/mirroring.rb +142 -0
  150. data/lib/shrine/plugins/model.rb +158 -0
  151. data/lib/shrine/plugins/module_include.rb +3 -3
  152. data/lib/shrine/plugins/multi_cache.rb +27 -0
  153. data/lib/shrine/plugins/presign_endpoint.rb +18 -22
  154. data/lib/shrine/plugins/pretty_location.rb +15 -9
  155. data/lib/shrine/plugins/processing.rb +22 -9
  156. data/lib/shrine/plugins/rack_file.rb +2 -42
  157. data/lib/shrine/plugins/rack_response.rb +15 -10
  158. data/lib/shrine/plugins/recache.rb +6 -5
  159. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  160. data/lib/shrine/plugins/remote_url.rb +49 -49
  161. data/lib/shrine/plugins/remove_attachment.rb +10 -6
  162. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  163. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  164. data/lib/shrine/plugins/sequel.rb +86 -36
  165. data/lib/shrine/plugins/signature.rb +10 -16
  166. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  167. data/lib/shrine/plugins/tempfile.rb +1 -3
  168. data/lib/shrine/plugins/type_predicates.rb +113 -0
  169. data/lib/shrine/plugins/upload_endpoint.rb +25 -23
  170. data/lib/shrine/plugins/upload_options.rb +14 -15
  171. data/lib/shrine/plugins/url_options.rb +31 -0
  172. data/lib/shrine/plugins/validation.rb +80 -0
  173. data/lib/shrine/plugins/validation_helpers.rb +34 -57
  174. data/lib/shrine/plugins/versions.rb +107 -87
  175. data/lib/shrine/plugins.rb +22 -0
  176. data/lib/shrine/storage/file_system.rb +46 -64
  177. data/lib/shrine/storage/linter.rb +42 -7
  178. data/lib/shrine/storage/memory.rb +49 -0
  179. data/lib/shrine/storage/s3.rb +154 -158
  180. data/lib/shrine/uploaded_file.rb +28 -30
  181. data/lib/shrine/version.rb +3 -3
  182. data/lib/shrine.rb +86 -149
  183. data/shrine.gemspec +9 -10
  184. metadata +79 -83
  185. data/doc/migrating_storage.md +0 -76
  186. data/doc/plugins/backup.md +0 -31
  187. data/doc/plugins/copy.md +0 -24
  188. data/doc/plugins/delete_promoted.md +0 -12
  189. data/doc/plugins/direct_upload.md +0 -172
  190. data/doc/plugins/hooks.md +0 -58
  191. data/doc/plugins/logging.md +0 -42
  192. data/doc/plugins/migration_helpers.md +0 -60
  193. data/doc/plugins/moving.md +0 -19
  194. data/doc/plugins/multi_delete.md +0 -20
  195. data/doc/plugins/parallelize.md +0 -16
  196. data/doc/plugins/parsed_json.md +0 -23
  197. data/doc/regenerating_versions.md +0 -143
  198. data/lib/shrine/plugins/background_helpers.rb +0 -5
  199. data/lib/shrine/plugins/backup.rb +0 -90
  200. data/lib/shrine/plugins/copy.rb +0 -50
  201. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  202. data/lib/shrine/plugins/direct_upload.rb +0 -217
  203. data/lib/shrine/plugins/hooks.rb +0 -90
  204. data/lib/shrine/plugins/logging.rb +0 -142
  205. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  206. data/lib/shrine/plugins/moving.rb +0 -57
  207. data/lib/shrine/plugins/multi_delete.rb +0 -32
  208. data/lib/shrine/plugins/parallelize.rb +0 -78
  209. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -1,6 +1,13 @@
1
- # Creating a New Plugin
1
+ ---
2
+ id: creating-plugins
3
+ title: Writing a Plugin
4
+ ---
5
+
6
+ Shrine has a lot of plugins built-in, but you can use Shrine's plugin system to
7
+ create your own.
8
+
9
+ ## Definition
2
10
 
3
- Shrine has a lot of plugins built-in, but you can also easily create your own.
4
11
  Simply put, a plugin is a module:
5
12
 
6
13
  ```rb
@@ -11,9 +18,9 @@ end
11
18
  Shrine.plugin MyPlugin
12
19
  ```
13
20
 
14
- If you would like to load plugins with a symbol, like you already load plugins
15
- that ship with Shrine, you need to put the plugin in
16
- `shrine/plugins/my_plugin.rb` in the load path, and register it:
21
+ If you would like to load plugins with a symbol (like you already do with
22
+ plugins that ship with Shrine), you need to put the plugin in
23
+ `shrine/plugins/my_plugin.rb` in your load path and register it:
17
24
 
18
25
  ```rb
19
26
  # shrine/plugins/my_plugin.rb
@@ -31,6 +38,8 @@ end
31
38
  Shrine.plugin :my_plugin
32
39
  ```
33
40
 
41
+ ## Methods
42
+
34
43
  The way to make plugins actually extend Shrine's core classes is by defining
35
44
  special modules inside the plugin. Here's a list of all "special" modules:
36
45
 
@@ -51,7 +60,7 @@ uploading:
51
60
  ```rb
52
61
  module MyPlugin
53
62
  module InstanceMethods
54
- def upload(io, context)
63
+ def upload(io, **options)
55
64
  time = Time.now
56
65
  result = super
57
66
  duration = Time.now - time
@@ -61,40 +70,51 @@ module MyPlugin
61
70
  end
62
71
  ```
63
72
 
64
- Notice that we can call `super` to get the original behaviour. In addition to
65
- these modules, you can also make your plugin configurable:
73
+ Notice that we can call `super` to get the original behaviour.
66
74
 
67
- ```rb
68
- Shrine.plugin :my_plugin, foo: "bar"
69
- ```
75
+ ## Configuration
70
76
 
71
- You can do this by adding a `.configure` method to your plugin, which will be
72
- given any passed in arguments or blocks. Typically you'll want to save these
73
- options into Shrine's `opts`, so that you can access them inside of Shrine's
74
- methods.
77
+ You'll likely want to make your plugin configurable. You can do that by
78
+ overriding the `.configure` class method and storing received options into
79
+ `Shrine.opts`:
75
80
 
76
81
  ```rb
77
82
  module MyPlugin
78
- def self.configure(uploader, options = {})
79
- uploader # The uploader class which called `.plugin`
80
- uploader.opts[:my_plugin_options] = options
83
+ def self.configure(uploader, **opts)
84
+ uploader.opts[:my_plugin] ||= {}
85
+ uploader.opts[:my_plugin].merge!(opts)
81
86
  end
82
87
 
83
88
  module InstanceMethods
84
- def foo
85
- opts[:my_plugin_options] #=> {foo: "bar"}
89
+ def upload(io, **options)
90
+ opts[:my_plugin] #=> { ... }
91
+ # ...
86
92
  end
87
93
  end
88
94
  end
89
95
  ```
90
96
 
97
+ Users can now pass these configuration options when loading your plugin:
98
+
99
+ ```rb
100
+ Shrine.plugin :my_plugin, foo: "bar"
101
+ ```
102
+
103
+ ## Dependencies
104
+
91
105
  If your plugin depends on other plugins, you can load them inside of
92
- `.load_dependencies` (which is given the same arguments as `.configure`):
106
+ `.load_dependencies`:
93
107
 
94
108
  ```rb
95
109
  module MyPlugin
96
- def self.load_dependencies(uploader, *)
97
- uploader.plugin :versions # depends on the versions plugin
110
+ def self.load_dependencies(uploader, **opts)
111
+ uploader.plugin :derivatives # depends on the derivatives plugin
98
112
  end
99
113
  end
100
114
  ```
115
+
116
+ The dependencies will get loaded before your plugin, allowing you to override
117
+ methods of your dependencies in your method modules.
118
+
119
+ The same configuration options passed to `.configure` are passed to
120
+ `.load_dependencies` as well.
@@ -1,4 +1,7 @@
1
- # Creating a New Storage
1
+ ---
2
+ id: creating-storages
3
+ title: Writing a Storage
4
+ ---
2
5
 
3
6
  Shrine ships with the FileSystem and S3 storages, but it's also easy to create
4
7
  your own. A storage is a class which needs to implement `#upload`, `#url`,
@@ -122,6 +125,8 @@ The storage can support additional options to customize how the file will be
122
125
  opened, `Shrine::UploadedFile#open` and `Shrine::UploadedFile#download` will
123
126
  forward any given options to `#open`.
124
127
 
128
+ When file is not found, `Shrine::FileNotFound` exception should be raised.
129
+
125
130
  ## Url
126
131
 
127
132
  The `#url` storage method is called by `Shrine::UploadedFile#url`, it accepts a
@@ -204,15 +209,24 @@ The storage can support additional options to customize how the presign will be
204
209
  generated, those can be forwarded via the `:presign_options` option on the
205
210
  `presign_endpoint` plugin.
206
211
 
207
- ## Clear
212
+ ## Delete Prefixed and Clear
213
+
214
+ There are two methods that are not currently used by shrine, but which it's good
215
+ for storages to provide to allow client code to delete files from storage. If
216
+ storages provide these conventional methods, then clients can delete files using
217
+ consistent API for any storage.
208
218
 
209
- While this method is not used by Shrine, it is good to give users the
210
- possibility to delete all files in a storage, and the conventional name for
211
- this method is `#clear!`.
219
+ `#clear!` deletes all files from storage, and `#delete_prefixed` will delete all
220
+ files in a given directory/prefix/path. While not strictly required for shrine storage
221
+ service functionality, storages should usually implement if possible.
212
222
 
213
223
  ```rb
214
224
  class MyStorage
215
225
  # ...
226
+ def delete_prefixed(prefix_path)
227
+ # deletes all files under the supplied argument prefix
228
+ end
229
+
216
230
  def clear!
217
231
  # deletes all files in the storage
218
232
  end
data/doc/design.md CHANGED
@@ -1,21 +1,26 @@
1
- # The Design of Shrine
1
+ ---
2
+ title: The Design of Shrine
3
+ ---
2
4
 
3
- *If you want an in-depth walkthrough through the Shrine codebase, see [Notes on study of shrine implementation] article by Jonathan Rochkind.*
5
+ *If you want an in-depth walkthrough through the Shrine codebase, see [Notes on
6
+ study of shrine implementation] article by Jonathan Rochkind.*
4
7
 
5
- There are five main types of objects that you deal with in Shrine:
8
+ There are five main types of classes that you deal with in Shrine:
6
9
 
7
- * Storage
8
- * `Shrine`
9
- * `Shrine::UploadedFile`
10
- * `Shrine::Attacher`
11
- * `Shrine::Attachment`
10
+ | Class | Description |
11
+ | :---- | :---------- |
12
+ | [`Shrine::Storage::*`](#Storage) | Manages files on a particular storage service |
13
+ | [`Shrine`](#Shrine) | Wraps uploads and handles loading plugins |
14
+ | [`Shrine::UploadedFile`](#shrineuploadedfile) | Represents a file uploaded to a storage |
15
+ | [`Shrine::Attacher`](#shrineattacher) | Handles file attachment logic |
16
+ | [`Shrine::Attachment`](#shrineattachment) | Provides convenience model attachment interface |
12
17
 
13
18
  ## Storage
14
19
 
15
20
  On the lowest level we have a storage. A storage class encapsulates file
16
21
  management logic on a particular service. It is what actually performs uploads,
17
22
  generation of URLs, deletions and similar. By convention it is namespaced under
18
- `Shrine::Storage`.
23
+ `Shrine::Storage::*`.
19
24
 
20
25
  ```rb
21
26
  filesystem = Shrine::Storage::FileSystem.new("uploads")
@@ -24,7 +29,7 @@ filesystem.url("foo") #=> "uploads/foo"
24
29
  filesystem.delete("foo")
25
30
  ```
26
31
 
27
- A storage is a PORO which responds to certain methods:
32
+ A storage is a PORO which implements the following interface:
28
33
 
29
34
  ```rb
30
35
  class Shrine
@@ -34,7 +39,7 @@ class Shrine
34
39
  # uploads `io` to the location `id`
35
40
  end
36
41
 
37
- def open(id)
42
+ def open(id, **options)
38
43
  # returns the remote file as an IO-like object
39
44
  end
40
45
 
@@ -46,7 +51,7 @@ class Shrine
46
51
  # deletes the file from the storage
47
52
  end
48
53
 
49
- def url(id, options = {})
54
+ def url(id, **options)
50
55
  # URL to the remote file, accepts options for customizing the URL
51
56
  end
52
57
  end
@@ -54,24 +59,24 @@ class Shrine
54
59
  end
55
60
  ```
56
61
 
57
- Storages are typically not used directly, but through `Shrine`.
62
+ Storages are typically not used directly, but through [`Shrine`](#shrine) and
63
+ [`Shrine::UploadedFile`](#shrine-uploadedfile) classes.
58
64
 
59
65
  ## `Shrine`
60
66
 
61
- A `Shrine` object (also called an "uploader") is essentially a wrapper around
62
- the `#upload` storage method. First the storage needs to be registered under a
63
- name:
67
+ The `Shrine` class (also called an "uploader") primarily provides a wrapper
68
+ method around `Storage#upload`. First, the storage needs to be registered under
69
+ a name:
64
70
 
65
71
  ```rb
66
- Shrine.storages[:file_system] = Shrine::Storage::FileSystem.new("uploads")
72
+ Shrine.storages[:disk] = Shrine::Storage::FileSystem.new("uploads")
67
73
  ```
68
74
 
69
- Now we can instantiate an uploader with this identifier and upload files:
75
+ Now we can upload files to the registered storage:
70
76
 
71
77
  ```rb
72
- uploader = Shrine.new(:file_system)
73
- uploaded_file = uploader.upload(file)
74
- uploaded_file #=> #<Shrine::UploadedFile>
78
+ uploaded_file = Shrine.upload(file, :disk)
79
+ uploaded_file #=> #<Shrine::UploadedFile storage=:disk id="6a9fb596cc554efb" ...>
75
80
  ```
76
81
 
77
82
  The argument to `Shrine#upload` must be an IO-like object. The method does the
@@ -83,63 +88,97 @@ following:
83
88
  * closes the file
84
89
  * creates a `Shrine::UploadedFile` from the data
85
90
 
86
- `Shrine` class and subclasses are also used for loading plugins that extend all
87
- core classes. Each `Shrine` subclass has its own subclass of each of the core
88
- classes (`Shrine::UploadedFile`, `Shrine::Attacher`, and `Shrine::Attachment`),
89
- which makes it possible to have different `Shrine` subclasses with differently
90
- customized attachment logic. See [Creating a New Plugin] guide and the [Plugin
91
- system of Sequel and Roda] article for more details on the design of Shrine's
92
- plugin system.
91
+ ### Plugins
92
+
93
+ The `Shrine` class is also used for loading plugins, which provide additional
94
+ functionality by extending core classes.
95
+
96
+ ```rb
97
+ Shrine.plugin :derivatives
98
+
99
+ Shrine::UploadedFile.ancestors #=> [..., Shrine::Plugins::Derivatives::FileMethods, Shrine::UploadedFile::InstanceMethods, ...]
100
+ Shrine::Attacher.ancestors #=> [..., Shrine::Plugins::Derivatives::AttacherMethods, Shrine::Attacher::InstanceMethods, ...]
101
+ Shrine::Attachment.ancestors #=> [..., Shrine::Plugins::Derivatives::AttachmentMethods, Shrine::Attachment::InstanceMethods, ...]
102
+ ```
103
+
104
+ The plugins store their configuration in `Shrine.opts`:
105
+
106
+ ```rb
107
+ Shrine.plugin :derivation_endpoint, secret_key: "foo"
108
+ Shrine.plugin :default_storage, store: :other_store
109
+ Shrine.plugin :activerecord
110
+
111
+ Shrine.opts #=>
112
+ # { derivation_endpoint: { options: { secret_key: "foo" }, derivations: {} },
113
+ # default_storage: { store: :other_store },
114
+ # column: { serializer: Shrine::Plugins::Column::JsonSerializer },
115
+ # model: { cache: true },
116
+ # activerecord: { callbacks: true, validations: true } }
117
+ ```
118
+
119
+ Each `Shrine` subclass has its own copy of the core classes, storages and
120
+ options, which makes it possible to customize attachment logic per uploader.
121
+
122
+ ```rb
123
+ MyUploader = Class.new(Shrine)
124
+ MyUploader::UploadedFile.superclass #=> Shrine::UploadedFile
125
+ MyUploader::Attacher.superclass #=> Shrine::Attacher
126
+ MyUploader::Attachment.superclass #=> Shrine::Attachment
127
+ ```
128
+
129
+ See [Creating a New Plugin] guide and the [Plugin system of Sequel and Roda]
130
+ article for more details on the design of Shrine's plugin system.
93
131
 
94
132
  ## `Shrine::UploadedFile`
95
133
 
96
- `Shrine::UploadedFile` represents a file that was uploaded to a storage, and is
97
- the result of `Shrine#upload`. It is essentially a wrapper around a data hash
98
- containing information about the uploaded file.
134
+ A `Shrine::UploadedFile` object represents a file that was uploaded to a
135
+ storage, containing upload location, storage, and any metadata extracted during
136
+ the upload.
99
137
 
100
138
  ```rb
101
- uploaded_file #=> #<Shrine::UploadedFile>
102
- uploaded_file.data #=>
139
+ uploaded_file #=> #<Shrine::UploadedFile id="949sdjg834.jpg" storage=:store metadata={...}>
140
+
141
+ uploaded_file.id #=> "949sdjg834.jpg"
142
+ uploaded_file.storage_key #=> :store
143
+ uploaded_file.storage #=> #<Shrine::Storage::S3>
144
+ uploaded_file.metadata #=> {...}
145
+ ```
146
+
147
+ It has convenience methods for accessing metadata:
148
+
149
+ ```rb
150
+ uploaded_file.metadata #=>
103
151
  # {
104
- # "storage" => "file_system",
105
- # "id" => "9260ea09d8effd.pdf",
106
- # "metadata" => {
107
- # "filename" => "resume.pdf",
108
- # "mime_type" => "application/pdf",
109
- # "size" => 983294,
110
- # },
152
+ # "filename" => "matrix.mp4",
153
+ # "mime_type" => "video/mp4",
154
+ # "size" => 345993,
111
155
  # }
156
+
157
+ uploaded_file.original_filename #=> "matrix.mp4"
158
+ uploaded_file.extension #=> "mp4"
159
+ uploaded_file.mime_type #=> "video/mp4"
160
+ uploaded_file.size #=> 345993
112
161
  ```
113
162
 
114
- The data hash contains the storage the file was uploaded to, the location, and
115
- some metadata: original filename, MIME type and filesize. The
116
- `Shrine::UploadedFile` object has handy methods which use this data:
163
+ It also has methods that delegate to the storage:
117
164
 
118
165
  ```rb
119
- # metadata methods
120
- uploaded_file.original_filename
121
- uploaded_file.mime_type
122
- uploaded_file.size
123
- # ...
124
-
125
- # storage methods
126
- uploaded_file.url
127
- uploaded_file.exists?
128
- uploaded_file.open
129
- uploaded_file.download
130
- uploaded_file.delete
131
- # ...
166
+ uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
167
+ uploaded_file.open { |io| ... } # opens the uploaded file stream
168
+ uploaded_file.download { |file| ... } # downloads the uploaded file to disk
169
+ uploaded_file.stream(destination) # streams uploaded content into a writable destination
170
+ uploaded_file.exists? #=> true
171
+ uploaded_file.delete # deletes the uploaded file from the storage
132
172
  ```
133
173
 
134
- A `Shrine::UploadedFile` is itself an IO-like object (representing the
135
- remote file), so it can be passed to `Shrine#upload` as well.
174
+ A `Shrine::UploadedFile` is itself an IO-like object (built on top of
175
+ `Storage#open`), so it can be passed to `Shrine#upload` as well.
136
176
 
137
177
  ## `Shrine::Attacher`
138
178
 
139
179
  We usually want to treat uploaded files as *attachments* to records, saving
140
- their data into a database column. This is the responsibility of
141
- `Shrine::Attacher`. A `Shrine::Attacher` uses `Shrine` uploaders and
142
- `Shrine::UploadedFile` objects internally.
180
+ their data into a database column. This is done by `Shrine::Attacher`, which
181
+ internally uses `Shrine` and `Shrine::UploadedFile` classes.
143
182
 
144
183
  The attaching process requires a temporary and a permanent storage to be
145
184
  registered (by default that's `:cache` and `:store`):
@@ -151,71 +190,82 @@ Shrine.storages = {
151
190
  }
152
191
  ```
153
192
 
154
- A `Shrine::Attacher` is instantiated with a model instance and an attachment
155
- name (an "image" attachment will be saved to `image_data` field):
193
+ A `Shrine::Attacher` can be initialized standalone and handle the common
194
+ attachment flow, which includes dirty tracking (promoting cached file to
195
+ permanent storage, deleting previously attached file), validation, processing,
196
+ serialization etc.
156
197
 
157
198
  ```rb
158
- attacher = Shrine::Attacher.new(photo, :image)
199
+ attacher = Shrine::Attacher.new
159
200
 
160
- attacher.assign(file)
161
- attacher.get #=> #<Shrine::UploadedFile>
162
- attacher.record.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
201
+ # ... user uploads a file ...
202
+
203
+ attacher.assign(io) # uploads to temporary storage
204
+ attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
205
+
206
+ # ... handle file validations ...
163
207
 
164
- attacher._promote
165
- attacher.get #=> #<Shrine::UploadedFile>
166
- attacher.record.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
208
+ attacher.finalize # uploads to permanent storage
209
+ attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
167
210
  ```
168
211
 
169
- Above a file is assigned by the attacher, which "caches" (uploads) the file to
170
- the temporary storage. The cached file is then "promoted" (uploaded) to
171
- permanent storage. Behind the scenes a cached `Shrine::UploadedFile` is given
172
- to `Shrine#upload`, which works because `Shrine::UploadedFile` is an IO-like
173
- object. After both caching and promoting the data hash of the uploaded file is
174
- assigned to the record's column as JSON.
212
+ It can also be initialized with a model instance to handle serialization into a
213
+ model attribute:
175
214
 
176
- For more details see [Using Attacher].
215
+ ```rb
216
+ attacher = Shrine::Attacher.from_model(photo, :image)
217
+
218
+ attacher.assign(file)
219
+ photo.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
220
+
221
+ attacher.finalize
222
+ photo.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
223
+ ```
224
+
225
+ For more details, see the [Using Attacher] guide and
226
+ [`entity`][entity]/[`model`][model] plugins.
177
227
 
178
228
  ## `Shrine::Attachment`
179
229
 
180
- `Shrine::Attachment` is the highest level of abstraction. A
181
- `Shrine::Attachment` module exposes the `Shrine::Attacher` object through the
182
- model instance. The `Shrine::Attachment` class is a sublcass of `Module`, which
183
- means that an instance of `Shrine::Attachment` is a module:
230
+ A `Shrine::Attachment` module provides a convenience model interface around the
231
+ `Shrine::Attacher` object. The `Shrine::Attachment` class is a subclass of
232
+ `Module`, which means that an instance of `Shrine::Attachment` is a module:
184
233
 
185
234
  ```rb
186
235
  Shrine::Attachment.new(:image).is_a?(Module) #=> true
187
- Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher]
236
+ Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher, ...]
188
237
 
189
238
  # equivalents
190
239
  Shrine::Attachment.new(:image)
240
+ Shrine::Attachment[:image]
191
241
  Shrine::Attachment(:image)
192
- Shrine[:image]
193
242
  ```
194
243
 
195
- We can include this module to a model:
244
+ We can include this module into a model:
196
245
 
197
246
  ```rb
198
- class Photo
199
- include Shrine::Attachment.new(:image)
200
- end
247
+ Photo.include Shrine::Attachment(:image)
201
248
  ```
202
249
  ```rb
203
- photo.image = file # shorthand for `photo.image_attacher.assign(file)`
204
- photo.image # shorthand for `photo.image_attacher.get`
205
- photo.image_url # shorthand for `photo.image_attacher.url`
250
+ photo.image = file # shorthand for `photo.image_attacher.assign(file)`
251
+ photo.image # shorthand for `photo.image_attacher.get`
252
+ photo.image_url # shorthand for `photo.image_attacher.url`
206
253
 
207
- photo.image_attacher #=> #<Shrine::Attacher>
254
+ photo.image_attacher #=> #<Shrine::Attacher @cache_key=:cache @store_key=:store ...>
208
255
  ```
209
256
 
210
- When an ORM plugin is loaded, the `Shrine::Attachment` module also
211
- automatically:
257
+ When a persistence plugin is loaded ([`activerecord`][activerecord],
258
+ [`sequel`][sequel]), the `Shrine::Attachment` module also automatically:
212
259
 
213
260
  * syncs Shrine's validation errors with the record
214
261
  * triggers promoting after record is saved
215
- * deletes the uploaded file if attachment was replaced/removed or the record
216
- destroyed
262
+ * deletes the uploaded file if attachment was replaced or the record destroyed
217
263
 
218
- [Using Attacher]: /doc/attacher.md#readme
264
+ [Using Attacher]: https://shrinerb.com/docs/attacher
219
265
  [Notes on study of shrine implementation]: https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/
220
- [Creating a New Plugin]: /doc/creating_plugins.md#readme
266
+ [Creating a New Plugin]: https://shrinerb.com/docs/creating-plugins
221
267
  [Plugin system of Sequel and Roda]: https://twin.github.io/the-plugin-system-of-sequel-and-roda/
268
+ [entity]: https://shrinerb.com/docs/plugins/entity
269
+ [model]: https://shrinerb.com/docs/plugins/model
270
+ [activerecord]: https://shrinerb.com/docs/plugins/activerecord
271
+ [sequel]: https://shrinerb.com/docs/plugins/sequel
data/doc/direct_s3.md CHANGED
@@ -1,4 +1,7 @@
1
- # Direct Uploads to S3
1
+ ---
2
+ id: direct-s3
3
+ title: Direct Uploads to S3
4
+ ---
2
5
 
3
6
  Shrine gives you the ability to upload files directly to Amazon S3 (or any
4
7
  other storage service that accepts direct uploads). Uploading directly to a
@@ -23,12 +26,14 @@ storage service is beneficial for several reasons:
23
26
  request-response lifecycle might not be able to finish before the request
24
27
  times out.
25
28
 
29
+ ## Storage
30
+
26
31
  To start, let's set both temporary and permanent storage to S3, with the
27
32
  temporary storage uploading to the `cache/` prefix:
28
33
 
29
34
  ```rb
30
35
  # Gemfile
31
- gem "shrine", "~> 2.11"
36
+ gem "shrine", "~> 3.0"
32
37
  gem "aws-sdk-s3", "~> 1.14"
33
38
  ```
34
39
  ```rb
@@ -71,6 +76,7 @@ If you're using [Uppy], this is the recommended CORS configuration for the
71
76
  <AllowedHeader>x-amz-content-sha256</AllowedHeader>
72
77
  <AllowedHeader>content-type</AllowedHeader>
73
78
  <AllowedHeader>content-disposition</AllowedHeader>
79
+ <ExposeHeader>ETag</ExposeHeader>
74
80
  </CORSRule>
75
81
  <CORSRule>
76
82
  <AllowedOrigin>*</AllowedOrigin>
@@ -80,6 +86,31 @@ If you're using [Uppy], this is the recommended CORS configuration for the
80
86
  </CORSConfiguration>
81
87
  ```
82
88
 
89
+ Or in JSON format:
90
+
91
+ ```json
92
+ [
93
+ {
94
+ "AllowedOrigins": [ "https://my-app.com" ],
95
+ "AllowedMethods": [ "GET", "POST", "PUT" ],
96
+ "MaxAgeSeconds": 3000,
97
+ "AllowedHeaders": [
98
+ "Authorization",
99
+ "x-amz-date",
100
+ "x-amz-content-sha256",
101
+ "Content-Type",
102
+ "Content-Disposition"
103
+ ],
104
+ "ExposeHeaders": [ "ETag" ]
105
+ },
106
+ {
107
+ "AllowedOrigins": [ "*" ],
108
+ "AllowedMethods": [ "GET" ],
109
+ "MaxAgeSeconds": 3000
110
+ }
111
+ ]
112
+ ```
113
+
83
114
  Replace `https://my-app.com` with the URL to your app (in development you can
84
115
  set this to `*`). Once you've hit "Save", it may take some time for the
85
116
  new CORS settings to be applied.
@@ -247,28 +278,6 @@ additional HTTP requests.
247
278
  See [this section][metadata direct uploads] or the rationale and instructions
248
279
  on how to opt in.
249
280
 
250
- ## Object data
251
-
252
- When the cached S3 object is copied to permanent storage, the destination S3
253
- object will by default inherit any object data that was assigned to the cached
254
- object via presign parameters. However, S3 will by default also ignore any new
255
- object parameters that are given to the copy request.
256
-
257
- Whether object data will be copied or replaced depends on the value of the
258
- `:metadata_directive` parameter:
259
-
260
- * `"COPY"` - destination object will inherit source object data and any new data will be ignored (default)
261
- * `"REPLACE"` - destination object will not inherit any of the source object data and will accept new data
262
-
263
- You can use the `upload_options` plugin to change the `:metadata_directive`
264
- option when S3 objects are copied:
265
-
266
- ```rb
267
- plugin :upload_options, store: -> (io, context) do
268
- { metadata_directive: "REPLACE" } if io.is_a?(Shrine::UploadedFile)
269
- end
270
- ```
271
-
272
281
  ## Clearing cache
273
282
 
274
283
  Directly uploaded files won't automatically be deleted from your temporary
@@ -330,8 +339,9 @@ backgrounding library to perform the job with a delay:
330
339
  ```rb
331
340
  Shrine.plugin :backgrounding
332
341
 
333
- Shrine::Attacher.promote do |data|
334
- PromoteJob.perform_in(3, data) # tells a Sidekiq worker to perform in 3 seconds
342
+ Shrine::Attacher.promote_block do
343
+ # tells a Sidekiq worker to perform in 3 seconds
344
+ PromoteJob.perform_in(3, self.class.name, record.class.name, record.id, name, file_data)
335
345
  end
336
346
  ```
337
347
 
@@ -396,6 +406,6 @@ setup] guide.
396
406
  [lifecycle Console]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
397
407
  [lifecycle API]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_lifecycle_configuration-instance_method
398
408
  [Minio]: https://minio.io
399
- [minio setup]: /doc/testing.md#minio
400
- [metadata direct uploads]: /doc/metadata.md#direct-uploads
409
+ [minio setup]: https://shrinerb.com/docs/testing#minio
410
+ [metadata direct uploads]: https://shrinerb.com/docs/metadata#direct-uploads
401
411
  [content_disposition]: https://github.com/shrinerb/content_disposition