shrine 2.19.3 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +83 -979
  5. data/doc/advantages.md +231 -204
  6. data/doc/attacher.md +304 -153
  7. data/doc/carrierwave.md +297 -226
  8. data/doc/changing_derivatives.md +308 -0
  9. data/doc/changing_location.md +103 -21
  10. data/doc/changing_storage.md +110 -0
  11. data/doc/creating_persistence_plugins.md +132 -0
  12. data/doc/creating_plugins.md +43 -23
  13. data/doc/creating_storages.md +19 -5
  14. data/doc/design.md +147 -97
  15. data/doc/direct_s3.md +38 -28
  16. data/doc/external/articles.md +63 -0
  17. data/doc/external/extensions.md +53 -0
  18. data/doc/external/misc.md +32 -0
  19. data/doc/getting_started.md +1156 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +93 -30
  22. data/doc/paperclip.md +384 -262
  23. data/doc/plugins/activerecord.md +177 -46
  24. data/doc/plugins/add_metadata.md +139 -38
  25. data/doc/plugins/atomic_helpers.md +217 -0
  26. data/doc/plugins/backgrounding.md +156 -98
  27. data/doc/plugins/cached_attachment_data.md +7 -5
  28. data/doc/plugins/column.md +121 -0
  29. data/doc/plugins/data_uri.md +23 -22
  30. data/doc/plugins/default_storage.md +36 -10
  31. data/doc/plugins/default_url.md +30 -13
  32. data/doc/plugins/delete_raw.md +4 -2
  33. data/doc/plugins/derivation_endpoint.md +186 -101
  34. data/doc/plugins/derivatives.md +839 -0
  35. data/doc/plugins/determine_mime_type.md +4 -2
  36. data/doc/plugins/download_endpoint.md +64 -8
  37. data/doc/plugins/dynamic_storage.md +5 -3
  38. data/doc/plugins/entity.md +263 -0
  39. data/doc/plugins/form_assign.md +55 -0
  40. data/doc/plugins/included.md +31 -8
  41. data/doc/plugins/infer_extension.md +21 -10
  42. data/doc/plugins/instrumentation.md +38 -16
  43. data/doc/plugins/keep_files.md +16 -17
  44. data/doc/plugins/metadata_attributes.md +42 -13
  45. data/doc/plugins/mirroring.md +118 -0
  46. data/doc/plugins/model.md +210 -0
  47. data/doc/plugins/module_include.md +4 -2
  48. data/doc/plugins/multi_cache.md +24 -0
  49. data/doc/plugins/persistence.md +101 -0
  50. data/doc/plugins/presign_endpoint.md +9 -4
  51. data/doc/plugins/pretty_location.md +16 -3
  52. data/doc/plugins/processing.md +4 -2
  53. data/doc/plugins/rack_file.md +8 -2
  54. data/doc/plugins/rack_response.md +6 -2
  55. data/doc/plugins/recache.md +4 -2
  56. data/doc/plugins/refresh_metadata.md +49 -9
  57. data/doc/plugins/remote_url.md +84 -47
  58. data/doc/plugins/remove_attachment.md +27 -6
  59. data/doc/plugins/remove_invalid.md +21 -6
  60. data/doc/plugins/restore_cached_data.md +11 -3
  61. data/doc/plugins/sequel.md +159 -35
  62. data/doc/plugins/signature.md +16 -5
  63. data/doc/plugins/store_dimensions.md +14 -2
  64. data/doc/plugins/tempfile.md +4 -2
  65. data/doc/plugins/type_predicates.md +96 -0
  66. data/doc/plugins/upload_endpoint.md +13 -13
  67. data/doc/plugins/upload_options.md +6 -4
  68. data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
  69. data/doc/plugins/validation.md +97 -0
  70. data/doc/plugins/validation_helpers.md +16 -13
  71. data/doc/plugins/versions.md +15 -19
  72. data/doc/processing.md +438 -221
  73. data/doc/refile.md +188 -170
  74. data/doc/release_notes/1.0.0.md +4 -0
  75. data/doc/release_notes/1.1.0.md +6 -2
  76. data/doc/release_notes/1.2.0.md +4 -0
  77. data/doc/release_notes/1.3.0.md +4 -0
  78. data/doc/release_notes/1.4.0.md +4 -0
  79. data/doc/release_notes/1.4.1.md +4 -0
  80. data/doc/release_notes/1.4.2.md +4 -0
  81. data/doc/release_notes/2.0.0.md +4 -0
  82. data/doc/release_notes/2.0.1.md +4 -0
  83. data/doc/release_notes/2.1.0.md +5 -1
  84. data/doc/release_notes/2.1.1.md +4 -0
  85. data/doc/release_notes/2.10.0.md +4 -0
  86. data/doc/release_notes/2.10.1.md +4 -0
  87. data/doc/release_notes/2.11.0.md +4 -0
  88. data/doc/release_notes/2.12.0.md +4 -0
  89. data/doc/release_notes/2.13.0.md +4 -0
  90. data/doc/release_notes/2.14.0.md +5 -1
  91. data/doc/release_notes/2.15.0.md +11 -7
  92. data/doc/release_notes/2.16.0.md +4 -0
  93. data/doc/release_notes/2.17.0.md +4 -0
  94. data/doc/release_notes/2.18.0.md +4 -0
  95. data/doc/release_notes/2.19.0.md +6 -3
  96. data/doc/release_notes/2.2.0.md +4 -0
  97. data/doc/release_notes/2.3.0.md +4 -0
  98. data/doc/release_notes/2.3.1.md +4 -0
  99. data/doc/release_notes/2.4.0.md +4 -0
  100. data/doc/release_notes/2.4.1.md +4 -0
  101. data/doc/release_notes/2.5.0.md +4 -0
  102. data/doc/release_notes/2.6.0.md +4 -0
  103. data/doc/release_notes/2.6.1.md +4 -0
  104. data/doc/release_notes/2.7.0.md +4 -0
  105. data/doc/release_notes/2.8.0.md +4 -0
  106. data/doc/release_notes/2.9.0.md +4 -0
  107. data/doc/release_notes/3.0.0.md +981 -0
  108. data/doc/release_notes/3.0.1.md +22 -0
  109. data/doc/release_notes/3.1.0.md +73 -0
  110. data/doc/release_notes/3.2.0.md +96 -0
  111. data/doc/release_notes/3.2.1.md +31 -0
  112. data/doc/release_notes/3.2.2.md +14 -0
  113. data/doc/release_notes/3.3.0.md +105 -0
  114. data/doc/release_notes/3.4.0.md +35 -0
  115. data/doc/release_notes/3.5.0.md +63 -0
  116. data/doc/release_notes/3.6.0.md +23 -0
  117. data/doc/retrieving_uploads.md +5 -2
  118. data/doc/securing_uploads.md +60 -37
  119. data/doc/storage/file_system.md +20 -3
  120. data/doc/storage/memory.md +19 -0
  121. data/doc/storage/s3.md +122 -78
  122. data/doc/testing.md +141 -133
  123. data/doc/upgrading_to_3.md +708 -0
  124. data/doc/validation.md +54 -90
  125. data/lib/shrine/attacher.rb +292 -169
  126. data/lib/shrine/attachment.rb +13 -46
  127. data/lib/shrine/plugins/_persistence.rb +93 -0
  128. data/lib/shrine/plugins/activerecord.rb +77 -34
  129. data/lib/shrine/plugins/add_metadata.rb +25 -17
  130. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  131. data/lib/shrine/plugins/backgrounding.rb +77 -113
  132. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  133. data/lib/shrine/plugins/column.rb +102 -0
  134. data/lib/shrine/plugins/data_uri.rb +38 -36
  135. data/lib/shrine/plugins/default_storage.rb +45 -15
  136. data/lib/shrine/plugins/default_url.rb +12 -24
  137. data/lib/shrine/plugins/default_url_options.rb +3 -30
  138. data/lib/shrine/plugins/delete_raw.rb +10 -16
  139. data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
  140. data/lib/shrine/plugins/derivatives.rb +645 -0
  141. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  142. data/lib/shrine/plugins/download_endpoint.rb +118 -133
  143. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  144. data/lib/shrine/plugins/entity.rb +158 -0
  145. data/lib/shrine/plugins/form_assign.rb +108 -0
  146. data/lib/shrine/plugins/included.rb +6 -6
  147. data/lib/shrine/plugins/infer_extension.rb +17 -20
  148. data/lib/shrine/plugins/instrumentation.rb +59 -43
  149. data/lib/shrine/plugins/keep_files.rb +3 -15
  150. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  151. data/lib/shrine/plugins/mirroring.rb +142 -0
  152. data/lib/shrine/plugins/model.rb +160 -0
  153. data/lib/shrine/plugins/module_include.rb +3 -3
  154. data/lib/shrine/plugins/multi_cache.rb +27 -0
  155. data/lib/shrine/plugins/presign_endpoint.rb +27 -28
  156. data/lib/shrine/plugins/pretty_location.rb +15 -9
  157. data/lib/shrine/plugins/processing.rb +22 -9
  158. data/lib/shrine/plugins/rack_file.rb +2 -42
  159. data/lib/shrine/plugins/rack_response.rb +21 -10
  160. data/lib/shrine/plugins/recache.rb +6 -5
  161. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  162. data/lib/shrine/plugins/remote_url.rb +49 -49
  163. data/lib/shrine/plugins/remove_attachment.rb +12 -6
  164. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  165. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  166. data/lib/shrine/plugins/sequel.rb +86 -36
  167. data/lib/shrine/plugins/signature.rb +10 -16
  168. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  169. data/lib/shrine/plugins/tempfile.rb +1 -3
  170. data/lib/shrine/plugins/type_predicates.rb +113 -0
  171. data/lib/shrine/plugins/upload_endpoint.rb +28 -24
  172. data/lib/shrine/plugins/upload_options.rb +14 -15
  173. data/lib/shrine/plugins/url_options.rb +31 -0
  174. data/lib/shrine/plugins/validation.rb +80 -0
  175. data/lib/shrine/plugins/validation_helpers.rb +35 -58
  176. data/lib/shrine/plugins/versions.rb +107 -87
  177. data/lib/shrine/plugins.rb +22 -0
  178. data/lib/shrine/storage/file_system.rb +46 -64
  179. data/lib/shrine/storage/linter.rb +42 -7
  180. data/lib/shrine/storage/memory.rb +49 -0
  181. data/lib/shrine/storage/s3.rb +173 -160
  182. data/lib/shrine/uploaded_file.rb +32 -32
  183. data/lib/shrine/version.rb +3 -3
  184. data/lib/shrine.rb +87 -150
  185. data/shrine.gemspec +11 -12
  186. metadata +92 -82
  187. data/doc/migrating_storage.md +0 -76
  188. data/doc/plugins/backup.md +0 -31
  189. data/doc/plugins/copy.md +0 -24
  190. data/doc/plugins/delete_promoted.md +0 -12
  191. data/doc/plugins/direct_upload.md +0 -172
  192. data/doc/plugins/hooks.md +0 -58
  193. data/doc/plugins/logging.md +0 -42
  194. data/doc/plugins/migration_helpers.md +0 -60
  195. data/doc/plugins/moving.md +0 -19
  196. data/doc/plugins/multi_delete.md +0 -20
  197. data/doc/plugins/parallelize.md +0 -16
  198. data/doc/plugins/parsed_json.md +0 -23
  199. data/doc/regenerating_versions.md +0 -143
  200. data/lib/shrine/plugins/background_helpers.rb +0 -5
  201. data/lib/shrine/plugins/backup.rb +0 -90
  202. data/lib/shrine/plugins/copy.rb +0 -50
  203. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  204. data/lib/shrine/plugins/direct_upload.rb +0 -217
  205. data/lib/shrine/plugins/hooks.rb +0 -90
  206. data/lib/shrine/plugins/logging.rb +0 -142
  207. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  208. data/lib/shrine/plugins/moving.rb +0 -57
  209. data/lib/shrine/plugins/multi_delete.rb +0 -32
  210. data/lib/shrine/plugins/parallelize.rb +0 -78
  211. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -1,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