shrine 3.3.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +14 -12
- data/doc/carrierwave.md +2 -2
- data/doc/changing_derivatives.md +1 -1
- data/doc/changing_location.md +8 -6
- data/doc/design.md +5 -5
- data/doc/direct_s3.md +25 -0
- data/doc/external/articles.md +16 -16
- data/doc/external/extensions.md +1 -1
- data/doc/getting_started.md +79 -27
- data/doc/metadata.md +1 -1
- data/doc/multiple_files.md +57 -22
- data/doc/paperclip.md +1 -0
- data/doc/plugins/backgrounding.md +4 -4
- data/doc/plugins/derivation_endpoint.md +24 -0
- data/doc/plugins/derivatives.md +11 -1
- data/doc/plugins/entity.md +12 -4
- data/doc/plugins/instrumentation.md +1 -1
- data/doc/plugins/keep_files.md +6 -4
- data/doc/plugins/model.md +8 -3
- data/doc/plugins/sequel.md +1 -1
- data/doc/plugins/validation_helpers.md +1 -1
- data/doc/processing.md +3 -2
- data/doc/refile.md +3 -3
- data/doc/release_notes/2.1.0.md +1 -1
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/release_notes/3.5.0.md +63 -0
- data/doc/retrieving_uploads.md +1 -1
- data/doc/testing.md +55 -17
- data/doc/upgrading_to_3.md +6 -8
- data/lib/shrine/plugins/activerecord.rb +3 -3
- data/lib/shrine/plugins/derivation_endpoint.rb +31 -30
- data/lib/shrine/plugins/derivatives.rb +25 -19
- data/lib/shrine/plugins/determine_mime_type.rb +2 -0
- data/lib/shrine/plugins/download_endpoint.rb +7 -0
- data/lib/shrine/plugins/entity.rb +14 -7
- data/lib/shrine/plugins/infer_extension.rb +4 -0
- data/lib/shrine/plugins/instrumentation.rb +17 -19
- data/lib/shrine/plugins/model.rb +3 -1
- data/lib/shrine/plugins/remove_attachment.rb +2 -0
- data/lib/shrine/plugins/sequel.rb +1 -1
- data/lib/shrine/plugins/validation_helpers.rb +1 -1
- data/lib/shrine/storage/s3.rb +17 -7
- data/lib/shrine/uploaded_file.rb +3 -2
- data/lib/shrine/version.rb +1 -1
- data/lib/shrine.rb +3 -3
- data/shrine.gemspec +3 -2
- metadata +26 -10
data/doc/testing.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
title: Testing with Shrine
|
3
3
|
---
|
4
4
|
|
5
|
+
import Tabs from '@theme/Tabs';
|
6
|
+
import TabItem from '@theme/TabItem';
|
7
|
+
|
5
8
|
The goal of this guide is to provide some useful tips for testing file
|
6
9
|
attachments implemented with Shrine in your application.
|
7
10
|
|
@@ -137,19 +140,26 @@ module TestData
|
|
137
140
|
end
|
138
141
|
end
|
139
142
|
```
|
140
|
-
|
141
|
-
|
143
|
+
|
144
|
+
<Tabs>
|
145
|
+
<TabItem value="factory_bot" label="FactoryBot">
|
146
|
+
|
142
147
|
```rb
|
143
148
|
factory :photo do
|
144
149
|
image_data { TestData.image_data }
|
145
150
|
end
|
146
151
|
```
|
147
|
-
|
152
|
+
|
153
|
+
</TabItem>
|
154
|
+
<TabItem value="fixtures" label="Rails YAML fixtures">
|
155
|
+
|
148
156
|
```erb
|
149
157
|
photo:
|
150
158
|
image_data: <%= TestData.image_data %>
|
151
159
|
```
|
152
|
-
|
160
|
+
|
161
|
+
</TabItem>
|
162
|
+
</Tabs>
|
153
163
|
|
154
164
|
## Unit tests
|
155
165
|
|
@@ -182,24 +192,33 @@ end
|
|
182
192
|
In acceptance tests you're testing your app end-to-end, and you likely want to
|
183
193
|
also test file attachments here. Here are examples for some common use cases:
|
184
194
|
|
185
|
-
|
186
|
-
|
195
|
+
<Tabs>
|
196
|
+
<TabItem value="capybara" label="Capybara">
|
197
|
+
|
187
198
|
```rb
|
188
199
|
attach_file("#image-field", "test/files/image.jpg")
|
189
200
|
```
|
190
|
-
|
201
|
+
|
202
|
+
</TabItem>
|
203
|
+
<TabItem value="rack-test" label="rack-test">
|
204
|
+
|
191
205
|
```rb
|
192
206
|
post "/photos", photo: {
|
193
207
|
image: Rack::Test::UploadedFile.new("test/files/image.jpg", "image/jpeg")
|
194
208
|
}
|
195
209
|
```
|
196
|
-
|
210
|
+
|
211
|
+
</TabItem>
|
212
|
+
</Tabs>
|
213
|
+
|
214
|
+
If you want to test requests with cached attachment data, you can do so as
|
215
|
+
follows:
|
216
|
+
|
197
217
|
```rb
|
198
|
-
|
199
|
-
|
200
|
-
}
|
218
|
+
cached_file = Shrine.upload(file, :cache)
|
219
|
+
|
220
|
+
post "/photos", photo: { image: cached_file.to_json }
|
201
221
|
```
|
202
|
-
<!--END_DOCUSAURUS_CODE_TABS-->
|
203
222
|
|
204
223
|
## Background jobs
|
205
224
|
|
@@ -207,21 +226,30 @@ If you're using background jobs with Shrine, you probably want to make them
|
|
207
226
|
synchronous in tests. See your backgrounding library docs for how to make jobs
|
208
227
|
synchronous.
|
209
228
|
|
210
|
-
|
211
|
-
|
229
|
+
<Tabs>
|
230
|
+
<TabItem value="activejob" label="Active Job">
|
231
|
+
|
212
232
|
```rb
|
213
233
|
ActiveJob::Base.queue_adapter = :inline
|
214
234
|
```
|
215
|
-
|
235
|
+
|
236
|
+
</TabItem>
|
237
|
+
<TabItem value="sidekiq" label="Sidekiq">
|
238
|
+
|
216
239
|
```rb
|
217
240
|
require "sidekiq/testing"
|
218
241
|
Sidekiq::Testing.inline!
|
219
242
|
```
|
220
|
-
|
243
|
+
|
244
|
+
</TabItem>
|
245
|
+
<TabItem value="sucker_punch" label="SuckerPunch">
|
246
|
+
|
221
247
|
```rb
|
222
248
|
require "sucker_punch/testing/inline"
|
223
249
|
```
|
224
|
-
|
250
|
+
|
251
|
+
</TabItem>
|
252
|
+
</Tabs>
|
225
253
|
|
226
254
|
## Processing
|
227
255
|
|
@@ -251,6 +279,16 @@ TestMode.disable_processing(Photo.image_attacher) do
|
|
251
279
|
end
|
252
280
|
```
|
253
281
|
|
282
|
+
## Testing direct upload
|
283
|
+
|
284
|
+
If you'd like to unit-test direct upload on the server side, you can
|
285
|
+
emulate it by uploading a file to `cache` and then assigning it to the record.
|
286
|
+
|
287
|
+
```rb
|
288
|
+
cached_file = Shrine.upload(some_file, :cache)
|
289
|
+
record.attachment = cached_file.to_json
|
290
|
+
```
|
291
|
+
|
254
292
|
[DatabaseCleaner]: https://github.com/DatabaseCleaner/database_cleaner
|
255
293
|
[`#attach_file`]: http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions#attach_file-instance_method
|
256
294
|
[aws-sdk-ruby stubs]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ClientStubs.html
|
data/doc/upgrading_to_3.md
CHANGED
@@ -7,9 +7,6 @@ This guide provides instructions for upgrading Shrine in your apps to version
|
|
7
7
|
3.x. If you're looking for a full list of changes, see the **[3.0 release
|
8
8
|
notes]**.
|
9
9
|
|
10
|
-
If you would like assistance with the upgrade, I'm available for consultation,
|
11
|
-
you can email me at <janko.marohnic@gmail.com>.
|
12
|
-
|
13
10
|
## Attacher
|
14
11
|
|
15
12
|
The `Shrine::Attacher` class has been rewritten in Shrine 3.0, though much of
|
@@ -237,7 +234,7 @@ class PromoteJob
|
|
237
234
|
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
238
235
|
# attachment has changed or record has been deleted, nothing to do
|
239
236
|
end
|
240
|
-
|
237
|
+
end
|
241
238
|
```
|
242
239
|
```rb
|
243
240
|
class DestroyJob
|
@@ -258,7 +255,7 @@ class DestroyJob
|
|
258
255
|
attacher = attacher_class.from_data(data)
|
259
256
|
attacher.destroy
|
260
257
|
end
|
261
|
-
|
258
|
+
end
|
262
259
|
```
|
263
260
|
|
264
261
|
### Attacher backgrounding
|
@@ -439,7 +436,7 @@ creation in the `PromoteJob` instead of the controller:
|
|
439
436
|
class PromoteJob
|
440
437
|
include Sidekiq::Worker
|
441
438
|
|
442
|
-
def perform(attacher_class, record_class,
|
439
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
443
440
|
attacher_class = Object.const_get(attacher_class)
|
444
441
|
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
445
442
|
|
@@ -447,7 +444,7 @@ class PromoteJob
|
|
447
444
|
attacher.create_derivatives # call derivatives processor
|
448
445
|
attacher.atomic_promote
|
449
446
|
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
450
|
-
# attachment has changed or record has
|
447
|
+
# attachment has changed or record has been deleted, nothing to do
|
451
448
|
end
|
452
449
|
end
|
453
450
|
```
|
@@ -649,7 +646,8 @@ attacher.copy(other_attacher)
|
|
649
646
|
with
|
650
647
|
|
651
648
|
```rb
|
652
|
-
attacher.set
|
649
|
+
attacher.set nil # clear original attachment
|
650
|
+
attacher.attach other_attacher.file, storage: other_attacher.file.storage_key
|
653
651
|
attacher.add_derivatives other_attacher.derivatives # if using derivatives
|
654
652
|
```
|
655
653
|
|
@@ -55,7 +55,7 @@ class Shrine
|
|
55
55
|
# reload the attacher on record reload
|
56
56
|
define_method :reload do |*args|
|
57
57
|
result = super(*args)
|
58
|
-
send(:"#{name}_attacher").reload
|
58
|
+
send(:"#{name}_attacher").reload if instance_variable_defined?(:"@#{name}_attacher")
|
59
59
|
result
|
60
60
|
end
|
61
61
|
end
|
@@ -75,8 +75,8 @@ class Shrine
|
|
75
75
|
def activerecord_validate
|
76
76
|
return unless respond_to?(:errors)
|
77
77
|
|
78
|
-
errors.each do |
|
79
|
-
record.errors.add(name,
|
78
|
+
errors.each do |(type, options)|
|
79
|
+
record.errors.add(name, type, **options.to_h)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -28,8 +28,8 @@ class Shrine
|
|
28
28
|
uploader.opts[:derivation_endpoint] ||= { options: {}, derivations: {} }
|
29
29
|
uploader.opts[:derivation_endpoint][:options].merge!(opts)
|
30
30
|
|
31
|
-
|
32
|
-
fail Error, "must provide :secret_key option to derivation_endpoint plugin"
|
31
|
+
if !uploader.opts[:derivation_endpoint][:options][:secret_key] && !uploader.opts[:derivation_endpoint][:options][:signer]
|
32
|
+
fail Error, "must provide :secret_key option to derivation_endpoint plugin when no custom signer is set"
|
33
33
|
end
|
34
34
|
|
35
35
|
# instrumentation plugin integration
|
@@ -197,6 +197,7 @@ class Shrine
|
|
197
197
|
option :metadata, default: -> { [] }
|
198
198
|
option :prefix
|
199
199
|
option :secret_key
|
200
|
+
option :signer
|
200
201
|
option :type
|
201
202
|
option :upload, default: -> { false }
|
202
203
|
option :upload_location, default: -> { default_upload_location }, result: -> (o) { upload_location(o) }
|
@@ -216,7 +217,7 @@ class Shrine
|
|
216
217
|
option_definition = self.class.options.fetch(name)
|
217
218
|
|
218
219
|
value = options.fetch(name) { shrine_class.derivation_options[name] }
|
219
|
-
value = instance_exec(&value) if value.is_a?(Proc)
|
220
|
+
value = instance_exec(&value) if value.is_a?(Proc) && value.arity == 0
|
220
221
|
|
221
222
|
if value.nil?
|
222
223
|
default = option_definition[:default]
|
@@ -300,20 +301,36 @@ class Shrine
|
|
300
301
|
end
|
301
302
|
|
302
303
|
class Derivation::Url < Derivation::Command
|
303
|
-
delegate :name, :args, :source, :secret_key
|
304
|
+
delegate :name, :args, :source, :secret_key, :signer
|
304
305
|
|
305
306
|
def call(host: nil, prefix: nil, **options)
|
306
|
-
[host, *prefix
|
307
|
+
base_url = [host, *prefix].join("/")
|
308
|
+
path = path_identifier(metadata: options.delete(:metadata))
|
309
|
+
|
310
|
+
if signer
|
311
|
+
url = [base_url, path].join("/")
|
312
|
+
signer.call(url, **options)
|
313
|
+
else
|
314
|
+
signed_part = signed_url("#{path}?#{query(**options)}")
|
315
|
+
[base_url, signed_part].join("/")
|
316
|
+
end
|
307
317
|
end
|
308
318
|
|
309
319
|
private
|
310
320
|
|
311
|
-
def
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
321
|
+
def path_identifier(metadata: [])
|
322
|
+
[
|
323
|
+
name,
|
324
|
+
*args,
|
325
|
+
source.urlsafe_dump(metadata: metadata)
|
326
|
+
].map{|component| Rack::Utils.escape_path(component.to_s)}.join('/')
|
327
|
+
end
|
328
|
+
|
329
|
+
def query(expires_in: nil,
|
330
|
+
type: nil,
|
331
|
+
filename: nil,
|
332
|
+
disposition: nil,
|
333
|
+
version: nil)
|
317
334
|
|
318
335
|
params = {}
|
319
336
|
params[:expires_at] = (Time.now + expires_in).to_i if expires_in
|
@@ -322,23 +339,7 @@ class Shrine
|
|
322
339
|
params[:filename] = filename if filename
|
323
340
|
params[:disposition] = disposition if disposition
|
324
341
|
|
325
|
-
|
326
|
-
source_component = source.urlsafe_dump(metadata: metadata)
|
327
|
-
|
328
|
-
# generate plain URL
|
329
|
-
url = plain_url(name, *args, source_component, params)
|
330
|
-
|
331
|
-
# generate signed URL
|
332
|
-
signed_url(url)
|
333
|
-
end
|
334
|
-
|
335
|
-
def plain_url(*components, params)
|
336
|
-
# When using Rack < 2, Rack::Utils#escape_path will escape '/'.
|
337
|
-
# Escape each component and then join them together.
|
338
|
-
path = components.map{|component| Rack::Utils.escape_path(component.to_s)}.join('/')
|
339
|
-
query = Rack::Utils.build_query(params)
|
340
|
-
|
341
|
-
"#{path}?#{query}"
|
342
|
+
Rack::Utils.build_query(params)
|
342
343
|
end
|
343
344
|
|
344
345
|
def signed_url(url)
|
@@ -379,7 +380,7 @@ class Shrine
|
|
379
380
|
# Returns "404 Not Found" if derivation block is not defined, or if source
|
380
381
|
# file was not found on the storage.
|
381
382
|
def handle_request(request)
|
382
|
-
verify_signature!(request)
|
383
|
+
verify_signature!(request) if secret_key
|
383
384
|
check_expiry!(request)
|
384
385
|
|
385
386
|
name, *args, serialized_file = request.path_info.split("/")[1..-1]
|
@@ -603,7 +604,7 @@ class Shrine
|
|
603
604
|
def instrument_derivation(&block)
|
604
605
|
return yield unless shrine_class.respond_to?(:instrument)
|
605
606
|
|
606
|
-
shrine_class.instrument(:derivation, derivation: derivation, &block)
|
607
|
+
shrine_class.instrument(:derivation, { derivation: derivation }, &block)
|
607
608
|
end
|
608
609
|
|
609
610
|
# Massages the derivation result, ensuring it's opened in binary mode,
|
@@ -12,18 +12,18 @@ class Shrine
|
|
12
12
|
}.inspect}"
|
13
13
|
end
|
14
14
|
|
15
|
-
def self.load_dependencies(uploader,
|
15
|
+
def self.load_dependencies(uploader, **)
|
16
16
|
uploader.plugin :default_url
|
17
|
-
|
18
|
-
AttacherMethods.prepend(VersionsCompatibility) if versions_compatibility
|
19
17
|
end
|
20
18
|
|
21
|
-
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
22
|
-
uploader.opts[:derivatives] ||= { processors: {}, processor_settings: {}, storage: proc { store_key } }
|
19
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, versions_compatibility: false, **opts)
|
20
|
+
uploader.opts[:derivatives] ||= { processors: {}, processor_settings: {}, storage: proc { store_key }, mutex: true }
|
23
21
|
uploader.opts[:derivatives].merge!(opts)
|
24
22
|
|
25
23
|
# instrumentation plugin integration
|
26
24
|
uploader.subscribe(:derivatives, &log_subscriber) if uploader.respond_to?(:subscribe)
|
25
|
+
|
26
|
+
uploader::Attacher.include(VersionsCompatibility) if versions_compatibility
|
27
27
|
end
|
28
28
|
|
29
29
|
module AttachmentMethods
|
@@ -109,7 +109,7 @@ class Shrine
|
|
109
109
|
super(**options)
|
110
110
|
|
111
111
|
@derivatives = derivatives
|
112
|
-
@derivatives_mutex = Mutex.new
|
112
|
+
@derivatives_mutex = Mutex.new if shrine_class.derivatives_options[:mutex]
|
113
113
|
end
|
114
114
|
|
115
115
|
# Convenience method for accessing derivatives.
|
@@ -139,7 +139,7 @@ class Shrine
|
|
139
139
|
# Allows generating a URL to the derivative by passing the derivative
|
140
140
|
# name.
|
141
141
|
#
|
142
|
-
# attacher.add_derivatives(thumb: thumb)
|
142
|
+
# attacher.add_derivatives({ thumb: thumb })
|
143
143
|
# attacher.url(:thumb) #=> "https://example.org/thumb.jpg"
|
144
144
|
def url(*path, **options)
|
145
145
|
return super if path.empty?
|
@@ -180,7 +180,7 @@ class Shrine
|
|
180
180
|
|
181
181
|
# In addition to deleting the main file it also deletes any derivatives.
|
182
182
|
#
|
183
|
-
# attacher.add_derivatives(thumb: thumb)
|
183
|
+
# attacher.add_derivatives({ thumb: thumb })
|
184
184
|
# attacher.derivatives[:thumb].exists? #=> true
|
185
185
|
# attacher.destroy
|
186
186
|
# attacher.derivatives[:thumb].exists? #=> false
|
@@ -208,7 +208,7 @@ class Shrine
|
|
208
208
|
# # {
|
209
209
|
# # thumb: #<Shrine::UploadedFile>,
|
210
210
|
# # }
|
211
|
-
# attacher.add_derivatives(cropped: cropped)
|
211
|
+
# attacher.add_derivatives({ cropped: cropped })
|
212
212
|
# attacher.derivatives #=>
|
213
213
|
# # {
|
214
214
|
# # thumb: #<Shrine::UploadedFile>,
|
@@ -239,7 +239,7 @@ class Shrine
|
|
239
239
|
|
240
240
|
# Uploads given hash of files.
|
241
241
|
#
|
242
|
-
# hash = attacher.upload_derivatives(thumb: thumb)
|
242
|
+
# hash = attacher.upload_derivatives({ thumb: thumb })
|
243
243
|
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
244
244
|
def upload_derivatives(files, **options)
|
245
245
|
map_derivative(files) do |path, file|
|
@@ -298,10 +298,10 @@ class Shrine
|
|
298
298
|
# Deep merges given uploaded derivatives with current derivatives.
|
299
299
|
#
|
300
300
|
# attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
|
301
|
-
# attacher.merge_derivatives(two: uploaded_file)
|
301
|
+
# attacher.merge_derivatives({ two: uploaded_file })
|
302
302
|
# attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
|
303
303
|
def merge_derivatives(new_derivatives)
|
304
|
-
|
304
|
+
derivatives_synchronize do
|
305
305
|
merged_derivatives = deep_merge_derivatives(derivatives, new_derivatives)
|
306
306
|
set_derivatives(merged_derivatives)
|
307
307
|
end
|
@@ -379,7 +379,7 @@ class Shrine
|
|
379
379
|
|
380
380
|
# Deletes given hash of uploaded files.
|
381
381
|
#
|
382
|
-
# attacher.delete_derivatives(thumb: uploaded_file)
|
382
|
+
# attacher.delete_derivatives({ thumb: uploaded_file })
|
383
383
|
# uploaded_file.exists? #=> false
|
384
384
|
def delete_derivatives(derivatives = self.derivatives)
|
385
385
|
map_derivative(derivatives) { |_, derivative| derivative.delete }
|
@@ -387,7 +387,7 @@ class Shrine
|
|
387
387
|
|
388
388
|
# Sets the given hash of uploaded files as derivatives.
|
389
389
|
#
|
390
|
-
# attacher.set_derivatives(thumb: uploaded_file)
|
390
|
+
# attacher.set_derivatives({ thumb: uploaded_file })
|
391
391
|
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
392
392
|
def set_derivatives(derivatives)
|
393
393
|
self.derivatives = derivatives
|
@@ -398,7 +398,7 @@ class Shrine
|
|
398
398
|
# Adds derivative data into the hash.
|
399
399
|
#
|
400
400
|
# attacher.attach(io)
|
401
|
-
# attacher.add_derivatives(thumb: thumb)
|
401
|
+
# attacher.add_derivatives({ thumb: thumb })
|
402
402
|
# attacher.data
|
403
403
|
# #=>
|
404
404
|
# # {
|
@@ -507,14 +507,12 @@ class Shrine
|
|
507
507
|
def instrument_derivatives(processor_name, source, processor_options, &block)
|
508
508
|
return yield unless shrine_class.respond_to?(:instrument)
|
509
509
|
|
510
|
-
shrine_class.instrument(
|
511
|
-
:derivatives,
|
510
|
+
shrine_class.instrument(:derivatives, {
|
512
511
|
processor: processor_name,
|
513
512
|
processor_options: processor_options,
|
514
513
|
io: source,
|
515
514
|
attacher: self,
|
516
|
-
|
517
|
-
)
|
515
|
+
}, &block)
|
518
516
|
end
|
519
517
|
|
520
518
|
# Returns symbolized array or single key.
|
@@ -546,6 +544,14 @@ class Shrine
|
|
546
544
|
def create_derivatives_on_promote?
|
547
545
|
shrine_class.derivatives_options[:create_on_promote]
|
548
546
|
end
|
547
|
+
|
548
|
+
def derivatives_synchronize
|
549
|
+
if @derivatives_mutex
|
550
|
+
@derivatives_mutex.synchronize { yield }
|
551
|
+
else
|
552
|
+
yield
|
553
|
+
end
|
554
|
+
end
|
549
555
|
end
|
550
556
|
|
551
557
|
module ClassMethods
|
@@ -182,12 +182,19 @@ class Shrine
|
|
182
182
|
@shrine_class::UploadedFile.urlsafe_load(serialized)
|
183
183
|
rescue Shrine::Error # storage not found
|
184
184
|
not_found!
|
185
|
+
rescue JSON::ParserError, ArgumentError => error # invalid serialized component
|
186
|
+
raise if error.is_a?(ArgumentError) && error.message != "invalid base64"
|
187
|
+
bad_request!("Invalid serialized file")
|
185
188
|
end
|
186
189
|
|
187
190
|
def not_found!
|
188
191
|
error!(404, "File Not Found")
|
189
192
|
end
|
190
193
|
|
194
|
+
def bad_request!(message)
|
195
|
+
error!(400, message)
|
196
|
+
end
|
197
|
+
|
191
198
|
# Halts the request with the error message.
|
192
199
|
def error!(status, message)
|
193
200
|
throw :halt, [status, { "Content-Type" => "text/plain" }, [message]]
|
@@ -36,8 +36,14 @@ class Shrine
|
|
36
36
|
attachment = self
|
37
37
|
|
38
38
|
# Returns the attached file.
|
39
|
-
|
40
|
-
|
39
|
+
if shrine_class::Attacher.instance_method(:get).arity == 0
|
40
|
+
define_method :"#{name}" do
|
41
|
+
send(:"#{name}_attacher").get
|
42
|
+
end
|
43
|
+
else # derivatives
|
44
|
+
define_method :"#{name}" do |*args|
|
45
|
+
send(:"#{name}_attacher").get(*args)
|
46
|
+
end
|
41
47
|
end
|
42
48
|
|
43
49
|
# Returns the URL to the attached file.
|
@@ -112,9 +118,15 @@ class Shrine
|
|
112
118
|
# attacher.file #=> #<Shrine::UploadedFile>
|
113
119
|
def reload
|
114
120
|
read
|
121
|
+
@previous = nil
|
115
122
|
self
|
116
123
|
end
|
117
124
|
|
125
|
+
# Loads attachment from the entity attribute.
|
126
|
+
def read
|
127
|
+
load_column(read_attribute)
|
128
|
+
end
|
129
|
+
|
118
130
|
# Returns a hash with entity attribute name and column data.
|
119
131
|
#
|
120
132
|
# attacher.column_values
|
@@ -134,11 +146,6 @@ class Shrine
|
|
134
146
|
|
135
147
|
private
|
136
148
|
|
137
|
-
# Loads attachment from the entity attribute.
|
138
|
-
def read
|
139
|
-
load_column(read_attribute)
|
140
|
-
end
|
141
|
-
|
142
149
|
# Reads value from the entity attribute.
|
143
150
|
def read_attribute
|
144
151
|
record.public_send(attribute)
|
@@ -12,7 +12,11 @@ class Shrine
|
|
12
12
|
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
13
13
|
uploader.opts[:instrumentation] ||= { log_events: EVENTS, subscribers: {} }
|
14
14
|
uploader.opts[:instrumentation].merge!(opts)
|
15
|
-
|
15
|
+
begin
|
16
|
+
uploader.opts[:instrumentation][:notifications] ||= ::ActiveSupport::Notifications
|
17
|
+
rescue NameError
|
18
|
+
fail Error, "default notifications library is ActiveSupport::Notifications, but activesupport gem is not installed"
|
19
|
+
end
|
16
20
|
|
17
21
|
uploader.opts[:instrumentation][:log_events].each do |event_name|
|
18
22
|
uploader.subscribe(event_name, &log_subscriber)
|
@@ -69,27 +73,25 @@ class Shrine
|
|
69
73
|
|
70
74
|
# Sends a `upload.shrine` event.
|
71
75
|
def _upload(io, location:, metadata:, upload_options: {}, **options)
|
72
|
-
self.class.instrument(
|
73
|
-
:upload,
|
76
|
+
self.class.instrument(:upload, {
|
74
77
|
storage: storage_key,
|
75
78
|
location: location,
|
76
79
|
io: io,
|
77
80
|
upload_options: upload_options,
|
78
81
|
metadata: metadata,
|
79
82
|
options: options,
|
80
|
-
) { super }
|
83
|
+
}) { super }
|
81
84
|
end
|
82
85
|
|
83
86
|
# Sends a `metadata.shrine` event.
|
84
87
|
def get_metadata(io, metadata: nil, **options)
|
85
88
|
return super if io.is_a?(UploadedFile) && metadata != true || metadata == false
|
86
89
|
|
87
|
-
self.class.instrument(
|
88
|
-
:metadata,
|
90
|
+
self.class.instrument(:metadata, {
|
89
91
|
storage: storage_key,
|
90
92
|
io: io,
|
91
93
|
options: options,
|
92
|
-
) { super }
|
94
|
+
}) { super }
|
93
95
|
end
|
94
96
|
end
|
95
97
|
|
@@ -98,30 +100,27 @@ class Shrine
|
|
98
100
|
def stream(destination, **options)
|
99
101
|
return super if opened?
|
100
102
|
|
101
|
-
shrine_class.instrument(
|
102
|
-
:download,
|
103
|
+
shrine_class.instrument(:download, {
|
103
104
|
storage: storage_key,
|
104
105
|
location: id,
|
105
106
|
download_options: options,
|
106
|
-
) { super(destination, **options, instrument: false) }
|
107
|
+
}) { super(destination, **options, instrument: false) }
|
107
108
|
end
|
108
109
|
|
109
110
|
# Sends a `exists.shrine` event.
|
110
111
|
def exists?
|
111
|
-
shrine_class.instrument(
|
112
|
-
:exists,
|
112
|
+
shrine_class.instrument(:exists, {
|
113
113
|
storage: storage_key,
|
114
114
|
location: id,
|
115
|
-
) { super }
|
115
|
+
}) { super }
|
116
116
|
end
|
117
117
|
|
118
118
|
# Sends a `delete.shrine` event.
|
119
119
|
def delete
|
120
|
-
shrine_class.instrument(
|
121
|
-
:delete,
|
120
|
+
shrine_class.instrument(:delete, {
|
122
121
|
storage: storage_key,
|
123
122
|
location: id,
|
124
|
-
) { super }
|
123
|
+
}) { super }
|
125
124
|
end
|
126
125
|
|
127
126
|
private
|
@@ -130,12 +129,11 @@ class Shrine
|
|
130
129
|
def _open(instrument: true, **options)
|
131
130
|
return super(**options) unless instrument
|
132
131
|
|
133
|
-
shrine_class.instrument(
|
134
|
-
:open,
|
132
|
+
shrine_class.instrument(:open, {
|
135
133
|
storage: storage_key,
|
136
134
|
location: id,
|
137
135
|
download_options: options,
|
138
|
-
) { super(**options) }
|
136
|
+
}) { super(**options) }
|
139
137
|
end
|
140
138
|
end
|
141
139
|
|
data/lib/shrine/plugins/model.rb
CHANGED
@@ -48,7 +48,9 @@ class Shrine
|
|
48
48
|
# The copy constructor that's called on #dup and #clone.
|
49
49
|
define_method :initialize_copy do |other|
|
50
50
|
super(other)
|
51
|
-
|
51
|
+
attacher_copy = instance_variable_get(:"@#{name}_attacher")&.dup
|
52
|
+
attacher_copy.set_entity(self, name) if attacher_copy
|
53
|
+
instance_variable_set(:"@#{name}_attacher", attacher_copy)
|
52
54
|
self
|
53
55
|
end
|
54
56
|
private :initialize_copy
|
@@ -62,7 +62,7 @@ class Shrine
|
|
62
62
|
# reload the attacher on record reload
|
63
63
|
define_method :_refresh do |*args|
|
64
64
|
result = super(*args)
|
65
|
-
send(:"#{name}_attacher").reload
|
65
|
+
send(:"#{name}_attacher").reload if instance_variable_defined?(:"@#{name}_attacher")
|
66
66
|
result
|
67
67
|
end
|
68
68
|
private :_refresh
|