shrine 3.1.0 → 3.2.0

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.

@@ -119,7 +119,7 @@ module TestData
119
119
  small: uploaded_image,
120
120
  )
121
121
 
122
- attacher.column_data
122
+ attacher.column_data # or attacher.data in case of postgres jsonb column
123
123
  end
124
124
 
125
125
  def uploaded_image
@@ -128,7 +128,7 @@ module TestData
128
128
  # for performance we skip metadata extraction and assign test metadata
129
129
  uploaded_file = Shrine.upload(file, :store, metadata: false)
130
130
  uploaded_file.metadata.merge!(
131
- "size" => file.size,
131
+ "size" => File.size(file.path),
132
132
  "mime_type" => "image/jpeg",
133
133
  "filename" => "test.jpg",
134
134
  )
@@ -283,8 +283,9 @@ attacher.destroy_background # calls destroy block
283
283
  ## Versions
284
284
 
285
285
  The `versions`, `processing`, `recache`, and `delete_raw` plugins have been
286
- deprecated in favour of the new **[`derivatives`][derivatives]** plugin. Let's
287
- assume you have the following `versions` code:
286
+ deprecated in favour of the new **[`derivatives`][derivatives]** plugin.
287
+
288
+ Let's assume you have the following `versions` configuration:
288
289
 
289
290
  ```rb
290
291
  class ImageUploader < Shrine
@@ -307,11 +308,15 @@ class ImageUploader < Shrine
307
308
  end
308
309
  end
309
310
  ```
311
+
312
+ When an attached file is promoted to permanent storage, the versions would
313
+ automatically get generated:
314
+
310
315
  ```rb
311
316
  photo = Photo.new(photo_params)
312
317
 
313
318
  if photo.valid?
314
- photo.save # automatically calls processing block
319
+ photo.save # generates versions on promotion
315
320
  # ...
316
321
  else
317
322
  # ...
@@ -319,7 +324,7 @@ end
319
324
  ```
320
325
 
321
326
  With `derivatives`, the original file is automatically downloaded and retained,
322
- so the code is now much simpler:
327
+ so the processing code is much simpler:
323
328
 
324
329
  ```rb
325
330
  Shrine.plugin :derivatives, versions_compatibility: true # handle versions column format
@@ -338,22 +343,25 @@ class ImageUploader < Shrine
338
343
  end
339
344
  end
340
345
  ```
346
+
347
+ However, you now need to trigger processing manually during attachment:
348
+
341
349
  ```rb
342
350
  photo = Photo.new(photo_params)
343
351
 
344
352
  if photo.valid?
345
353
  photo.image_derivatives! if photo.image_changed? # create derivatives
346
- photo.save # automatically calls processing block
354
+ photo.save
347
355
  # ...
348
356
  else
349
357
  # ...
350
358
  end
351
359
  ```
352
360
 
353
- If you have multiple places where you need to generate derivatives, and want it
354
- to happen automatically like it did with the `versions` plugin, you can
355
- override `Attacher#promote` to call `Attacher#create_derivatives` before
356
- promotion:
361
+ ### Automatic processing
362
+
363
+ If you prefer processing to happen automatically with promotion (like it did
364
+ with the `versions` plugin), you can put the following in your initializer:
357
365
 
358
366
  ```rb
359
367
  class Shrine::Attacher
@@ -372,7 +380,7 @@ The derivative URLs are accessed in the same way as versions:
372
380
  photo.image_url(:small)
373
381
  ```
374
382
 
375
- But the derivatives themselves are accessed differently:
383
+ But the files themselves are accessed differently:
376
384
 
377
385
  ```rb
378
386
  # versions
@@ -426,8 +434,8 @@ database column in different formats:
426
434
 
427
435
  The `:versions_compatibility` flag to the `derivatives` plugin enables it to
428
436
  read the `versions` format, which aids in transition. Once the `derivatives`
429
- plugin has been deployed to production, you can switch existing records to the
430
- new column format:
437
+ plugin has been deployed to production, you can update existing records with
438
+ the new column format:
431
439
 
432
440
  ```rb
433
441
  Photo.find_each do |photo|
@@ -488,48 +496,77 @@ else
488
496
  end
