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
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shrine
|
4
|
+
module Plugins
|
5
|
+
module FormAssign
|
6
|
+
def self.load_dependencies(uploader)
|
7
|
+
uploader.plugin :entity
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.configure(uploader, **opts)
|
11
|
+
uploader.opts[:form_assign] ||= { result: :params }
|
12
|
+
uploader.opts[:form_assign].merge!(opts)
|
13
|
+
end
|
14
|
+
|
15
|
+
module AttacherMethods
|
16
|
+
# Helper for setting the attachment from form fields. Returns normalized
|
17
|
+
# fields.
|
18
|
+
#
|
19
|
+
# attacher = Shrine::Attacher.from_entity(photo, :image)
|
20
|
+
#
|
21
|
+
# attacher.form_assign({ image: file, title: "Title" })
|
22
|
+
# #=> { image: '{...}', title: "Title" }
|
23
|
+
#
|
24
|
+
# attacher.form_assign({ image: "", image_remote_url: "...", title: "Title" })
|
25
|
+
# #=> { image: '{...}', title: "Title" }
|
26
|
+
#
|
27
|
+
# attacher.form_assign({ image: "", title: "Title" })
|
28
|
+
# #=> { title: "Title" }
|
29
|
+
#
|
30
|
+
# You can also return the result in form of attributes to be used for
|
31
|
+
# database record creation.
|
32
|
+
#
|
33
|
+
# attacher.form_assign({ image: file, title: "Title" }, result: :attributes)
|
34
|
+
# #=> { image_data: '{...}', title: "Title" }
|
35
|
+
def form_assign(fields, result: shrine_class.opts[:form_assign][:result])
|
36
|
+
form = create_form_object
|
37
|
+
fields = form_write(form, fields)
|
38
|
+
|
39
|
+
form_attach(form)
|
40
|
+
|
41
|
+
form_result(fields, result)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Assigns form params to the form object using Shrine's attachment
|
47
|
+
# writers.
|
48
|
+
def form_write(form, fields)
|
49
|
+
result = fields.dup
|
50
|
+
|
51
|
+
fields.each do |key, value|
|
52
|
+
if form.respond_to?(:"#{key}=")
|
53
|
+
form.send(:"#{key}=", value)
|
54
|
+
|
55
|
+
result.delete(key)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
# Attaches the file from the form object if atachment has changed.
|
63
|
+
def form_attach(form)
|
64
|
+
return unless form.send(:"#{name}_attacher").changed?
|
65
|
+
|
66
|
+
file = form.send(:"#{name}_attacher").file
|
67
|
+
|
68
|
+
if file
|
69
|
+
change uploaded_file(file.data) # use our UploadedFile class
|
70
|
+
else
|
71
|
+
change nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds attached file data to the fields if attachment has changed.
|
76
|
+
def form_result(fields, result_type)
|
77
|
+
return fields unless changed?
|
78
|
+
|
79
|
+
case result_type
|
80
|
+
when :params then fields[name] = file&.to_json
|
81
|
+
when :attributes then fields[:"#{name}_data"] = column_data
|
82
|
+
else
|
83
|
+
fail ArgumentError, "unrecognized result type: #{result_type.inspect}"
|
84
|
+
end
|
85
|
+
|
86
|
+
fields
|
87
|
+
end
|
88
|
+
|
89
|
+
# Creates a disposable form object with model plugin loaded.
|
90
|
+
def create_form_object
|
91
|
+
# load the model plugin into a disposable Shrine subclass
|
92
|
+
shrine_subclass = Class.new(shrine_class)
|
93
|
+
shrine_subclass.plugin :model
|
94
|
+
|
95
|
+
# create a model class with attachment methods
|
96
|
+
form_class = Struct.new(:"#{name}_data")
|
97
|
+
form_class.include shrine_subclass::Attachment(name)
|
98
|
+
|
99
|
+
# instantiate form object
|
100
|
+
form_class.new(column_data)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
register_plugin(:form_assign, FormAssign)
|
106
|
+
end
|
107
|
+
end
|
@@ -7,13 +7,14 @@ class Shrine
|
|
7
7
|
# [doc/plugins/included.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/included.md
|
8
8
|
module Included
|
9
9
|
def self.configure(uploader, &block)
|
10
|
-
uploader.opts[:
|
10
|
+
uploader.opts[:included] ||= {}
|
11
|
+
uploader.opts[:included][:block] = block
|
11
12
|
end
|
12
13
|
|
13
14
|
module AttachmentMethods
|
14
|
-
def included(
|
15
|
+
def included(klass)
|
15
16
|
super
|
16
|
-
|
17
|
+
klass.instance_exec(@name, &shrine_class.opts[:included][:block])
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "pathname"
|
4
|
+
|
3
5
|
class Shrine
|
4
6
|
module Plugins
|
5
7
|
# Documentation lives in [doc/plugins/infer_extension.md] on GitHub.
|
@@ -13,14 +15,12 @@ class Shrine
|
|
13
15
|
}.inspect}"
|
14
16
|
end
|
15
17
|
|
16
|
-
def self.configure(uploader,
|
17
|
-
uploader.opts[:infer_extension] ||= { inferrer: :
|
18
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
19
|
+
uploader.opts[:infer_extension] ||= { inferrer: :mini_mime }
|
18
20
|
uploader.opts[:infer_extension].merge!(opts)
|
19
21
|
|
20
22
|
# instrumentation plugin integration
|
21
|
-
if uploader.respond_to?(:subscribe)
|
22
|
-
uploader.subscribe(:extension, &uploader.opts[:infer_extension][:log_subscriber])
|
23
|
-
end
|
23
|
+
uploader.subscribe(:extension, &log_subscriber) if uploader.respond_to?(:subscribe)
|
24
24
|
end
|
25
25
|
|
26
26
|
module ClassMethods
|
@@ -54,21 +54,14 @@ class Shrine
|
|
54
54
|
|
55
55
|
module InstanceMethods
|
56
56
|
def basic_location(io, metadata:)
|
57
|
-
location = super
|
58
|
-
current_extension = File.extname(location)
|
57
|
+
location = Pathname(super)
|
59
58
|
|
60
|
-
if
|
61
|
-
inferred_extension = infer_extension(metadata["mime_type"])
|
62
|
-
location = location.
|
59
|
+
if location.extname.empty? || opts[:infer_extension][:force]
|
60
|
+
inferred_extension = self.class.infer_extension(metadata["mime_type"])
|
61
|
+
location = location.sub_ext(inferred_extension) if inferred_extension
|
63
62
|
end
|
64
63
|
|
65
|
-
location
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def infer_extension(mime_type)
|
71
|
-
self.class.infer_extension(mime_type).to_s
|
64
|
+
location.to_s
|
72
65
|
end
|
73
66
|
end
|
74
67
|
|
@@ -6,21 +6,18 @@ class Shrine
|
|
6
6
|
#
|
7
7
|
# [doc/plugins/instrumentation.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/instrumentation.md
|
8
8
|
module Instrumentation
|
9
|
-
EVENTS = %i[upload download exists delete metadata].freeze
|
9
|
+
EVENTS = %i[upload download open exists delete metadata].freeze
|
10
10
|
|
11
11
|
# We use a proc in order to be able identify listeners.
|
12
12
|
LOG_SUBSCRIBER = -> (event) { LogSubscriber.call(event) }
|
13
13
|
|
14
|
-
def self.configure(uploader,
|
15
|
-
uploader.opts[:instrumentation] ||= {
|
14
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
15
|
+
uploader.opts[:instrumentation] ||= { log_events: EVENTS, subscribers: {} }
|
16
16
|
uploader.opts[:instrumentation].merge!(opts)
|
17
17
|
uploader.opts[:instrumentation][:notifications] ||= ::ActiveSupport::Notifications
|
18
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] = [] }
|
21
|
-
|
22
19
|
uploader.opts[:instrumentation][:log_events].each do |event_name|
|
23
|
-
uploader.subscribe(event_name, &
|
20
|
+
uploader.subscribe(event_name, &log_subscriber)
|
24
21
|
end
|
25
22
|
end
|
26
23
|
|
@@ -48,12 +45,13 @@ class Shrine
|
|
48
45
|
# end
|
49
46
|
def subscribe(event_name, &subscriber)
|
50
47
|
return if subscriber.nil?
|
51
|
-
return if subscribers[event_name]
|
48
|
+
return if subscribers[event_name]&.include?(subscriber)
|
52
49
|
|
53
50
|
notifications.subscribe("#{event_name}.shrine") do |event|
|
54
51
|
subscriber.call(event) if event[:uploader] <= self
|
55
52
|
end
|
56
53
|
|
54
|
+
subscribers[event_name] ||= []
|
57
55
|
subscribers[event_name] << subscriber
|
58
56
|
end
|
59
57
|
|
@@ -63,12 +61,8 @@ class Shrine
|
|
63
61
|
Notifications.new(opts[:instrumentation][:notifications])
|
64
62
|
end
|
65
63
|
|
66
|
-
def log_subscriber
|
67
|
-
opts[:instrumentation][:log_subscriber]
|
68
|
-
end
|
69
|
-
|
70
64
|
def subscribers
|
71
|
-
opts[:
|
65
|
+
opts[:instrumentation][:subscribers]
|
72
66
|
end
|
73
67
|
end
|
74
68
|
|
@@ -76,46 +70,49 @@ class Shrine
|
|
76
70
|
private
|
77
71
|
|
78
72
|
# Sends a `upload.shrine` event.
|
79
|
-
def
|
73
|
+
def _upload(io, location:, metadata:, upload_options: {}, **options)
|
80
74
|
self.class.instrument(
|
81
75
|
:upload,
|
82
76
|
storage: storage_key,
|
83
|
-
location:
|
77
|
+
location: location,
|
84
78
|
io: io,
|
85
|
-
upload_options:
|
86
|
-
|
79
|
+
upload_options: upload_options,
|
80
|
+
metadata: metadata,
|
81
|
+
options: options,
|
87
82
|
) { super }
|
88
83
|
end
|
89
84
|
|
90
85
|
# Sends a `metadata.shrine` event.
|
91
|
-
def get_metadata(io,
|
92
|
-
return super if io.is_a?(UploadedFile) &&
|
86
|
+
def get_metadata(io, metadata: nil, **options)
|
87
|
+
return super if io.is_a?(UploadedFile) && metadata != true || metadata == false
|
93
88
|
|
94
89
|
self.class.instrument(
|
95
90
|
:metadata,
|
96
91
|
storage: storage_key,
|
97
92
|
io: io,
|
98
|
-
options:
|
93
|
+
options: options,
|
99
94
|
) { super }
|
100
95
|
end
|
101
96
|
end
|
102
97
|
|
103
98
|
module FileMethods
|
104
99
|
# Sends a `download.shrine` event.
|
105
|
-
def
|
100
|
+
def stream(destination, **options)
|
101
|
+
return super if opened?
|
102
|
+
|
106
103
|
shrine_class.instrument(
|
107
104
|
:download,
|
108
|
-
storage: storage_key
|
105
|
+
storage: storage_key,
|
109
106
|
location: id,
|
110
107
|
download_options: options,
|
111
|
-
) { super }
|
108
|
+
) { super(destination, **options, instrument: false) }
|
112
109
|
end
|
113
110
|
|
114
111
|
# Sends a `exists.shrine` event.
|
115
112
|
def exists?
|
116
113
|
shrine_class.instrument(
|
117
114
|
:exists,
|
118
|
-
storage: storage_key
|
115
|
+
storage: storage_key,
|
119
116
|
location: id,
|
120
117
|
) { super }
|
121
118
|
end
|
@@ -124,10 +121,24 @@ class Shrine
|
|
124
121
|
def delete
|
125
122
|
shrine_class.instrument(
|
126
123
|
:delete,
|
127
|
-
storage: storage_key
|
124
|
+
storage: storage_key,
|
128
125
|
location: id,
|
129
126
|
) { super }
|
130
127
|
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# Sends an `open.shrine` event.
|
132
|
+
def _open(instrument: true, **options)
|
133
|
+
return super(**options) unless instrument
|
134
|
+
|
135
|
+
shrine_class.instrument(
|
136
|
+
:open,
|
137
|
+
storage: storage_key,
|
138
|
+
location: id,
|
139
|
+
download_options: options,
|
140
|
+
) { super(**options) }
|
141
|
+
end
|
131
142
|
end
|
132
143
|
|
133
144
|
# Abstracts away different types of notifications objects
|
@@ -255,6 +266,15 @@ class Shrine
|
|
255
266
|
)}"
|
256
267
|
end
|
257
268
|
|
269
|
+
def on_open(event)
|
270
|
+
log "Open (#{event.duration}ms) – #{format(
|
271
|
+
storage: event[:storage],
|
272
|
+
location: event[:location],
|
273
|
+
download_options: event[:download_options],
|
274
|
+
uploader: event[:uploader],
|
275
|
+
)}"
|
276
|
+
end
|
277
|
+
|
258
278
|
def on_exists(event)
|
259
279
|
log "Exists (#{event.duration}ms) – #{format(
|
260
280
|
storage: event[:storage],
|
@@ -6,19 +6,9 @@ class Shrine
|
|
6
6
|
#
|
7
7
|
# [doc/plugins/keep_files.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/keep_files.md
|
8
8
|
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
9
|
module AttacherMethods
|
16
|
-
def
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def destroy
|
21
|
-
super unless shrine_class.opts[:keep_files].include?(:destroyed)
|
10
|
+
def destroy_attached(*)
|
11
|
+
# don't delete files
|
22
12
|
end
|
23
13
|
end
|
24
14
|
end
|
@@ -6,33 +6,34 @@ class Shrine
|
|
6
6
|
#
|
7
7
|
# [doc/plugins/metadata_attributes.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/metadata_attributes.md
|
8
8
|
module MetadataAttributes
|
9
|
+
def self.load_dependencies(uploader, *)
|
10
|
+
uploader.plugin :entity
|
11
|
+
end
|
12
|
+
|
9
13
|
def self.configure(uploader, mappings = {})
|
10
|
-
uploader.opts[:
|
11
|
-
uploader.opts[:
|
14
|
+
uploader.opts[:metadata_attributes] ||= { mappings: {} }
|
15
|
+
uploader.opts[:metadata_attributes][:mappings].merge!(mappings)
|
12
16
|
end
|
13
17
|
|
14
18
|
module AttacherClassMethods
|
15
19
|
def metadata_attributes(mappings)
|
16
|
-
shrine_class.opts[:
|
20
|
+
shrine_class.opts[:metadata_attributes][:mappings].merge!(mappings)
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
20
24
|
module AttacherMethods
|
21
|
-
def
|
22
|
-
super
|
23
|
-
cached_file = get
|
25
|
+
def column_values
|
26
|
+
values = super
|
24
27
|
|
25
|
-
shrine_class.opts[:
|
26
|
-
|
28
|
+
shrine_class.opts[:metadata_attributes][:mappings].each do |source, destination|
|
29
|
+
metadata_attribute = destination.is_a?(Symbol) ? :"#{name}_#{destination}" : :"#{destination}"
|
27
30
|
|
28
|
-
next unless record.respond_to?(
|
31
|
+
next unless record.respond_to?(metadata_attribute)
|
29
32
|
|
30
|
-
|
31
|
-
record.send(:"#{attribute_name}=", cached_file.metadata[source.to_s])
|
32
|
-
else
|
33
|
-
record.send(:"#{attribute_name}=", nil)
|
34
|
-
end
|
33
|
+
values[metadata_attribute] = file && file.metadata[source.to_s]
|
35
34
|
end
|
35
|
+
|
36
|
+
values
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shrine
|
4
|
+
module Plugins
|
5
|
+
# Documentation lives in [doc/plugins/model.md] on GitHub.
|
6
|
+
#
|
7
|
+
# [doc/plugins/model.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/model.md
|
8
|
+
module Model
|
9
|
+
def self.load_dependencies(uploader, **)
|
10
|
+
uploader.plugin :entity
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.configure(uploader, **opts)
|
14
|
+
uploader.opts[:model] ||= { cache: true }
|
15
|
+
uploader.opts[:model].merge!(opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
module AttachmentMethods
|
19
|
+
# Allows specifying whether the attachment should be for a model
|
20
|
+
# (default) or an entity.
|
21
|
+
#
|
22
|
+
# Shrine::Attachment.new(:image) # model (default)
|
23
|
+
# Shrine::Attachment.new(:image, type: :model) # model
|
24
|
+
# Shrine::Attachment.new(:image, type: :entity) # entity
|
25
|
+
def initialize(name, **options)
|
26
|
+
super(name, type: :model, **options)
|
27
|
+
end
|
28
|
+
|
29
|
+
# We define the setter dynamically on inclusion to allow other plugins
|
30
|
+
# to still have time to override attachment type on inclusion.
|
31
|
+
def included(klass)
|
32
|
+
super
|
33
|
+
|
34
|
+
return unless options[:type] == :model
|
35
|
+
|
36
|
+
name = attachment_name
|
37
|
+
|
38
|
+
define_method :"#{name}=" do |value|
|
39
|
+
send(:"#{name}_attacher").model_assign(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
define_method :initialize_copy do |other|
|
43
|
+
super(other)
|
44
|
+
instance_variable_set(:"@#{name}_attacher", instance_variable_get(:"@#{name}_attacher")&.dup)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
private :initialize_copy
|
48
|
+
end
|
49
|
+
|
50
|
+
# Memoizes the attacher instance into an instance variable.
|
51
|
+
def attacher(record, options)
|
52
|
+
return super unless @options[:type] == :model
|
53
|
+
|
54
|
+
name = attachment_name
|
55
|
+
|
56
|
+
if !record.instance_variable_get(:"@#{name}_attacher") || options.any?
|
57
|
+
record.instance_variable_set(:"@#{name}_attacher", super)
|
58
|
+
else
|
59
|
+
record.instance_variable_get(:"@#{name}_attacher")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module AttacherClassMethods
|
65
|
+
# Initializes itself from a model instance and attachment name.
|
66
|
+
#
|
67
|
+
# photo.image_data #=> "{...}" # a file is attached
|
68
|
+
#
|
69
|
+
# attacher = Attacher.from_model(photo, :image)
|
70
|
+
# attacher.file #=> #<Shrine::UploadedFile>
|
71
|
+
def from_model(record, name, type: :model, **options)
|
72
|
+
attacher = new(**options)
|
73
|
+
attacher.load_model(record, name, type: type)
|
74
|
+
attacher
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module AttacherMethods
|
79
|
+
def initialize(model_cache: shrine_class.opts[:model][:cache], **options)
|
80
|
+
super(**options)
|
81
|
+
@model_cache = model_cache
|
82
|
+
end
|
83
|
+
|
84
|
+
# Saves record and name and initializes attachment from the model
|
85
|
+
# attribute. Called from `Attacher.from_model`.
|
86
|
+
def load_model(record, name, type: :model)
|
87
|
+
load_entity(record, name, type: type)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Called by the attachment attribute setter on the model.
|
91
|
+
def model_assign(value, **options)
|
92
|
+
if model_cache?
|
93
|
+
assign(value, **options)
|
94
|
+
else
|
95
|
+
attach(value, **options)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Writes uploaded file data into the model.
|
100
|
+
def set(*args)
|
101
|
+
result = super
|
102
|
+
write if model?
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
# Writes the attachment data into the model attribute.
|
107
|
+
def write
|
108
|
+
column_values.each do |name, value|
|
109
|
+
write_attribute(name, value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Writes given value into the model attribute.
|
116
|
+
def write_attribute(name = attribute, value)
|
117
|
+
record.public_send(:"#{name}=", value)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns whether assigned files should be uploaded to/loaded from
|
121
|
+
# temporary storage.
|
122
|
+
def model_cache?
|
123
|
+
@model_cache
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns whether the attacher is being backed by a model instance.
|
127
|
+
# This allows users to still use the attacher with an entity instance
|
128
|
+
# or without any record instance.
|
129
|
+
def model?
|
130
|
+
type == :model
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
register_plugin(:model, Model)
|
136
|
+
end
|
137
|
+
end
|