shrine 2.19.4 → 3.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -11
  3. data/README.md +9 -3
  4. data/doc/advantages.md +1 -1
  5. data/doc/carrierwave.md +4 -4
  6. data/doc/creating_persistence_plugins.md +172 -0
  7. data/doc/creating_plugins.md +1 -1
  8. data/doc/creating_storages.md +3 -1
  9. data/doc/design.md +2 -2
  10. data/doc/direct_s3.md +0 -22
  11. data/doc/paperclip.md +3 -3
  12. data/doc/plugins/activerecord.md +211 -42
  13. data/doc/plugins/atomic_helpers.md +153 -0
  14. data/doc/plugins/column.md +90 -0
  15. data/doc/plugins/derivation_endpoint.md +54 -62
  16. data/doc/plugins/derivatives.md +752 -0
  17. data/doc/plugins/entity.md +204 -0
  18. data/doc/plugins/infer_extension.md +8 -8
  19. data/doc/plugins/instrumentation.md +33 -13
  20. data/doc/plugins/keep_files.md +5 -15
  21. data/doc/plugins/model.md +157 -0
  22. data/doc/plugins/presign_endpoint.md +2 -1
  23. data/doc/plugins/refresh_metadata.md +44 -7
  24. data/doc/plugins/sequel.md +190 -33
  25. data/doc/plugins/{default_url_options.md → url_options.md} +5 -5
  26. data/doc/processing.md +1 -1
  27. data/doc/release_notes/1.1.0.md +2 -2
  28. data/doc/release_notes/2.15.0.md +1 -1
  29. data/doc/storage/s3.md +2 -2
  30. data/doc/testing.md +1 -1
  31. data/lib/shrine.rb +72 -138
  32. data/lib/shrine/attacher.rb +272 -176
  33. data/lib/shrine/attachment.rb +2 -42
  34. data/lib/shrine/plugins/activerecord.rb +103 -26
  35. data/lib/shrine/plugins/add_metadata.rb +9 -10
  36. data/lib/shrine/plugins/atomic_helpers.rb +111 -0
  37. data/lib/shrine/plugins/attacher_options.rb +55 -0
  38. data/lib/shrine/plugins/backgrounding.rb +147 -115
  39. data/lib/shrine/plugins/cached_attachment_data.rb +6 -9
  40. data/lib/shrine/plugins/column.rb +104 -0
  41. data/lib/shrine/plugins/data_uri.rb +35 -38
  42. data/lib/shrine/plugins/default_storage.rb +18 -12
  43. data/lib/shrine/plugins/default_url.rb +11 -21
  44. data/lib/shrine/plugins/default_url_options.rb +3 -30
  45. data/lib/shrine/plugins/delete_raw.rb +9 -13
  46. data/lib/shrine/plugins/derivation_endpoint.rb +75 -114
  47. data/lib/shrine/plugins/derivatives.rb +576 -0
  48. data/lib/shrine/plugins/determine_mime_type.rb +3 -15
  49. data/lib/shrine/plugins/download_endpoint.rb +83 -131
  50. data/lib/shrine/plugins/dynamic_storage.rb +4 -8
  51. data/lib/shrine/plugins/entity.rb +128 -0
  52. data/lib/shrine/plugins/form_assign.rb +107 -0
  53. data/lib/shrine/plugins/included.rb +4 -3
  54. data/lib/shrine/plugins/infer_extension.rb +10 -17
  55. data/lib/shrine/plugins/instrumentation.rb +45 -25
  56. data/lib/shrine/plugins/keep_files.rb +2 -12
  57. data/lib/shrine/plugins/metadata_attributes.rb +15 -14
  58. data/lib/shrine/plugins/model.rb +137 -0
  59. data/lib/shrine/plugins/module_include.rb +2 -0
  60. data/lib/shrine/plugins/presign_endpoint.rb +1 -15
  61. data/lib/shrine/plugins/pretty_location.rb +5 -5
  62. data/lib/shrine/plugins/processing.rb +21 -6
  63. data/lib/shrine/plugins/rack_file.rb +1 -39
  64. data/lib/shrine/plugins/rack_response.rb +14 -7
  65. data/lib/shrine/plugins/recache.rb +5 -2
  66. data/lib/shrine/plugins/refresh_metadata.rb +12 -8
  67. data/lib/shrine/plugins/remote_url.rb +44 -53
  68. data/lib/shrine/plugins/remove_attachment.rb +7 -2
  69. data/lib/shrine/plugins/remove_invalid.rb +8 -4
  70. data/lib/shrine/plugins/restore_cached_data.rb +12 -4
  71. data/lib/shrine/plugins/sequel.rb +115 -27
  72. data/lib/shrine/plugins/signature.rb +2 -7
  73. data/lib/shrine/plugins/store_dimensions.rb +13 -27
  74. data/lib/shrine/plugins/upload_endpoint.rb +14 -15
  75. data/lib/shrine/plugins/upload_options.rb +9 -8
  76. data/lib/shrine/plugins/url_options.rb +33 -0
  77. data/lib/shrine/plugins/validation.rb +87 -0
  78. data/lib/shrine/plugins/validation_helpers.rb +33 -54
  79. data/lib/shrine/plugins/versions.rb +106 -84
  80. data/lib/shrine/storage/file_system.rb +32 -57
  81. data/lib/shrine/storage/linter.rb +9 -1
  82. data/lib/shrine/storage/memory.rb +42 -0
  83. data/lib/shrine/storage/s3.rb +38 -146
  84. data/lib/shrine/uploaded_file.rb +22 -29
  85. data/lib/shrine/version.rb +4 -4
  86. data/shrine.gemspec +2 -3
  87. metadata +27 -54
  88. data/doc/plugins/backup.md +0 -31
  89. data/doc/plugins/copy.md +0 -24
  90. data/doc/plugins/delete_promoted.md +0 -12
  91. data/doc/plugins/direct_upload.md +0 -172
  92. data/doc/plugins/hooks.md +0 -58
  93. data/doc/plugins/logging.md +0 -42
  94. data/doc/plugins/migration_helpers.md +0 -60
  95. data/doc/plugins/moving.md +0 -19
  96. data/doc/plugins/multi_delete.md +0 -20
  97. data/doc/plugins/parallelize.md +0 -16
  98. data/doc/plugins/parsed_json.md +0 -23
  99. data/lib/shrine/plugins/background_helpers.rb +0 -5
  100. data/lib/shrine/plugins/backup.rb +0 -90
  101. data/lib/shrine/plugins/copy.rb +0 -50
  102. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  103. data/lib/shrine/plugins/direct_upload.rb +0 -217
  104. data/lib/shrine/plugins/hooks.rb +0 -90
  105. data/lib/shrine/plugins/logging.rb +0 -142
  106. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  107. data/lib/shrine/plugins/moving.rb +0 -57
  108. data/lib/shrine/plugins/multi_delete.rb +0 -32
  109. data/lib/shrine/plugins/parallelize.rb +0 -78
  110. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -2,146 +2,178 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/backgrounding.md] on GitHub.
