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
@@ -2,18 +2,18 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/included.md] on GitHub.
6
- #
7
- # [doc/plugins/included.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/included.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/included
8
6
  module Included
9
7
  def self.configure(uploader, &block)
10
- uploader.opts[:included_block] = block
8
+ uploader.opts[:included] ||= {}
9
+ uploader.opts[:included][:block] = block
11
10
  end
12
11
 
13
12
  module AttachmentMethods
14
- def included(model)
13
+ def included(klass)
15
14
  super
16
- model.instance_exec(@name, &shrine_class.opts[:included_block])
15
+
16
+ klass.instance_exec(@name, &shrine_class.opts[:included][:block])
17
17
  end
18
18
  end
19
19
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  class Shrine
4
6
  module Plugins
5
- # Documentation lives in [doc/plugins/infer_extension.md] on GitHub.
6
- #
7
- # [doc/plugins/infer_extension.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/infer_extension.md
7
+ # Documentation can be found on https://shrinerb.com/docs/plugins/infer_extension
8
8
  module InferExtension
9
9
  LOG_SUBSCRIBER = -> (event) do
10
10
  Shrine.logger.info "Extension (#{event.duration}ms) – #{{
@@ -13,14 +13,12 @@ class Shrine
13
13
  }.inspect}"
14
14
  end
15
15
 
16
- def self.configure(uploader, opts = {})
17
- uploader.opts[:infer_extension] ||= { inferrer: :mime_types, force: false, log_subscriber: LOG_SUBSCRIBER }
16
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
17
+ uploader.opts[:infer_extension] ||= { inferrer: :mini_mime }
18
18
  uploader.opts[:infer_extension].merge!(opts)
19
19
 
20
20
  # instrumentation plugin integration
21
- if uploader.respond_to?(:subscribe)
22
- uploader.subscribe(:extension, &uploader.opts[:infer_extension][:log_subscriber])
23
- end
21
+ uploader.subscribe(:extension, &log_subscriber) if uploader.respond_to?(:subscribe)
24
22
  end
25
23
 
26
24
  module ClassMethods
@@ -53,22 +51,17 @@ class Shrine
53
51
  end
54
52
 
55
53
  module InstanceMethods
54
+ private
55
+
56
56
  def basic_location(io, metadata:)
57
- location = super
58
- current_extension = File.extname(location)
57
+ location = Pathname(super)
59
58
 
60
- if current_extension.empty? || opts[:infer_extension][:force]
61
- inferred_extension = infer_extension(metadata["mime_type"])
62
- location = location.chomp(current_extension) << inferred_extension unless inferred_extension.empty?
59
+ if location.extname.empty? || opts[:infer_extension][:force]
60
+ inferred_extension = self.class.infer_extension(metadata["mime_type"])
61
+ location = location.sub_ext(inferred_extension) if inferred_extension
63
62
  end
64
63
 
65
- location
66
- end
67
-
68
- private
69
-
70
- def infer_extension(mime_type)
71
- self.class.infer_extension(mime_type).to_s
64
+ location.to_s
72
65
  end
73
66
  end
74
67
 
@@ -2,25 +2,20 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/instrumentation.md] on GitHub.
6
- #
7
- # [doc/plugins/instrumentation.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/instrumentation.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/instrumentation
8
6
  module Instrumentation
9
- EVENTS = %i[upload download exists delete metadata].freeze
7
+ EVENTS = %i[upload download open exists delete metadata].freeze
10
8
 
11
9
  # We use a proc in order to be able identify listeners.
12
10
  LOG_SUBSCRIBER = -> (event) { LogSubscriber.call(event) }
13
11
 
14
- def self.configure(uploader, opts = {})
15
- uploader.opts[:instrumentation] ||= { log_subscriber: LOG_SUBSCRIBER, log_events: EVENTS }
12
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
13
+ uploader.opts[:instrumentation] ||= { log_events: EVENTS, subscribers: {} }
16
14
  uploader.opts[:instrumentation].merge!(opts)
17
15
  uploader.opts[:instrumentation][:notifications] ||= ::ActiveSupport::Notifications
18
16
 
19
- # we assign it to the top-level so that it's duplicated on subclassing
20
- uploader.opts[:instrumentation_subscribers] ||= Hash.new { |h, k| h[k] = [] }
21
-
22
17
  uploader.opts[:instrumentation][:log_events].each do |event_name|
