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
@@ -4,10 +4,10 @@ require "down"
4
4
 
5
5
  class Shrine
6
6
  module Plugins
7
- # Documentation lives in [doc/plugins/remote_url.md] on GitHub.
8
- #
9
- # [doc/plugins/remote_url.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/remote_url.md
7
+ # Documentation can be found on https://shrinerb.com/docs/plugins/remote_url
10
8
  module RemoteUrl
9
+ class DownloadError < Error; end
10
+
11
11
  LOG_SUBSCRIBER = -> (event) do
12
12
  Shrine.logger.info "Remote URL (#{event.duration}ms) – #{{
13
13
  remote_url: event[:remote_url],
@@ -16,8 +16,14 @@ class Shrine
16
16
  }.inspect}"
17
17
  end
18
18
 
19
- def self.configure(uploader, opts = {})
20
- uploader.opts[:remote_url] ||= { downloader: Down.method(:download), log_subscriber: LOG_SUBSCRIBER }
19
+ DOWNLOADER = -> (url, **options) { Down.download(url, **options) }
20
+
21
+ def self.load_dependencies(uploader, *)
22
+ uploader.plugin :validation
23
+ end
24
+
25
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
26
+ uploader.opts[:remote_url] ||= { downloader: DOWNLOADER }
21
27
  uploader.opts[:remote_url].merge!(opts)
22
28
 
23
29
  unless uploader.opts[:remote_url].key?(:max_size)
@@ -25,8 +31,20 @@ class Shrine
25
31
  end
26
32
 
27
33
  # instrumentation plugin integration
28
- if uploader.respond_to?(:subscribe)
29
- uploader.subscribe(:remote_url, &uploader.opts[:remote_url][:log_subscriber])
34
+ uploader.subscribe(:remote_url, &log_subscriber) if uploader.respond_to?(:subscribe)
35
+ end
36
+
37
+ module AttachmentMethods
38
+ def define_model_methods(name)
39
+ super if defined?(super)
40
+
41
+ define_method :"#{name}_remote_url=" do |url|
42
+ send(:"#{name}_attacher").remote_url = url
43
+ end
44
+
45
+ define_method :"#{name}_remote_url" do
46
+ send(:"#{name}_attacher").remote_url
47
+ end
30
48
  end
31
49
  end
32
50
 
@@ -38,12 +56,22 @@ class Shrine
38
56
  options = { max_size: opts[:remote_url][:max_size] }.merge(options)
39
57
 
40
58
  instrument_remote_url(url, options) do
41
- opts[:remote_url][:downloader].call(url, options)
59
+ download_remote_url(url, options)
42
60
  end
43
61
  end
44
62
 
45
63
  private
46
64
 
65
+ def download_remote_url(url, options)
66
+ opts[:remote_url][:downloader].call(url, **options)
67
+ rescue Down::TooLarge
68
+ fail DownloadError, "remote file too large"
69
+ rescue Down::Error
70
+ fail DownloadError, "remote file not found"
71
+ rescue DownloadError
72
+ fail # re-raise
73
+ end
74
+
47
75
  # Sends a `remote_url.shrine` event for instrumentation plugin.
48
76
  def instrument_remote_url(url, options, &block)
49
77
  return yield unless respond_to?(:instrument)
@@ -52,20 +80,6 @@ class Shrine
52
80
  end
53
81
  end
54
82
 
55
- module AttachmentMethods
56
- def initialize(name, **options)
57
- super
58
-
59
- define_method :"#{name}_remote_url=" do |url|
60
- send(:"#{name}_attacher").remote_url = url
61
- end
62
-
63
- define_method :"#{name}_remote_url" do
64
- send(:"#{name}_attacher").remote_url
65
- end
66
- end
67
- end
68
-
69
83
  module AttacherMethods
70
84
  # Downloads the remote file and assigns it. If download failed, sets
71
85
  # the error message and assigns the url to an instance variable so that
@@ -73,45 +87,31 @@ class Shrine
73
87
  def assign_remote_url(url, downloader: {}, **options)
74
88
  return if url == "" || url.nil?
75
89
 
76
- begin
77
- downloaded_file = shrine_class.remote_url(url, downloader)
78
- rescue => error
79
- download_error = error
80
- end
81
-
82
- if downloaded_file
83
- assign(downloaded_file, **options)
84
- else
85
- message = download_error_message(url, download_error)
86
- errors.replace [message]
87
- @remote_url = url
88
- end
90
+ downloaded_file = shrine_class.remote_url(url, **downloader)
91
+ attach_cached(downloaded_file, **options)
92
+ rescue DownloadError => error
93
+ errors.clear << remote_url_error_message(url, error)
94
+ false
89
95
  end