5
+ # The backgrounding plugin allows delaying promotion and deletion into a
6
+ # background job.
6
7
  #
7
- # [doc/plugins/backgrounding.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/backgrounding.md
8
+ # You can register promotion and deletion blocks on an instance of the
9
+ # attacher, and they will be called as needed.
10
+ #
11
+ # ## Promotion
12
+ #
13
+ # attacher.promote_block do
14
+ # Attachment::PromoteJob.perform_async(record, name, data)
15
+ # end
16
+ #
17
+ # attacher.assign(io)
18
+ # attacher.finalize # promote block called
19
+ #
20
+ # attacher.file # cached file
21
+ # # ... background job finishes ...
22
+ # attacher.file # stored file
23
+ #
24
+ # The promote worker can be implemented like this:
25
+ #
26
+ # class Attachment::PromoteJob
27
+ # def perform(record, name, data)
28
+ # attacher = Shrine::Attacher.retrieve(model: record, name: name, data: data)
29
+ # attacher.atomic_promote
30
+ # end
31
+ # end
32
+ #
33
+ # ## Deletion
34
+ #
35
+ # attacher.destroy_block do
36
+ # Attachment::DestroyJob.perform_async(data)
37
+ # end
38
+ #
39
+ # previous_file = attacher.file
40
+ #
41
+ # attacher.attach(io)
42
+ # attacher.finalize # delete hook called
43
+ #
44
+ # previous_file.exists? #=> true
45
+ # # ... background job finishes ...
46
+ # previous_file.exists? #=> false
47
+ #
48
+ # attacher.destroy_attached
49
+ #
50
+ # attacher.file.exists? #=> true
51
+ # # ... background job finishes ...
52
+ # attacher.file.exists? #=> false
53
+ #
54
+ # The delete worker can be implemented like this:
55
+ #
56
+ # class Attachment::DestroyJob
57
+ # def perform(data)
58
+ # attacher = Shrine::Attacher.from_data(data)
59
+ # attacher.destroy
60
+ # end
61
+ # end
62
+ #
63
+ # ## Global hooks
64
+ #
65
+ # You can also register promotion and deletion hooks globally:
66
+ #
67
+ # Shrine::Attacher.promote_block do
68
+ # Attachment::PromoteJob.perform_async(record, name, data)
69
+ # end
70
+ #
71
+ # Shrine::Attacher.destroy_block do
72
+ # Attachment::DestroyJob.perform_async(data)
73
+ # end
8
74
  module Backgrounding