23
- uploader.subscribe(event_name, &uploader.opts[:instrumentation][:log_subscriber])
18
+ uploader.subscribe(event_name, &log_subscriber)
24
19
  end
25
20
  end
26
21
 
@@ -48,12 +43,13 @@ class Shrine
48
43
  # end
49
44
  def subscribe(event_name, &subscriber)
50
45
  return if subscriber.nil?
51
- return if subscribers[event_name].include?(subscriber)
46
+ return if subscribers[event_name]&.include?(subscriber)
52
47
 
53
48
  notifications.subscribe("#{event_name}.shrine") do |event|
54
49
  subscriber.call(event) if event[:uploader] <= self
55
50
  end
56
51
 
52
+ subscribers[event_name] ||= []
57
53
  subscribers[event_name] << subscriber
58
54
  end
59
55
 
@@ -63,12 +59,8 @@ class Shrine
63
59
  Notifications.new(opts[:instrumentation][:notifications])
64
60
  end
65
61
 
66
- def log_subscriber
67
- opts[:instrumentation][:log_subscriber]
68
- end
69
-
70
62
  def subscribers
71
- opts[:instrumentation_subscribers]
63
+ opts[:instrumentation][:subscribers]
72
64
  end
73
65
  end
74
66
 
@@ -76,57 +68,68 @@ class Shrine
76
68
  private
77
69
 
78
70
  # Sends a `upload.shrine` event.
79
- def copy(io, context)
80
- self.class.instrument(
81
- :upload,
71
+ def _upload(io, location:, metadata:, upload_options: {}, **options)
72
+ self.class.instrument(:upload, {
82
73
  storage: storage_key,
83
- location: context[:location],
74
+ location: location,
84
75
  io: io,
85
- upload_options: context[:upload_options] || {},
86
- options: context,
87
- ) { super }
76
+ upload_options: upload_options,
77
+ metadata: metadata,
78
+ options: options,
79
+ }) { super }
88
80
  end
89
81
 
90
82
  # Sends a `metadata.shrine` event.
91
- def get_metadata(io, context)
92
- return super if io.is_a?(UploadedFile) && context[:metadata] != true || context[:metadata] == false
83
+ def get_metadata(io, metadata: nil, **options)
84
+ return super if io.is_a?(UploadedFile) && metadata != true || metadata == false
93
85
 
94
- self.class.instrument(
95
- :metadata,
86
+ self.class.instrument(:metadata, {
96
87
  storage: storage_key,
97
88
  io: io,
98
- options: context,
99
- ) { super }
89
+ options: options,
90
+ }) { super }
100
91
  end
101
92
  end
102
93
 
103
94
  module FileMethods
104
95
  # Sends a `download.shrine` event.
105
- def open(**options)
106
- shrine_class.instrument(
107
- :download,
108
- storage: storage_key.to_sym,
96
+ def stream(destination, **options)
97
+ return super if opened?
98
+
99
+ shrine_class.instrument(:download, {
100
+ storage: storage_key,
109
101
  location: id,
110
102
  download_options: options,
111
- ) { super }
103
+ }) { super(destination, **options, instrument: false) }
112
104
  end
113
105
 
114
106
  # Sends a `exists.shrine` event.
115
107
  def exists?
116
- shrine_class.instrument(
117
- :exists,
118
- storage: storage_key.to_sym,
108
+ shrine_class.instrument(:exists, {
109
+ storage: storage_key,
119
110
  location: id,
120
- ) { super }
111
+ }) { super }
121
112
  end
122
113
 
123
114
  # Sends a `delete.shrine` event.
124
115
  def delete
125
- shrine_class.instrument(
126
- :delete,
127
- storage: storage_key.to_sym,
116
+ shrine_class.instrument(:delete, {
117
+ storage: storage_key,
118
+ location: id,
119
+ }) { super }
120
+ end
121
+
122
+ private
123
+
124
+ # Sends an `open.shrine` event.
125
+ def _open(instrument: true, **options)
126
+ return super(**options) unless instrument
127
+
128
+ shrine_class.instrument(:open, {
129
+ storage: storage_key,
128
130
  location: id,
129
- ) { super }
131
+ download_options: options,
132
+ }) { super(**options) }
130
133
  end
131
134
  end
132
135
 
@@ -255,6 +258,15 @@ class Shrine
255
258
  )}"
256
259
  end
257
260
 