90
96
 
91
- # Alias for #assign_remote_url.
97
+ # Used by `<name>_data_uri=` attachment method.
92
98
  def remote_url=(url)
93
99
  assign_remote_url(url)
100
+ @remote_url = url
94
101
  end
95
102
 
96
- # Form builders require the reader as well.
103
+ # Used by `<name>_data_uri` attachment method.
97
104
  def remote_url
98
105
  @remote_url
99
106
  end
100
107
 
101
108
  private
102
109
 
103
- def download_error_message(url, error)
104
- if message = shrine_class.opts[:remote_url][:error_message]
105
- if message.respond_to?(:call)
106
- args = [url, error].take(message.arity.abs)
107
- message = message.call(*args)
108
- end
109
- else
110
- message = "download failed"
111
- message = "#{message}: #{error.message}" if error
112
- end
113
-
114
- message
110
+ # Generates an error message for failed remote URL download.
111
+ def remote_url_error_message(url, error)
112
+ message = shrine_class.opts[:remote_url][:error_message]
113
+ message = message.call *[url, error].take(message.arity.abs) if message.respond_to?(:call)
114
+ message || "download failed: #{error.message}"
115
115
  end
116
116
  end
117
117
  end
@@ -2,13 +2,11 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/remove_attachment.md] on GitHub.
6
- #
7
- # [doc/plugins/remove_attachment.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/remove_attachment.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/remove_attachment
8
6
  module RemoveAttachment
9
7
  module AttachmentMethods
10
- def initialize(name, **options)
11
- super
8
+ def define_model_methods(name)
9
+ super if defined?(super)
12
10
 
13
11
  define_method :"remove_#{name}=" do |value|
14
12
  send(:"#{name}_attacher").remove = value
@@ -24,7 +22,8 @@ class Shrine
24
22
  # We remove the attachment if the value evaluates to true.
25
23
  def remove=(value)
26
24
  @remove = value
27
- assign(nil) if remove?
25
+
26
+ change(nil) if remove?
28
27
  end
29
28
 
30
29
  def remove
@@ -33,8 +32,15 @@ class Shrine
33
32
 
34
33
  private
35
34
 
35
+ # Don't override previously removed attachment that wasn't yet deleted.
36
+ def change?(file)
37
+ super && !(changed? && remove?)
38
+ end
39
+
36
40
  # Rails sends "0" or "false" if the checkbox hasn't been ticked.
37
41
  def remove?
42
+ return remove if [true, false].include?(remove)
43
+
38
44
  remove && remove != "" && remove !~ /\A(0|false)\z/
39
45
  end
40
46
  end
@@ -2,18 +2,29 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/remove_invalid.md] on GitHub.
6
- #
7
- # [doc/plugins/remove_invalid.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/remove_invalid.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/remove_invalid
8
6
  module RemoveInvalid
7
+ def self.load_dependencies(uploader)
8
+ uploader.plugin :validation
9
+ end
10
+
9
11
  module AttacherMethods
10
- def validate
12
+ def validate(*)
11
13
  super
12
14
  ensure
13
- if errors.any? && changed?
14
- _delete(get, action: :validate)
15
- _set(@old)
16
- remove_instance_variable(:@old)
15
+ deassign if errors.any?
16
+ end
17
+
18
+ private
19
+
20
+ def deassign
21
+ destroy
22
+
23
+ if changed?
24
+ load_data @previous.data
25
+ @previous = nil
26
+ else
27
+ load_data nil
17
28
  end
18
29
  end
19
30
  end
@@ -2,20 +2,26 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/restore_cached_data.md] on GitHub.
6
- #
7
- # [doc/plugins/restore_cached_data.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/restore_cached_data.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/restore_cached_data
8
6
  module RestoreCachedData
9
- def self.load_dependencies(uploader, *)
7
+ def self.load_dependencies(uploader)
10
8
  uploader.plugin :refresh_metadata
11
9
  end
12
10
 
13
11
  module AttacherMethods
14
12
  private
15
13
 
16
- def assign_cached(cached_file)
17
- uploaded_file(cached_file) { |file| file.refresh_metadata!(context) }
18
- super(cached_file)
14
+ def cached(value, **options)
15
+ cached_file = super
16
+
17
+ # TODO: Remove this conditional when we remove the versions plugin
18
+ if cached_file.is_a?(Hash) || cached_file.is_a?(Array)
19
+ uploaded_file(cached_file) { |file| file.refresh_metadata!(**context, **options) }
20
+ else
21
+ cached_file.refresh_metadata!(**context, **options)
22
+ end
23
+
24
+ cached_file
19
25
  end