9
- module AttacherClassMethods
10
- # If block is passed in, stores it to be called on promotion. Otherwise
11
- # resolves data into objects and calls `Attacher#promote`.
12
- def promote(data = nil, &block)
13
- if block
14
- shrine_class.opts[:backgrounding_promote] = block
15
- else
16
- attacher = load(data)
17
- cached_file = attacher.uploaded_file(data["attachment"])
18
- action = data["action"].to_sym if data["action"]
19
-
20
- return if cached_file != attacher.get
21
- attacher.promote(cached_file, action: action) or return
75
+ def self.configure(uploader)
76
+ uploader.opts[:backgrounding] ||= {}
77
+ end
22
78
 
23
- attacher
24
- end
79
+ module AttacherClassMethods
80
+ # Registers a global promotion block.
81
+ #
82
+ # Shrine::Attacher.promote_block do |attacher|
83
+ # Attachment::PromoteJob.perform_async(
84
+ # attacher.record,
85
+ # attacher.name,
86
+ # attacher.data,
87
+ # )
88
+ # end
89
+ def promote_block(&block)
90
+ shrine_class.opts[:backgrounding][:promote_block] = block if block
91
+ shrine_class.opts[:backgrounding][:promote_block]
25
92
  end
26
93
 
27
- # If block is passed in, stores it to be called on deletion. Otherwise
28
- # resolves data into objects and calls `Shrine#delete`.
29
- def delete(data = nil, &block)
30
- if block
31
- shrine_class.opts[:backgrounding_delete] = block
32
- else
33
- attacher = load(data)
34
- uploaded_file = attacher.uploaded_file(data["attachment"])
35
- action = data["action"].to_sym if data["action"]
36
-
37
- attacher.delete!(uploaded_file, action: action)
38
-
39
- attacher
40
- end
94
+ # Registers a global deletion block.
95
+ #
96
+ # Shrine::Attacher.destroy_block do |attacher|
97
+ # Attachment::DeleteJob.perform_async(attacher.data)
98
+ # end
99
+ def destroy_block(&block)
100
+ shrine_class.opts[:backgrounding][:destroy_block] = block if block
101
+ shrine_class.opts[:backgrounding][:destroy_block]
41
102
  end
