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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +299 -11
- data/README.md +9 -3
- data/doc/advantages.md +1 -1
- data/doc/carrierwave.md +4 -4
- data/doc/creating_persistence_plugins.md +172 -0
- data/doc/creating_plugins.md +1 -1
- data/doc/creating_storages.md +3 -1
- data/doc/design.md +2 -2
- data/doc/direct_s3.md +0 -22
- data/doc/paperclip.md +3 -3
- data/doc/plugins/activerecord.md +211 -42
- data/doc/plugins/atomic_helpers.md +153 -0
- data/doc/plugins/column.md +90 -0
- data/doc/plugins/derivation_endpoint.md +54 -62
- data/doc/plugins/derivatives.md +752 -0
- data/doc/plugins/entity.md +204 -0
- data/doc/plugins/infer_extension.md +8 -8
- data/doc/plugins/instrumentation.md +33 -13
- data/doc/plugins/keep_files.md +5 -15
- data/doc/plugins/model.md +157 -0
- data/doc/plugins/presign_endpoint.md +2 -1
- data/doc/plugins/refresh_metadata.md +44 -7
- data/doc/plugins/sequel.md +190 -33
- data/doc/plugins/{default_url_options.md → url_options.md} +5 -5
- data/doc/processing.md +1 -1
- data/doc/release_notes/1.1.0.md +2 -2
- data/doc/release_notes/2.15.0.md +1 -1
- data/doc/storage/s3.md +2 -2
- data/doc/testing.md +1 -1
- data/lib/shrine.rb +72 -138
- data/lib/shrine/attacher.rb +272 -176
- data/lib/shrine/attachment.rb +2 -42
- data/lib/shrine/plugins/activerecord.rb +103 -26
- data/lib/shrine/plugins/add_metadata.rb +9 -10
- data/lib/shrine/plugins/atomic_helpers.rb +111 -0
- data/lib/shrine/plugins/attacher_options.rb +55 -0
- data/lib/shrine/plugins/backgrounding.rb +147 -115
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -9
- data/lib/shrine/plugins/column.rb +104 -0
- data/lib/shrine/plugins/data_uri.rb +35 -38
- data/lib/shrine/plugins/default_storage.rb +18 -12
- data/lib/shrine/plugins/default_url.rb +11 -21
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +9 -13
- data/lib/shrine/plugins/derivation_endpoint.rb +75 -114
- data/lib/shrine/plugins/derivatives.rb +576 -0
- data/lib/shrine/plugins/determine_mime_type.rb +3 -15
- data/lib/shrine/plugins/download_endpoint.rb +83 -131
- data/lib/shrine/plugins/dynamic_storage.rb +4 -8
- data/lib/shrine/plugins/entity.rb +128 -0
- data/lib/shrine/plugins/form_assign.rb +107 -0
- data/lib/shrine/plugins/included.rb +4 -3
- data/lib/shrine/plugins/infer_extension.rb +10 -17
- data/lib/shrine/plugins/instrumentation.rb +45 -25
- data/lib/shrine/plugins/keep_files.rb +2 -12
- data/lib/shrine/plugins/metadata_attributes.rb +15 -14
- data/lib/shrine/plugins/model.rb +137 -0
- data/lib/shrine/plugins/module_include.rb +2 -0
- data/lib/shrine/plugins/presign_endpoint.rb +1 -15
- data/lib/shrine/plugins/pretty_location.rb +5 -5
- data/lib/shrine/plugins/processing.rb +21 -6
- data/lib/shrine/plugins/rack_file.rb +1 -39
- data/lib/shrine/plugins/rack_response.rb +14 -7
- data/lib/shrine/plugins/recache.rb +5 -2
- data/lib/shrine/plugins/refresh_metadata.rb +12 -8
- data/lib/shrine/plugins/remote_url.rb +44 -53
- data/lib/shrine/plugins/remove_attachment.rb +7 -2
- data/lib/shrine/plugins/remove_invalid.rb +8 -4
- data/lib/shrine/plugins/restore_cached_data.rb +12 -4
- data/lib/shrine/plugins/sequel.rb +115 -27
- data/lib/shrine/plugins/signature.rb +2 -7
- data/lib/shrine/plugins/store_dimensions.rb +13 -27
- data/lib/shrine/plugins/upload_endpoint.rb +14 -15
- data/lib/shrine/plugins/upload_options.rb +9 -8
- data/lib/shrine/plugins/url_options.rb +33 -0
- data/lib/shrine/plugins/validation.rb +87 -0
- data/lib/shrine/plugins/validation_helpers.rb +33 -54
- data/lib/shrine/plugins/versions.rb +106 -84
- data/lib/shrine/storage/file_system.rb +32 -57
- data/lib/shrine/storage/linter.rb +9 -1
- data/lib/shrine/storage/memory.rb +42 -0
- data/lib/shrine/storage/s3.rb +38 -146
- data/lib/shrine/uploaded_file.rb +22 -29
- data/lib/shrine/version.rb +4 -4
- data/shrine.gemspec +2 -3
- metadata +27 -54
- data/doc/plugins/backup.md +0 -31
- data/doc/plugins/copy.md +0 -24
- data/doc/plugins/delete_promoted.md +0 -12
- data/doc/plugins/direct_upload.md +0 -172
- data/doc/plugins/hooks.md +0 -58
- data/doc/plugins/logging.md +0 -42
- data/doc/plugins/migration_helpers.md +0 -60
- data/doc/plugins/moving.md +0 -19
- data/doc/plugins/multi_delete.md +0 -20
- data/doc/plugins/parallelize.md +0 -16
- data/doc/plugins/parsed_json.md +0 -23
- data/lib/shrine/plugins/background_helpers.rb +0 -5
- data/lib/shrine/plugins/backup.rb +0 -90
- data/lib/shrine/plugins/copy.rb +0 -50
- data/lib/shrine/plugins/delete_promoted.rb +0 -20
- data/lib/shrine/plugins/direct_upload.rb +0 -217
- data/lib/shrine/plugins/hooks.rb +0 -90
- data/lib/shrine/plugins/logging.rb +0 -142
- data/lib/shrine/plugins/migration_helpers.rb +0 -70
- data/lib/shrine/plugins/moving.rb +0 -57
- data/lib/shrine/plugins/multi_delete.rb +0 -32
- data/lib/shrine/plugins/parallelize.rb +0 -78
- data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -2,146 +2,178 @@
|
|
2
2
|
|
3
3
|
class Shrine
|
4
4
|
module Plugins
|
5
|
-
#
|
5
|
+
# The backgrounding plugin allows delaying promotion and deletion into a
|
6
|
+
# background job.
|
6
7
|
#
|
7
|
-
#
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
24
|
-
|
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
|
-
#
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
#
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
91
|
-
#
|
92
|
-
|
93
|
-
|
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
|
107
|
-
#
|
108
|
-
def
|
109
|
-
if
|
110
|
-
|
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
|
-
|
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
|
-
|
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
|
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").
|
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
|
28
|
-
|
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.
|
30
|
-
uploader.
|
31
|
-
|
29
|
+
def self.load_dependencies(uploader, *)
|
30
|
+
uploader.plugin :validation
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
-
|
116
|
+
attach_cached(data_file, **options)
|
114
117
|
rescue ParseError => error
|
115
|
-
|
116
|
-
|
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
|
-
|
122
|
-
def data_uri=(uri)
|
123
|
-
assign_data_uri(uri)
|
124
|
-
end
|
122
|
+
private
|
125
123
|
|
126
|
-
#
|
127
|
-
def
|
128
|
-
|
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
|