261
+ def on_open(event)
262
+ log "Open (#{event.duration}ms) – #{format(
263
+ storage: event[:storage],
264
+ location: event[:location],
265
+ download_options: event[:download_options],
266
+ uploader: event[:uploader],
267
+ )}"
268
+ end
269
+
258
270
  def on_exists(event)
259
271
  log "Exists (#{event.duration}ms) – #{format(
260
272
  storage: event[:storage],
@@ -2,23 +2,11 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/keep_files.md] on GitHub.
6
- #
7
- # [doc/plugins/keep_files.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/keep_files.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/keep_files
8
6
  module KeepFiles
9
- def self.configure(uploader, opts = {})
10
- keep_files = (uploader.opts[:keep_files] ||= [])
11
- opts[:destroyed] ? keep_files << :destroyed : keep_files.delete(:destroyed) if opts.key?(:destroyed)
12
- opts[:replaced] ? keep_files << :replaced : keep_files.delete(:replaced) if opts.key?(:replaced)
13
- end
14
-
15
7
  module AttacherMethods
16
- def replace
17
- super unless shrine_class.opts[:keep_files].include?(:replaced)
18
- end
19
-
20
- def destroy
21
- super unless shrine_class.opts[:keep_files].include?(:destroyed)
8
+ def destroy?
9
+ false
22
10
  end
23
11
  end
24
12
  end
@@ -2,37 +2,46 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/metadata_attributes.md] on GitHub.
6
- #
7
- # [doc/plugins/metadata_attributes.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/metadata_attributes.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/metadata_attributes
8
6
  module MetadataAttributes
9
- def self.configure(uploader, mappings = {})
10
- uploader.opts[:metadata_attributes_mappings] ||= {}
11
- uploader.opts[:metadata_attributes_mappings].merge!(mappings)
7
+ def self.load_dependencies(uploader, *)
8
+ uploader.plugin :entity
9
+ end
10
+
11
+ def self.configure(uploader, **opts)
12
+ uploader.opts[:metadata_attributes] ||= {}
13
+ uploader.opts[:metadata_attributes].merge!(opts)
12
14
  end
13
15
 
14
16
  module AttacherClassMethods
15
- def metadata_attributes(mappings)
16
- shrine_class.opts[:metadata_attributes_mappings].merge!(mappings)
17
+ def metadata_attributes(mappings = nil)
18
+ if mappings
19
+ shrine_class.opts[:metadata_attributes].merge!(mappings)
20
+ else
21
+ shrine_class.opts[:metadata_attributes]
22
+ end
17
23
  end
18
24
  end
19
25
 
20
26
  module AttacherMethods
21
- def assign(value, **options)
22
- super
23
- cached_file = get
27
+ def column_values
28
+ super.merge(metadata_attributes)
29
+ end
24
30
 
25
- shrine_class.opts[:metadata_attributes_mappings].each do |source, destination|
26
- attribute_name = destination.is_a?(Symbol) ? :"#{name}_#{destination}" : :"#{destination}"
31
+ private
27
32
 
28
- next unless record.respond_to?(:"#{attribute_name}=")
33
+ def metadata_attributes
34
+ values = {}
29
35
 
30
- if cached_file
31
- record.send(:"#{attribute_name}=", cached_file.metadata[source.to_s])
32
- else
33
- record.send(:"#{attribute_name}=", nil)
34
- end
36
+ self.class.metadata_attributes.each do |source, destination|
37
+ metadata_attribute = destination.is_a?(Symbol) ? :"#{name}_#{destination}" : :"#{destination}"
38
+
39
+ next unless record.respond_to?(metadata_attribute)
40
+
41
+ values[metadata_attribute] = file && file.metadata[source.to_s]
35
42
  end
43
+
44
+ values
36
45
  end
37
46
  end
38
47
  end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/mirroring