103
+ end
42
104
 
43
- # Delegates to `Attacher#dump`.
44
- def dump(attacher)
45
- attacher.dump
105
+ module AttacherMethods
106
+ # Inherits global hooks if defined.
107
+ def initialize(*args)
108
+ super
109
+ @destroy_block = self.class.destroy_block
110
+ @promote_block = self.class.promote_block
46
111
  end
47
112
 
48
- # Loads the data created by #dump, resolving the record and returning
49
- # the attacher.
50
- def load(data)
51
- record = load_record(data)
52
- name = data["name"].to_sym
53
-
54
- if record.respond_to?(:"#{name}_attacher")
55
- attacher = record.send(:"#{name}_attacher")
56
- elsif data["shrine_class"]
57
- shrine_class = Object.const_get(data["shrine_class"])
58
- attacher = shrine_class::Attacher.new(record, name)
59
- else
60
- fail Error, "cannot load anonymous uploader class"
61
- end
113
+ # Registers an instance-level promotion hook.
114
+ #
115
+ # attacher.promote_block do |attacher|
116
+ # Attachment::PromoteJob.perform_async(
117
+ # attacher.record,
118
+ # attacher.name
119
+ # attacher.data,
120
+ # )
121
+ # end
122
+ def promote_block(&block)
123
+ @promote_block = block if block
124
+ @promote_block
125
+ end
62
126
 
63
- attacher
127
+ # Registers an instance-level deletion hook.
128
+ #
129
+ # attacher.destroy_block do |attacher|
130
+ # Attachment::DeleteJob.perform_async(attacher.data)
131
+ # end
132
+ def destroy_block(&block)
133
+ @destroy_block = block if block
134
+ @destroy_block
64
135
  end
65
136
 
66
- # Resolves the record from backgrounding data. If the record was found,
67
- # returns it. If the record wasn't found, returns an instance of the
68
- # model with ID assigned for logging. If `find_record` isn't defined,
69
- # then it is a PORO model and should be instantiated with the cached
70
- # attachment.
71
- def load_record(data)
72
- record_class, record_id = data["record"]
73
- record_class = Object.const_get(record_class)
137
+ # Signals the #promote method that it should use backgrounding if
138
+ # registered.
139
+ def promote_cached(**options)
140
+ super(background: true, **options)
141
+ end
74
142
 
75
- if respond_to?(:find_record)
76
- record = find_record(record_class, record_id)
77
- record ||= record_class.new.tap do |instance|
78
- # so that the id is always included in file deletion logs
79
- instance.singleton_class.send(:define_method, :id) { record_id }
80
- end
143
+ # Calls the promotion hook if registered and called via #promote_cached,
144
+ # otherwise promotes synchronously.
145
+ def promote(background: false, **options)
146
+ if promote_block && background
147
+ background_block(promote_block, **options)
81
148
  else
82
- record = record_class.new
83
- record.send(:"#{data["name"]}_data=", data["attachment"])
149
+ super(**options)
84
150
  end
85
-
86
- record
87
151
  end
88
- end
89
152
 
90
- module AttacherMethods
91
- # Calls the promoting block (if registered) with a serializable data
92
- # hash.
93
- def _promote(uploaded_file = get, phase: nil, action: phase)
94
- if background_promote = shrine_class.opts[:backgrounding_promote]
95
- data = self.class.dump(self).merge(
96
- "attachment" => uploaded_file.to_json,
97
- "action" => (action.to_s if action),
98
- "phase" => (action.to_s if action), # legacy
99
- )
100
- instance_exec(data, &background_promote)
101
- else
102
- super
103
- end
153
+ # Signals the #destroy method that it should use backgrounding if
154
+ # registered.
155
+ def destroy_attached(**options)
156
+ super(background: true, **options)
104
157
  end