489
497
  ```
490
498
 
491
- #### Parallelize
499
+ ### Default URL
492
500
 
493
- The `parallelize` plugin has been removed. The `derivatives` plugin is
494
- thread-safe, so you can parallelize uploading processed files manually:
501
+ If you were using the `default_url` plugin, the `Attacher.default_url` now
502
+ receives a `:derivative` option:
495
503
 
496
504
  ```rb
497
- # Gemfile
498
- gem "concurrent-ruby"
505
+ Attacher.default_url do |derivative: nil, **|
506
+ "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
507
+ end
499
508
  ```
500
- ```rb
501
- require "concurrent"
502
509
 
503
- derivatives = attacher.process_derivatives
510
+ #### Fallback to original
504
511
 
505
- tasks = derivatives.map do |name, file|
506
- Concurrent::Promises.future(name, file) do |name, file|
507
- attacher.add_derivative(name, file)
508
- end
509
- end
512
+ With the `versions` plugin, a missing version URL would automatically fall back
513
+ to the original file. The `derivatives` plugin has no such fallback, but you
514
+ can configure it manually:
510
515
 
511
- Concurrent::Promises.zip(*tasks).wait!
516
+ ```rb
517
+ Attacher.default_url do |derivative: nil, **|
518
+ file&.url if derivative
519
+ end
512
520
  ```
513
521
 
514
- #### Default URL
522
+ #### Fallback to version
515
523
 
516
- The `derivatives` plugin integrates with the `default_url` plugin:
524
+ The `versions` plugin had the ability to fall back missing version URL to
525
+ another version that already exists. The `derivatives` plugin doesn't have this
526
+ built in, but you can implement it as follows:
517
527
 
518
528
  ```rb
529
+ DERIVATIVE_FALLBACKS = { foo: :bar, ... }
530
+
519
531
  Attacher.default_url do |derivative: nil, **|
520
- "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
532
+ derivatives[DERIVATIVE_FALLBACKS[derivative]]&.url if derivative
533
+ end
534
+ ```
535
+
536
+ ### Location
537
+
538
+ The `Shrine#generate_location` method will now receive a `:derivative`
539
+ parameter instead of `:version`:
540
+
541
+ ```rb
542
+ class MyUploader < Shrine
543
+ def generate_location(io, derivative: nil, **)
544
+ derivative #=> :large, :medium, :small, ...
545
+ # ...
546
+ end
521
547
  end
522
548
  ```
523
549
 
524
- However, it doesn't implement any other URL fallbacks that the `versions`
525
- plugin has for missing derivatives.
550
+ ### Overwriting original
551
+
552
+ With the `derivatives` plugin, saving processed files separately from the
553
+ original file, so the original file is automatically kept. This means it's not
554
+ possible anymore to overwrite the original file as part of processing.
555
+
556
+ However, **it's highly recommended to always keep the original file**, even if
557
+ you don't plan to use it. That way, if there is ever a need to reprocess
558
+ derivatives, you have the original file to use as a base.
559
+
560
+ That being said, if you still want to overwrite the original file, [this
561
+ thread][overwriting original] has some tips.
526
562
 
527
563
  ## Other
528
564
 
529
565
  ### Processing
530
566
 
531
567
  The `processing` plugin has been deprecated over the new
532
- [`derivatives`][derivatives] plugin. If you were modifying the original file:
568
+ [`derivatives`][derivatives] plugin. If you were previously replacing the
569
+ original file:
533
570
 
534
571
  ```rb
535
572
  class MyUploader < Shrine
@@ -557,6 +594,30 @@ class MyUploader < Shrine
557
594
  end
558
595
  ```
559
596
 
597
+ ### Parallelize
598
+
599
+ The `parallelize` plugin has been removed. With `derivatives` plugin you can
600
+ parallelize uploading processed files manually:
601
+
602
+ ```rb
603
+ # Gemfile
604
+ gem "concurrent-ruby"
605
+ ```
606
+ ```rb
607
+ require "concurrent"
608
+
609
+ attacher = photo.image_attacher
610
+ derivatives = attacher.process_derivatives
611
+
612
+ tasks = derivatives.map do |name, file|
613
+ Concurrent::Promises.future(name, file) do |name, file|
614
+ attacher.add_derivative(name, file)
615
+ end
616
+ end
617
+
618
+ Concurrent::Promises.zip(*tasks).wait!
619
+ ```
620
+
560
621
  ### Logging