20
26
  end
21
27
  end
@@ -4,13 +4,21 @@ require "sequel"
4
4
 
5
5
  class Shrine
6
6
  module Plugins
7
- # Documentation lives in [doc/plugins/sequel.md] on GitHub.
8
- #
9
- # [doc/plugins/sequel.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/sequel.md
7
+ # Documentation can be found on https://shrinerb.com/docs/plugins/sequel
10
8
  module Sequel
11
- def self.configure(uploader, opts = {})
12
- uploader.opts[:sequel_callbacks] = opts.fetch(:callbacks, uploader.opts.fetch(:sequel_callbacks, true))
13
- uploader.opts[:sequel_validations] = opts.fetch(:validations, uploader.opts.fetch(:sequel_validations, true))
9
+ def self.load_dependencies(uploader, **)
10
+ uploader.plugin :model
11
+ uploader.plugin :_persistence, plugin: self
12
+ end
13
+
14
+ def self.configure(uploader, **opts)
15
+ if opts.key?(:callbacks)
16
+ Shrine.deprecation("The :callbacks option in sequel plugin has been renamed to :hooks. The :callbacks alias will be removed in Shrine 4.")
17
+ opts[:hooks] = opts.delete(:callbacks)
18
+ end
19
+
20
+ uploader.opts[:sequel] ||= { hooks: true, validations: true }
21
+ uploader.opts[:sequel].merge!(opts)
14
22
  end
15
23
 
16
24
  module AttachmentMethods
@@ -19,68 +27,110 @@ class Shrine
19
27
 
20
28
  return unless model < ::Sequel::Model
21
29
 
22
- name = attachment_name
30
+ name = @name
23
31
 
24
- if shrine_class.opts[:sequel_validations]
32
+ if shrine_class.opts[:sequel][:validations]
25
33
  define_method :validate do
26
34
  super()
27
- send(:"#{name}_attacher").errors.each do |message|
28
- errors.add(name, *message)
29
- end
35
+ send(:"#{name}_attacher").send(:sequel_validate)
30
36
  end
31
37
  end
32
38
 
33
- if shrine_class.opts[:sequel_callbacks]
39
+ if shrine_class.opts[:sequel][:hooks]
34
40
  define_method :before_save do
35
41
  super()
36
- attacher = send(:"#{name}_attacher")
37
- attacher.save if attacher.changed?
42
+ if send(:"#{name}_attacher").changed?
43
+ send(:"#{name}_attacher").send(:sequel_before_save)
44
+ end
38
45
  end
39
46
 
40
47
  define_method :after_save do
41
48
  super()
42
- attacher = send(:"#{name}_attacher")
43
- db.after_commit { attacher.finalize } if attacher.changed?
49
+ if send(:"#{name}_attacher").changed?
50
+ send(:"#{name}_attacher").send(:sequel_after_save)
51
+ end
44
52
  end
45
53
 
46
54
  define_method :after_destroy do
47
55
  super()
48
- attacher = send(:"#{name}_attacher")
49
- db.after_commit { attacher.destroy } if attacher.read
56
+ if send(:"#{name}_attacher").attached?
57
+ send(:"#{name}_attacher").send(:sequel_after_destroy)
58
+ end
50
59
  end
51
60
  end
52
- end
53
- end
54
61
 
55
- module AttacherClassMethods
56
- # Needed by the `backgrounding` plugin.
57
- def find_record(record_class, record_id)
58
- record_class.with_pk(record_id)
62
+ # reload the attacher on record reload
63
+ define_method :_refresh do |*args|
64
+ result = super(*args)
65
+ send(:"#{name}_attacher").reload if instance_variable_defined?(:"@#{name}_attacher")
66
+ result
67
+ end
68
+ private :_refresh
59
69
  end
60
70
  end
61
71
 
72
+ # The _persistence plugin uses #sequel_persist, #sequel_reload and
73
+ # #sequel? to implement the following methods:
74
+ #
75
+ # * Attacher#persist
76
+ # * Attacher#atomic_persist
77
+ # * Attacher#atomic_promote
62
78
  module AttacherMethods
63
79
  private
64
80
 