105
158
 
106
- # Calls the deleting block (if registered) with a serializable data
107
- # hash.
108
- def _delete(uploaded_file, phase: nil, action: phase)
109
- if background_delete = shrine_class.opts[:backgrounding_delete]
110
- data = self.class.dump(self).merge(
111
- "attachment" => uploaded_file.to_json,
112
- "action" => (action.to_s if action),
113
- "phase" => (action.to_s if action), # legacy
114
- )
115
- instance_exec(data, &background_delete)
116
- uploaded_file
159
+ # Calls the destroy hook if registered and called via #destroy_attached,
160
+ # otherwise destroys synchronously.
161
+ def destroy(background: false, **options)
162
+ if destroy_block && background
163
+ background_block(destroy_block, **options)
117
164
  else
118
- super
165
+ super(**options)
119
166
  end
120
167
  end
121
168
 
122
- # Dumps all the information about the attacher in a serializable hash
123
- # suitable for passing as an argument to background jobs.
124
- def dump
125
- {
126
- "attachment" => (get && get.to_json),
127
- "record" => [record.class.to_s, record.id.to_s],
128
- "name" => name.to_s,
129
- "shrine_class" => shrine_class.name,
130
- }
131
- end
132
-
133
- # Updates with the new file only if the attachment hasn't changed.
134
- def swap(new_file)
135
- if self.class.respond_to?(:find_record)
136
- reloaded = self.class.find_record(record.class, record.id)
137
- return if reloaded.nil?
138
-
139
- attacher = reloaded.send(:"#{name}_attacher") if reloaded.respond_to?(:"#{name}_attacher")
140
- attacher ||= self.class.new(reloaded, name) # Shrine::Attachment is not used
169
+ private
141
170
 
142
- return if attacher.get != self.get
171
+ def background_block(block, **options)
172
+ if block.arity == 1
173
+ block.call(self, **options)
174
+ else
175
+ instance_exec(**options, &block)
143
176
  end
144
- super
145
177
  end
146
178
  end
147
179
  end
@@ -7,25 +7,22 @@ class Shrine
7
7
  # [doc/plugins/cached_attachment_data.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/cached_attachment_data.md
8
8
  module CachedAttachmentData
9
9
  module AttachmentMethods
10
- def initialize(*)
10
+ def included(klass)
11
11
  super
12
12
 
13
+ return unless options[:type] == :model
14
+
13
15
  name = attachment_name
14
16
 
15
17
  define_method :"cached_#{name}_data" do
16
- send(:"#{name}_attacher").read_cached
17
- end
18
-
19
- define_method :"cached_#{name}_data=" do |value|
20
- Shrine.deprecation("Calling #cached_#{name}_data= is deprecated and will be removed in Shrine 3. You should use the original field name: `f.hidden_field :#{name}, value: record.cached_#{name}_data`.")
21
- send(:"#{name}_attacher").assign(value)
18
+ send(:"#{name}_attacher").cached_data
22
19
  end
23
20
  end
24
21
  end
25
22
 
26
23
  module AttacherMethods
27
- def read_cached
28
- get.to_json if cached? && changed?
24
+ def cached_data
25
+ file.to_json if cached? && changed?
29
26
  end
30
27
  end
