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
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/form_assign
6
+ module FormAssign
7
+ def self.load_dependencies(uploader)
8
+ uploader.plugin :entity
9
+ end
10
+
11
+ def self.configure(uploader, **opts)
12
+ uploader.opts[:form_assign] ||= { result: :params }
13
+ uploader.opts[:form_assign].merge!(opts)
14
+ end
15
+
16
+ module AttacherMethods
17
+ # Helper for setting the attachment from form fields. Returns normalized
18
+ # fields.
19
+ #
20
+ # attacher = Shrine::Attacher.from_entity(photo, :image)
21
+ #
22
+ # attacher.form_assign({ image: file, title: "Title" })
23
+ # #=> { image: '{...}', title: "Title" }
24
+ #
25
+ # attacher.form_assign({ image: "", image_remote_url: "...", title: "Title" })
26
+ # #=> { image: '{...}', title: "Title" }
27
+ #
28
+ # attacher.form_assign({ image: "", title: "Title" })
29
+ # #=> { title: "Title" }
30
+ #
31
+ # You can also return the result in form of attributes to be used for
32
+ # database record creation.
33
+ #
34
+ # attacher.form_assign({ image: file, title: "Title" }, result: :attributes)
35
+ # #=> { image_data: '{...}', title: "Title" }
36
+ def form_assign(fields, result: shrine_class.opts[:form_assign][:result])
37
+ form = create_form_object
38
+ fields = form_write(form, fields)
39
+
40
+ form_attach(form)
41
+
42
+ form_result(fields, result)
43
+ end
44
+
45
+ private
46
+
47
+ # Assigns form params to the form object using Shrine's attachment
48
+ # writers.
49
+ def form_write(form, fields)
50
+ result = fields.dup
51
+
52
+ fields.each do |key, value|
53
+ if form.respond_to?(:"#{key}=")
54
+ form.send(:"#{key}=", value)
55
+
56
+ result.delete(key)
57
+ end
58
+ end
59
+
60
+ result
61
+ end
62
+
63
+ # Attaches the file from the form object if atachment has changed.
64
+ def form_attach(form)
65
+ return unless form.send(:"#{name}_attacher").changed?
66
+
67
+ file = form.send(:"#{name}_attacher").file
68
+
69
+ if file
70
+ change uploaded_file(file.data) # use our UploadedFile class
71
+ else
72
+ change nil
73
+ end
74
+ end
75
+
76
+ # Adds attached file data to the fields if attachment has changed.
77
+ def form_result(fields, result_type)
78
+ return fields unless changed?
79
+
80
+ case result_type
81
+ when :params then fields[name] = file&.to_json
82
+ when :attributes then fields[attribute] = column_data
83
+ else
84
+ fail ArgumentError, "unrecognized result type: #{result_type.inspect}"
85
+ end
86
+
87
+ fields
88
+ end
89
+
90
+ # Creates a disposable form object with model plugin loaded.
91
+ def create_form_object
92
+ # load the model plugin into a disposable Shrine subclass
93
+ shrine_subclass = Class.new(shrine_class)
94
+ shrine_subclass.plugin :model
95
+
96
+ # create a model class with attachment methods
97
+ form_class = Struct.new(attribute)
98
+ form_class.include shrine_subclass::Attachment(name)
99
+
100
+ # instantiate form object
101
+ form_class.new(column_data)
102
+ end
103
+ end
104
+ end
105
+
106
+ register_plugin(:form_assign, FormAssign)
107
+ end
108
+ end
@@ -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,21 @@ class Shrine
53
51
  end
54
52
 
55
53
  module InstanceMethods
56
- def basic_location(io, metadata:)
57
- location = super
58
- current_extension = File.extname(location)
59
-
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?
63
- end
64
-
65
- location
54
+ def infer_extension(mime_type)
55
+ self.class.infer_extension(mime_type)
66
56
  end
67
57
 
68
58
  private
69
59
 
70
- def infer_extension(mime_type)
71
- self.class.infer_extension(mime_type).to_s
60
+ def basic_location(io, metadata:)
61
+ location = Pathname(super)
62
+
63
+ if location.extname.empty? || opts[:infer_extension][:force]
64
+ inferred_extension = self.class.infer_extension(metadata["mime_type"])
65
+ location = location.sub_ext(inferred_extension) if inferred_extension
66
+ end
67
+
68
+ location.to_s
72
69
  end
73
70
  end
74
71
 
@@ -2,25 +2,24 @@
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
- uploader.opts[:instrumentation][:notifications] ||= ::ActiveSupport::Notifications
18
-
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] = [] }
15
+ begin
16
+ uploader.opts[:instrumentation][:notifications] ||= ::ActiveSupport::Notifications
17
+ rescue NameError
18
+ fail Error, "default notifications library is ActiveSupport::Notifications, but activesupport gem is not installed"
19
+ end
21
20
 