65
- # Saves the record after assignment, skipping validations.
66
- def update(uploaded_file)
67
- super
81
+ # Adds file validation errors to the model. Called on model validation.
82
+ def sequel_validate
83
+ return unless respond_to?(:errors)
84
+
85
+ errors.each do |message|
86
+ record.errors.add(name, *message)
87
+ end
88
+ end
89
+
90
+ # Calls Attacher#save. Called before model save.
91
+ def sequel_before_save
92
+ save
93
+ end
94
+
95
+ # Finalizes attachment and persists changes. Called after model save.
96
+ def sequel_after_save
97
+ record.db.after_commit do
98
+ finalize
99
+ persist
100
+ end
101
+ end
102
+
103
+ # Deletes attached files. Called after model destroy.
104
+ def sequel_after_destroy
105
+ record.db.after_commit do
106
+ destroy_attached
107
+ end
108
+ end
109
+
110
+ # Saves changes to the model instance, skipping validations. Used by
111
+ # the _persistence plugin.
112
+ def sequel_persist
68
113
  record.save_changes(validate: false)
69
114
  end
70
115
 
71
- # If the data represents a JSON column with `pg_json` Sequel extension
72
- # loaded, a `Sequel::Postgres::JSONHashBase` object will be returned,
73
- # which we convert into a Hash.
74
- def convert_after_read(value)
75
- sequel_json_column? ? value.to_hash : super
116
+ # Locks the database row and yields the reloaded record. Used by the
117
+ # _persistence plugin.
118
+ def sequel_reload
119
+ record.db.transaction { yield record.dup.lock! }
76
120
  end
77
121
 
78
122
  # Returns true if the data attribute represents a JSON or JSONB column.
79
- def sequel_json_column?
80
- return false unless record.is_a?(::Sequel::Model)
81
- return false unless column = record.class.db_schema[data_attribute]
123
+ # Used by the _persistence plugin to determine whether serialization
124
+ # should be skipped.
125
+ def sequel_hash_attribute?
126
+ column = record.class.db_schema[attribute]
127
+ column && [:json, :jsonb].include?(column[:type])
128
+ end
82
129
 
83
- [:json, :jsonb].include?(column[:type])
130
+ # Returns whether the record is a Sequel model. Used by the
131
+ # _persistence plugin.
132
+ def sequel?
133
+ record.is_a?(::Sequel::Model)
84
134
  end
85
135
  end
86
136
  end
@@ -2,9 +2,7 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/signature.md] on GitHub.
6
- #
7
- # [doc/plugins/signature.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/signature.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/signature
8
6
  module Signature
9
7
  LOG_SUBSCRIBER = -> (event) do
10
8
  Shrine.logger.info "Signature (#{event.duration}ms) – #{{
@@ -15,23 +13,21 @@ class Shrine
15
13
  }.inspect}"
16
14
  end
17
15
 
18
- def self.configure(uploader, opts = {})
19
- uploader.opts[:signature] ||= { log_subscriber: LOG_SUBSCRIBER }
20
- uploader.opts[:signature].merge!(opts)
21
-
16
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER)
22
17
  # instrumentation plugin integration
23
- if uploader.respond_to?(:subscribe)
24
- uploader.subscribe(:signature, &uploader.opts[:signature][:log_subscriber])
25
- end
18
+ uploader.subscribe(:signature, &log_subscriber) if uploader.respond_to?(:subscribe)
26
19
  end
27
20
 
28
21
  module ClassMethods
29
22
  # Calculates `algorithm` hash of the contents of the IO object, and
30
23
  # encodes it into `format`.
31
- def calculate_signature(io, algorithm, format: :hex)
32
- instrument_signature(io, algorithm, format) do
33
- SignatureCalculator.new(algorithm.downcase, format: format).call(io)
34
- end
24
+ def calculate_signature(io, algorithm, format: :hex, rewind: true)
25
+ calculator = SignatureCalculator.new(algorithm.downcase, format: format)
26
+
27
+ signature = instrument_signature(io, algorithm, format) { calculator.call(io) }
28
+ io.rewind if rewind
29
+
30
+ signature
35
31
  end
36
32
  alias signature calculate_signature
37
33
 
@@ -69,8 +65,6 @@ class Shrine
69
65
 
70
66
  def call(io)
71
67
  hash = send(:"calculate_#{algorithm}", io)
72
- io.rewind
73
-
74
68
  send(:"encode_#{format}", hash)
75
69
  end
76
70
 
@@ -2,9 +2,7 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/store_dimensions.md] on GitHub.
6
- #
7
- # [doc/plugins/store_dimensions.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/store_dimensions.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/store_dimensions
8
6
  module StoreDimensions
9
7
  LOG_SUBSCRIBER = -> (event) do
10
8
  Shrine.logger.info "Image Dimensions (#{event.duration}ms) – #{{
@@ -13,24 +11,22 @@ class Shrine
13
11
  }.inspect}"
14
12
  end
15
13
 