31
28
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ class Shrine
6
+ module Plugins
7
+ # Documentation lives in [doc/plugins/column.md] on GitHub.
8
+ #
9
+ # [doc/plugins/column.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/column.md
10
+ module Column
11
+ def self.configure(uploader, **opts)
12
+ uploader.opts[:column] ||= { serializer: JsonSerializer.new(JSON) }
13
+ uploader.opts[:column].merge!(opts)
14
+ end
15
+
16
+ module AttacherClassMethods
17
+ # Initializes the attacher from a data hash/string expected to come
18
+ # from a database record column.
19
+ #
20
+ # Attacher.from_column('{"id":"...","storage":"...","metadata":{...}}')
21
+ def from_column(data, **options)
22
+ attacher = new(**options)
23
+ attacher.load_column(data)
24
+ attacher
25
+ end
26
+ end
27
+
28
+ module AttacherMethods
29
+ # Column serializer object.
30
+ attr_reader :column_serializer
31
+
32
+ # Allows overriding the default column serializer.
33
+ def initialize(column_serializer: shrine_class.opts[:column][:serializer], **options)
34
+ super(**options)
35
+ @column_serializer = column_serializer
36
+ end
37
+
38
+ # Loads attachment from column data.
39
+ #
40
+ # attacher.file #=> nil
41
+ # attacher.load_column('{"id":"...","storage":"...","metadata":{...}}')
42
+ # attacher.file #=> #<Shrine::UploadedFile>
43
+ def load_column(data)
44
+ load_data(deserialize_column(data))
45
+ end
46
+
47
+ # Returns attacher data as a serialized string (JSON by default).
48
+ #
49
+ # attacher.column_data #=> '{"id":"...","storage":"...","metadata":{...}}'
50
+ def column_data
51
+ serialize_column(data)
52
+ end
53
+
54
+ private
55
+
56
+ # Converts the column data hash into a string (generates JSON by
57
+ # default).
58
+ #
59
+ # Attacher.serialize_column({ "id" => "...", "storage" => "...", "metadata" => { ... } })
60
+ # #=> '{"id":"...","storage":"...","metadata":{...}}'
61
+ #
62
+ # Attacher.serialize_column(nil)
63
+ # #=> nil
64
+ def serialize_column(data)
65
+ return data unless column_serializer && data
66
+
67
+ column_serializer.dump(data)
68
+ end
69
+
70
+ # Converts the column data string into a hash (parses JSON by default).
71
+ #
72
+ # Attacher.deserialize_column('{"id":"...","storage":"...","metadata":{...}}')
73
+ # #=> { "id" => "...", "storage" => "...", "metadata" => { ... } }
74
+ #
75
+ # Attacher.deserialize_column(nil)
76
+ # #=> nil
77
+ def deserialize_column(data)
78
+ return data unless column_serializer && data
79
+
80
+ column_serializer.load(data)
81
+ end
82
+ end
83
+
84
+ # JSON.dump and JSON.load shouldn't be used with untrusted input, so we
85
+ # create this wrapper class which calls JSON.generate and JSON.parse
86
+ # instead.
87
+ class JsonSerializer
88
+ def initialize(json)
89
+ @json = json
90
+ end
91
+
92
+ def dump(data)
93
+ @json.generate(data)
94
+ end
95
+
96
+ def load(data)
97
+ @json.parse(data)
98
+ end
99
+ end
100
+ end
101
+
102
+ register_plugin(:column, Column)
103
+ end
104
+ end
@@ -26,17 +26,33 @@ class Shrine
26
26
  }.inspect}"
27
27
  end
28
28
 
29
- def self.configure(uploader, opts = {})
30
- uploader.opts[:data_uri] ||= { log_subscriber: LOG_SUBSCRIBER }
31
- uploader.opts[:data_uri].merge!(opts)
29
+ def self.load_dependencies(uploader, *)
30
+ uploader.plugin :validation
31
+ end
32
32
 
33
- if uploader.opts[:data_uri][:filename]
34
- Shrine.deprecation("The :filename option is deprecated for the data_uri plugin, and will be removed in Shrine 3. Use the infer_extension plugin instead.")
35
- end
33
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
34
+ uploader.opts[:data_uri] ||= {}
35
+ uploader.opts[:data_uri].merge!(opts)
36
36
 
37
37
  # instrumentation plugin integration