6
+ module Mirroring
7
+ def self.configure(uploader, **opts)
8
+ uploader.opts[:mirroring] ||= { upload: true, delete: true }
9
+ uploader.opts[:mirroring].merge!(opts)
10
+
11
+ fail Error, ":mirror option is required for mirroring plugin" unless uploader.opts[:mirroring][:mirror]
12
+ end
13
+
14
+ module ClassMethods
15
+ def mirrors(storage_key = nil)
16
+ if storage_key
17
+ mirrors = opts[:mirroring][:mirror][storage_key]
18
+
19
+ fail Error, "no mirrors registered for storage #{storage_key.inspect}" unless mirrors
20
+
21
+ Array(mirrors)
22
+ else
23
+ opts[:mirroring][:mirror]
24
+ end
25
+ end
26
+
27
+ def mirror_upload_block(&block)
28
+ if block
29
+ opts[:mirroring][:upload_block] = block
30
+ else
31
+ opts[:mirroring][:upload_block]
32
+ end
33
+ end
34
+
35
+ def mirror_delete_block(&block)
36
+ if block
37
+ opts[:mirroring][:delete_block] = block
38
+ else
39
+ opts[:mirroring][:delete_block]
40
+ end
41
+ end
42
+
43
+ def mirror_upload?
44
+ opts[:mirroring][:upload]
45
+ end
46
+
47
+ def mirror_delete?
48
+ opts[:mirroring][:delete]
49
+ end
50
+ end
51
+
52
+ module InstanceMethods
53
+ # Mirrors upload to other mirror storages.
54
+ def upload(io, mirror: true, **options)
55
+ file = super(io, **options)
56
+ file.trigger_mirror_upload(**options) if mirror
57
+ file
58
+ end
59
+ end
60
+
61
+ module FileMethods
62
+ # Mirrors upload if mirrors are defined. Calls mirror block if
63
+ # registered, otherwise mirrors synchronously.
64
+ def trigger_mirror_upload(**options)
65
+ return unless shrine_class.mirrors[storage_key] && shrine_class.mirror_upload?
66
+
67
+ if shrine_class.mirror_upload_block
68
+ mirror_upload_background(**options)
69
+ else
70
+ mirror_upload(**options)
71
+ end
72
+ end
73
+
74
+ # Calls mirror upload block.
75
+ def mirror_upload_background(**options)
76
+ fail Error, "mirror upload block is not registered" unless shrine_class.mirror_upload_block
77
+
78
+ shrine_class.mirror_upload_block.call(self, **options)
79
+ end
80
+
81
+ # Uploads the file to each mirror storage.
82
+ def mirror_upload(**options)
83
+ previously_opened = opened?
84
+
85
+ each_mirror do |mirror|
86
+ rewind if opened?
87
+
88
+ shrine_class.upload(self, mirror, **options, location: id, close: false, action: :mirror)
89
+ end
90
+ ensure
91
+ if opened? && !previously_opened
92
+ close
93
+ @io = nil
94
+ end
95
+ end
96
+
97
+ # Mirrors delete to other mirror storages.
98
+ def delete(mirror: true)
99
+ result = super()
100
+ trigger_mirror_delete if mirror
101
+ result
102
+ end
103
+
104
+ # Mirrors delete if mirrors are defined. Calls mirror block if
105
+ # registered, otherwise mirrors synchronously.
106
+ def trigger_mirror_delete
107
+ return unless shrine_class.mirrors[storage_key] && shrine_class.mirror_delete?
108
+
109
+ if shrine_class.mirror_delete_block
110
+ mirror_delete_background
111
+ else
112
+ mirror_delete
113
+ end
114
+ end
115
+
116
+ # Calls mirror delete block.
117
+ def mirror_delete_background
118
+ fail Error, "mirror delete block is not registered" unless shrine_class.mirror_delete_block
119
+
120
+ shrine_class.mirror_delete_block.call(self)
121
+ end
122
+
123
+ # Deletes the file from each mirror storage.
124
+ def mirror_delete
125
+ each_mirror do |mirror|
126
+ self.class.new(id: id, storage: mirror).delete
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ # Iterates over mirror storages.
133
+ def each_mirror(&block)
134
+ mirrors = shrine_class.mirrors(storage_key)
135
+ mirrors.map(&block)
136
+ end
137
+ end
138
+ end
139
+
140
+ register_plugin(:mirroring, Mirroring)
141
+ end
142
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/model
6
+ module Model
7
+ def self.load_dependencies(uploader, **)
8
+ uploader.plugin :entity
9
+ end
10
+
11
+ def self.configure(uploader, **opts)
12
+ uploader.opts[:model] ||= { cache: true }
13
+ uploader.opts[:model].merge!(opts)
14
+ end
15
+
16
+ module AttachmentMethods
17
+ # Allows disabling model behaviour:
18
+ #
19
+ # Shrine::Attachment(:image) # model (default)
20
+ # Shrine::Attachment(:image, model: false) # entity
21
+ def initialize(name, model: true, **options)
22
+ super(name, **options)
23
+ @model = model
24
+ end
25
+
26
+ # We define model methods only on inclusion. This gives other plugins
27
+ # the ability to disable model behaviour for entity classes. In this
28
+ # case we want to skip defining model methods as well.
29
+ def included(klass)
30
+ super
31
+ define_model_methods(@name) if model?
32
+ end
33
+
34
+ private
35
+
36
+ # Defines attachment setter and enhances the copy constructor.
37
+ def define_model_methods(name)
38
+ super if defined?(super)
39
+
40
+ define_method :"#{name}=" do |value|
41
+ send(:"#{name}_attacher").model_assign(value)
42
+ end
43
+
44
+ define_method :"#{name}_changed?" do
45
+ send(:"#{name}_attacher").changed?
46
+ end
47
+
48
+ # The copy constructor that's called on #dup and #clone.
49
+ define_method :initialize_copy do |other|
50
+ super(other)
51
+ instance_variable_set(:"@#{name}_attacher", instance_variable_get(:"@#{name}_attacher")&.dup)
52
+ self
53
+ end
54
+ private :initialize_copy
55
+ end
56
+
57
+ # Memoizes the attacher instance into an instance variable.
58
+ def attacher(record, **options)
59
+ return super unless model?
60
+
61
+ if !record.instance_variable_get(:"@#{@name}_attacher") || options.any?
62
+ attacher = class_attacher(**options)
63
+ attacher.load_model(record, @name)
64
+
65
+ record.instance_variable_set(:"@#{@name}_attacher", attacher)
66
+ else
67
+ record.instance_variable_get(:"@#{@name}_attacher")
68
+ end
69
+ end
70
+
71
+ def model?
72
+ @model
73
+ end
74
+ end
75
+
76
+ module AttacherClassMethods
77
+ # Initializes itself from a model instance and attachment name.
78
+ #
79
+ # photo.image_data #=> "{...}" # a file is attached
80
+ #
81
+ # attacher = Attacher.from_model(photo, :image)
82
+ # attacher.file #=> #<Shrine::UploadedFile>
83
+ def from_model(record, name, **options)
84
+ attacher = new(**options)
85
+ attacher.load_model(record, name)
86
+ attacher
87
+ end
88
+ end
89
+
90
+ module AttacherMethods
91
+ def initialize(model_cache: shrine_class.opts[:model][:cache], **options)
92
+ super(**options)
93
+ @model_cache = model_cache
94
+ @model = nil
95
+ end
96
+
97
+ # Saves record and name and initializes attachment from the model
98
+ # attribute. Called from `Attacher.from_model`.
99
+ def load_model(record, name)
100
+ set_model(record, name)
101
+ read
102
+ end
103
+
104
+ # Saves record and name without loading attachment from the model
105
+ # attribute.
106
+ def set_model(record, name)
107
+ set_entity(record, name)
108
+ @model = true
109
+ end
110
+
111
+ # Called by the attachment attribute setter on the model.
112
+ def model_assign(value, **options)
113
+ if model_cache?
114
+ assign(value, **options)
115
+ else
116
+ attach(value, **options)
117
+ end
118
+ end
119
+
120
+ # Writes uploaded file data into the model.
121
+ def set(*)
122
+ result = super
123
+ write if model?
124
+ result
125
+ end
126
+
127
+ # Writes the attachment data into the model attribute.
128
+ def write
129
+ column_values.each do |name, value|
130
+ write_attribute(name, value)
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ # Writes given value into the model attribute.
137
+ def write_attribute(name = attribute, value)
138
+ record.public_send(:"#{name}=", value)
139
+ end
140
+
141
+ # Returns whether assigned files should be uploaded to/loaded from
142
+ # temporary storage.
143
+ def model_cache?
144
+ @model_cache
145
+ end
146
+
147
+ # Returns whether the attacher is being backed by a model instance.
148
+ # This allows users to still use the attacher with an entity instance
149
+ # or without any record instance.
150
+ def model?
151
+ @model
152
+ end
153
+ end
154
+ end
155
+
156
+ register_plugin(:model, Model)
157
+ end
158
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ Shrine.deprecation("The module_include plugin is deprecated and will be removed in Shrine 4. Override core classes directly instead.")
4
+
3
5
  class Shrine
4
6
  module Plugins
5
- # Documentation lives in [doc/plugins/module_include.md] on GitHub.
6
- #
7
- # [doc/plugins/module_include.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/module_include.md
7
+ # Documentation can be found on https://shrinerb.com/docs/plugins/module_include
8
8
  module ModuleInclude
9
9
  module ClassMethods
10
10
  def attachment_module(mod = nil, &block)