561
622
 
562
623
  The `logging` plugin has been removed in favour of the
@@ -662,3 +723,4 @@ end
662
723
  [derivatives]: https://shrinerb.com/docs/plugins/derivatives
663
724
  [instrumentation]: https://shrinerb.com/docs/plugins/instrumentation
664
725
  [mirroring]: https://shrinerb.com/docs/plugins/mirroring
726
+ [overwriting original]: https://discourse.shrinerb.com/t/keep-original-file-after-processing/50/4
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "shrine/version"
4
3
  require "shrine/uploaded_file"
5
4
  require "shrine/attacher"
6
5
  require "shrine/attachment"
7
6
  require "shrine/plugins"
7
+ require "shrine/version"
8
8
 
9
9
  require "securerandom"
10
10
  require "json"
@@ -14,7 +14,8 @@ require "logger"
14
14
  # Core class that handles uploading files to specified storage.
15
15
  class Shrine
16
16
  # A generic exception used by Shrine.
17
- class Error < StandardError; end
17
+ class Error < StandardError
18
+ end
18
19
 
19
20
  # Raised when a file is not a valid IO.
20
21
  class InvalidFile < Error
@@ -39,10 +39,11 @@ class Shrine
39
39
 
40
40
  # Initializes the attached file, temporary and permanent storage.
41
41
  def initialize(file: nil, cache: :cache, store: :store)
42
- @file = file
43
- @cache = cache
44
- @store = store
45
- @context = {}
42
+ @file = file
43
+ @cache = cache
44
+ @store = store
45
+ @context = {}
46
+ @previous = nil
46
47
  end
47
48
 
48
49
  # Returns the temporary storage identifier.
@@ -69,6 +70,10 @@ class Shrine
69
70
  def assign(value, **options)
70
71
  return if value == "" # skip empty hidden field
71
72
 
73
+ if value.is_a?(Hash) || value.is_a?(String)
74
+ return if uploaded_file(value) == file # skip assignment for current file
75
+ end
76
+
72
77
  attach_cached(value, **options)
73
78
  end
74
79
 
@@ -89,7 +94,7 @@ class Shrine
89
94
  # attacher.attach_cached({ "id" => "...", "storage" => "cache", "metadata" => {} })
90
95
  def attach_cached(value, **options)
91
96
  if value.is_a?(String) || value.is_a?(Hash)
92
- change(cached(value, **options), **options)
97
+ change(cached(value, **options))
93
98
  else
94
99
  attach(value, storage: cache_key, action: :cache, **options)
95
100
  end
@@ -111,7 +116,7 @@ class Shrine
111
116
  def attach(io, storage: store_key, **options)
112
117
  file = upload(io, storage, **options) if io
113
118
 
114
- change(file, **options)
119
+ change(file)
115
120
  end
116
121
 
117
122
  # Deletes any previous file and promotes newly attached cached file.
@@ -138,7 +143,7 @@ class Shrine
138
143
  def finalize
139
144
  destroy_previous
140
145
  promote_cached
141
- remove_instance_variable(:@previous) if changed?
146
+ @previous = nil
142
147
  end
143
148
 
144
149
  # Plugins can override this if they want something to be done in a
@@ -211,7 +216,7 @@ class Shrine
211
216
  # attacher.change(uploaded_file)
212
217
  # attacher.file #=> #<Shrine::UploadedFile>
213
218
  # attacher.changed? #=> true
214
- def change(file, **)
219
+ def change(file)
215
220
  @previous = dup unless @file == file
216
221
  set(file)
217
222
  end
@@ -254,7 +259,7 @@ class Shrine
254
259
  # attacher.attach(file)
255
260
  # attacher.changed? #=> true
256
261
  def changed?
257
- instance_variable_defined?(:@previous)
262
+ !!@previous
258
263
  end
259
264
 
260
265
  # Returns whether a file is attached.
@@ -54,6 +54,19 @@ class Shrine
54
54
  end
55
55
  end
56
56
  end