38
- if uploader.respond_to?(:subscribe)
39
- uploader.subscribe(:data_uri, &uploader.opts[:data_uri][:log_subscriber])
38
+ uploader.subscribe(:data_uri, &log_subscriber) if uploader.respond_to?(:subscribe)
39
+ end
40
+
41
+ module AttachmentMethods
42
+ def included(klass)
43
+ super
44
+
45
+ return unless options[:type] == :model
46
+
47
+ name = attachment_name
48
+
49
+ define_method :"#{name}_data_uri=" do |uri|
50
+ send(:"#{name}_attacher").assign_data_uri(uri)
51
+ end
52
+
53
+ define_method :"#{name}_data_uri" do
54
+ # form builders require the reader method
55
+ end
40
56
  end
41
57
  end
42
58
 
@@ -57,10 +73,10 @@ class Shrine
57
73
 
58
74
  private
59
75
 
76
+ # Creates an IO-like object from parsed data URI.
60
77
  def create_data_file(info, filename: nil)
61
78
  content_type = info[:content_type] || DEFAULT_CONTENT_TYPE
62
79
  content = info[:base64] ? Base64.decode64(info[:data]) : CGI.unescape(info[:data])
63
- filename = opts[:data_uri][:filename].call(content_type) if opts[:data_uri][:filename]
64
80
 
65
81
  data_file = Shrine::DataFile.new(content, content_type: content_type, filename: filename)
66
82
  info[:data].clear
@@ -68,6 +84,7 @@ class Shrine
68
84
  data_file
69
85
  end
70
86
 
87
+ # Parses the data URI string and returns parts.
71
88
  def parse_data_uri(uri)
72
89
  scanner = StringScanner.new(uri)
73
90
  scanner.scan(DATA_REGEXP) or raise ParseError, "data URI has invalid format"
@@ -87,20 +104,6 @@ class Shrine
87
104
  end
88
105
  end
89
106
 
90
- module AttachmentMethods
91
- def initialize(name, **options)
92
- super
93
-
94
- define_method :"#{name}_data_uri=" do |uri|
95
- send(:"#{name}_attacher").data_uri = uri
96
- end
97
-
98
- define_method :"#{name}_data_uri" do
99
- send(:"#{name}_attacher").data_uri
100
- end
101
- end
102
- end
103
-
104
107
  module AttacherMethods
105
108
  # Handles assignment of a data URI. If the regexp matches, it extracts
106
109
  # the content type, decodes it, wrappes it in a StringIO and assigns it.
@@ -110,22 +113,19 @@ class Shrine
110
113
  return if uri == "" || uri.nil?
111
114
 
112
115
  data_file = shrine_class.data_uri(uri)
113
- assign(data_file, **options)
116
+ attach_cached(data_file, **options)
114
117
  rescue ParseError => error
115
- message = shrine_class.opts[:data_uri][:error_message] || error.message
116
- message = message.call(uri) if message.respond_to?(:call)
117
- errors.replace [message]
118
- @data_uri = uri
118
+ errors.clear << data_uri_error_messsage(uri, error)
119
+ false
119
120
  end
120
121
 
121
- # Alias for #assign_data_uri.
122
- def data_uri=(uri)
123
- assign_data_uri(uri)
124
- end
122
+ private
125
123
 
126
- # Form builders require the reader as well.
127
- def data_uri
128
- @data_uri
124
+ # Generates an error message for failed data URI parse.
125
+ def data_uri_error_messsage(uri, error)
126
+ message = shrine_class.opts[:data_uri][:error_message]
127
+ message = message.call(uri) if message.respond_to?(:call)
128
+ message || error.message
129
129
  end
130
130
  end
131
131
 
@@ -169,7 +169,4 @@ class Shrine
169
169
  @io.string.clear # deallocate string
170
170
  end
171
171
  end
172
-
173
- Plugins::DataUri.const_set(:DataFile, DataFile)
174
- Plugins::DataUri.deprecate_constant(:DataFile)
175
172
  end