16
- def self.configure(uploader, opts = {})
17
- uploader.opts[:store_dimensions] ||= { analyzer: :fastimage, on_error: :ignore, log_subscriber: LOG_SUBSCRIBER }
14
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
15
+ uploader.opts[:store_dimensions] ||= { analyzer: :fastimage, on_error: :warn, auto_extraction: true }
18
16
  uploader.opts[:store_dimensions].merge!(opts)
19
17
 
20
18
  # resolve error strategy
21
- case uploader.opts[:store_dimensions][:on_error]
22
- when :fail
23
- uploader.opts[:store_dimensions][:on_error] = -> (error) { fail error }
24
- when :warn
25
- uploader.opts[:store_dimensions][:on_error] = -> (error) { Shrine.warn "Error occurred when attempting to extract image dimensions: #{error.inspect}" }
26
- when :ignore
27
- uploader.opts[:store_dimensions][:on_error] = -> (error) { }
28
- end
19
+ uploader.opts[:store_dimensions][:on_error] =
20
+ case uploader.opts[:store_dimensions][:on_error]
21
+ when :fail then -> (error) { fail error }
22
+ when :warn then -> (error) { Shrine.warn "Error occurred when attempting to extract image dimensions: #{error.inspect}" }
23
+ when :ignore then -> (error) { }
24
+ else
25
+ uploader.opts[:store_dimensions][:on_error]
26
+ end
29
27
 
30
28
  # instrumentation plugin integration
31
- if uploader.respond_to?(:subscribe)
32
- uploader.subscribe(:image_dimensions, &uploader.opts[:store_dimensions][:log_subscriber])
33
- end
29
+ uploader.subscribe(:image_dimensions, &log_subscriber) if uploader.respond_to?(:subscribe)
34
30
  end
35
31
 
36
32
  module ClassMethods
@@ -75,23 +71,13 @@ class Shrine
75
71
  end
76
72
 
77
73
  module InstanceMethods
78
- # We update the metadata with "width" and "height".
79
- def extract_metadata(io, context = {})
80
- width, height = extract_dimensions(io)
74
+ def extract_metadata(io, **options)
75
+ return super unless opts[:store_dimensions][:auto_extraction]
81
76
 
82
- super.merge!("width" => width, "height" => height)
83
- end
84
-
85
- private
86
-
87
- # Extracts dimensions using the specified analyzer.
88
- def extract_dimensions(io)
89
- self.class.extract_dimensions(io)
90
- end
77
+ # We update the metadata with "width" and "height".
78
+ width, height = self.class.extract_dimensions(io)
91
79
 
92
- # Returns a hash of built-in dimensions analyzers.
93
- def dimensions_analyzers
94
- self.class.dimensions_analyzers
80
+ super.merge!("width" => width, "height" => height)
95
81
  end
96
82
  end
97
83
 
@@ -129,23 +115,32 @@ class Shrine
129
115
 
130
116
  def extract_with_fastimage(io)
131
117
  require "fastimage"
132
- FastImage.size(io, raise_on_failure: true)
133
- rescue FastImage::FastImageException => error
134
- on_error(error)
118
+
119
+ begin
120
+ FastImage.size(io, raise_on_failure: true)
121
+ rescue FastImage::FastImageException => error
122
+ on_error(error)
123
+ end
135
124
  end
136
125
 
137
126
  def extract_with_mini_magick(io)
138
127
  require "mini_magick"
139
- Shrine.with_file(io) { |file| MiniMagick::Image.new(file.path).dimensions }
140
- rescue MiniMagick::Error => error
141
- on_error(error)
128
+
129
+ begin
130
+ Shrine.with_file(io) { |file| MiniMagick::Image.new(file.path).dimensions }
131
+ rescue MiniMagick::Error => error
132
+ on_error(error)
133
+ end
142
134
  end
143
135
 
144
136
  def extract_with_ruby_vips(io)
145
137
  require "vips"
146
- Shrine.with_file(io) { |file| Vips::Image.new_from_file(file.path).size }
147
- rescue Vips::Error => error
148
- on_error(error)
138
+
139
+ begin
140
+ Shrine.with_file(io) { |file| Vips::Image.new_from_file(file.path).size }
141
+ rescue Vips::Error => error
142
+ on_error(error)
143
+ end
149
144
  end
150
145
 
151
146
  def on_error(error)
@@ -2,9 +2,7 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/tempfile.md] on GitHub.
6
- #
7
- # [doc/plugins/tempfile.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/tempfile.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/tempfile
8
6
  module Tempfile
9
7
  module ClassMethods
10
8
  def with_file(io)