57
+
58
+ module AttacherMethods
59
+ def add_metadata(new_metadata, &block)
60
+ file!.add_metadata(new_metadata, &block)
61
+ set(file) # trigger model write
62
+ end
63
+ end
64
+
65
+ module FileMethods
66
+ def add_metadata(new_metadata, &block)
67
+ @metadata = @metadata.merge(new_metadata, &block)
68
+ end
69
+ end
57
70
  end
58
71
 
59
72
  register_plugin(:add_metadata, AddMetadata)
@@ -227,8 +227,6 @@ class Shrine
227
227
  # hash[:thumb] #=> #<Shrine::UploadedFile>
228
228
  def upload_derivatives(files, **options)
229
229
  map_derivative(files) do |path, file|
230
- path = derivative_path(path)
231
-
232
230
  upload_derivative(path, file, **options)
233
231
  end
234
232
  end
@@ -238,6 +236,7 @@ class Shrine
238
236
  # hash = attacher.upload_derivative(:thumb, thumb)
239
237
  # hash[:thumb] #=> #<Shrine::UploadedFile>
240
238
  def upload_derivative(path, file, storage: nil, **options)
239
+ path = derivative_path(path)
241
240
  storage ||= derivative_storage(path)
242
241
 
243
242
  file.open if file.is_a?(Tempfile) # refresh file descriptor
@@ -374,7 +373,7 @@ class Shrine
374
373
  # attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
375
374
  def set_derivatives(derivatives)
376
375
  self.derivatives = derivatives
377
- set file # trigger model writing
376
+ set file # trigger model write
378
377
  derivatives
379
378
  end
380
379
 
@@ -442,7 +441,7 @@ class Shrine
442
441
  # attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
443
442
  # attacher.change(file)
444
443
  # attacher.derivatives #=> {}
445
- def change(*args)
444
+ def change(*)
446
445
  result = super
447
446
  set_derivatives({})
448
447
  result
@@ -473,7 +472,7 @@ class Shrine
473
472
  def _process_derivatives(processor_name, source, **options)
474
473
  processor = self.class.derivatives_processor(processor_name)
475
474
 
476
- result = instrument_derivatives(processor_name, options) do
475
+ result = instrument_derivatives(processor_name, source, options) do
477
476
  instance_exec(source, **options, &processor)
478
477
  end
479
478
 
@@ -485,20 +484,22 @@ class Shrine
485
484
  end
486
485
 
487
486
  # Sends a `derivatives.shrine` event for instrumentation plugin.
488
- def instrument_derivatives(processor_name, processor_options, &block)
487
+ def instrument_derivatives(processor_name, source, processor_options, &block)
489
488
  return yield unless shrine_class.respond_to?(:instrument)
490
489
 
491
490
  shrine_class.instrument(
492
491
  :derivatives,
493
492
  processor: processor_name,
494
493
  processor_options: processor_options,
494
+ io: source,
495
+ attacher: self,
495
496
  &block
496
497
  )
497
498
  end
498
499
 
499
500
  # Returns symbolized array or single key.
500
501
  def derivative_path(path)
501
- path = path.map { |key| key.is_a?(String) ? key.to_sym : key }
502
+ path = Array(path).map { |key| key.is_a?(String) ? key.to_sym : key }
502
503
  path = path.first if path.one?
503
504
  path
504
505
  end
@@ -141,7 +141,7 @@ class Shrine
141
141
  require "mimemagic"
142
142
 
143
143
  mime = MimeMagic.by_magic(io)
144
- mime.type if mime
144
+ mime&.type
145
145
  end
146
146
 
147
147
  def extract_with_marcel(io, options)
@@ -158,7 +158,7 @@ class Shrine
158
158
 
159
159
  if filename = extract_filename(io)
160
160
  mime_type = MIME::Types.of(filename).first
161
- mime_type.content_type if mime_type
161
+ mime_type&.content_type
162
162
  end
163
163
  end
164
164
 
@@ -167,7 +167,7 @@ class Shrine
167
167
 
168
168
  if filename = extract_filename(io)
169
169
  info = MiniMime.lookup_by_filename(filename)
170
- info.content_type if info
170
+ info&.content_type
171
171
  end
172
172
  end
173
173
 
@@ -118,7 +118,7 @@ class Shrine
118
118
  end
119
119
 
120
120
  # Writes uploaded file data into the model.
121
- def set(*args)
121
+ def set(*)
122
122
  result = super
123
123
  write if model?
124
124
  result