22
21
  uploader.opts[:instrumentation][:log_events].each do |event_name|
23
- uploader.subscribe(event_name, &uploader.opts[:instrumentation][:log_subscriber])
22
+ uploader.subscribe(event_name, &log_subscriber)
24
23
  end
25
24
  end
26
25
 
@@ -48,12 +47,13 @@ class Shrine
48
47
  # end
49
48
  def subscribe(event_name, &subscriber)
50
49
  return if subscriber.nil?
51
- return if subscribers[event_name].include?(subscriber)
50
+ return if subscribers[event_name]&.include?(subscriber)
52
51
 
53
52
  notifications.subscribe("#{event_name}.shrine") do |event|
54
53
  subscriber.call(event) if event[:uploader] <= self
55
54
  end
56
55
 
56
+ subscribers[event_name] ||= []
57
57
  subscribers[event_name] << subscriber
58
58
  end
59
59
 
@@ -63,12 +63,8 @@ class Shrine
63
63
  Notifications.new(opts[:instrumentation][:notifications])
64
64
  end
65
65
 
66
- def log_subscriber
67
- opts[:instrumentation][:log_subscriber]
68
- end
69
-
70
66
  def subscribers
71
- opts[:instrumentation_subscribers]
67
+ opts[:instrumentation][:subscribers]
72
68
  end
73
69
  end
74
70
 
@@ -76,57 +72,68 @@ class Shrine
76
72
  private
77
73
 
78
74
  # Sends a `upload.shrine` event.
79
- def copy(io, context)
80
- self.class.instrument(
81
- :upload,
75
+ def _upload(io, location:, metadata:, upload_options: {}, **options)
76
+ self.class.instrument(:upload, {
82
77
  storage: storage_key,
83
- location: context[:location],
78
+ location: location,
84
79
  io: io,
85
- upload_options: context[:upload_options] || {},
86
- options: context,
87
- ) { super }
80
+ upload_options: upload_options,
81
+ metadata: metadata,
82
+ options: options,
83
+ }) { super }
88
84
  end
89
85
 
90
86
  # 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
87
+ def get_metadata(io, metadata: nil, **options)
88
+ return super if io.is_a?(UploadedFile) && metadata != true || metadata == false
93
89
 
94
- self.class.instrument(
95
- :metadata,
90
+ self.class.instrument(:metadata, {
96
91
  storage: storage_key,
97
92
  io: io,
98
- options: context,
99
- ) { super }
93
+ options: options,
94
+ }) { super }
100
95
  end
101
96
  end
102
97
 
103
98
  module FileMethods
104
99
  # Sends a `download.shrine` event.
105
- def open(**options)
106
- shrine_class.instrument(
107
- :download,
108
- storage: storage_key.to_sym,
100
+ def stream(destination, **options)
101
+ return super if opened?
102
+
103
+ shrine_class.instrument(:download, {
104
+ storage: storage_key,
109
105
  location: id,
110
106
  download_options: options,
111
- ) { super }
107
+ }) { super(destination, **options, instrument: false) }
112
108
  end
113
109
 
114
110
  # Sends a `exists.shrine` event.
115
111
  def exists?
116
- shrine_class.instrument(
117
- :exists,
118
- storage: storage_key.to_sym,
112
+ shrine_class.instrument(:exists, {
113
+ storage: storage_key,
119
114
  location: id,
120
- ) { super }
115
+ }) { super }
121
116
  end
122
117
 
123
118
  # Sends a `delete.shrine` event.
124
119
  def delete
125
- shrine_class.instrument(
126
- :delete,
127
- storage: storage_key.to_sym,
120
+ shrine_class.instrument(:delete, {
121
+ storage: storage_key,
128
122
  location: id,
129
- ) { super }
123
+ }) { super }
124
+ end
125
+
126
+ private
127
+
128
+ # Sends an `open.shrine` event.
129
+ def _open(instrument: true, **options)
130
+ return super(**options) unless instrument
131
+
132
+ shrine_class.instrument(:open, {
133
+ storage: storage_key,
134
+ location: id,
135
+ download_options: options,
136
+ }) { super(**options) }
130
137
  end
131
138
  end
132
139
 
@@ -255,6 +262,15 @@ class Shrine
255
262
  )}"
256
263
  end
257
264
 
265
+ def on_open(event)
266
+ log "Open (#{event.duration}ms) – #{format(
267
+ storage: event[:storage],
268
+ location: event[:location],
269
+ download_options: event[:download_options],
270
+ uploader: event[:uploader],
271
+ )}"
272
+ end
273
+
258
274
  def on_exists(event)
259
275
  log "Exists (#{event.duration}ms) – #{format(
260
276
  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