shrine 3.1.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/README.md +11 -4
  4. data/doc/advantages.md +4 -4
  5. data/doc/attacher.md +2 -2
  6. data/doc/carrierwave.md +24 -12
  7. data/doc/changing_derivatives.md +1 -1
  8. data/doc/changing_location.md +6 -5
  9. data/doc/design.md +134 -85
  10. data/doc/direct_s3.md +26 -0
  11. data/doc/external/articles.md +57 -45
  12. data/doc/external/extensions.md +41 -35
  13. data/doc/external/misc.md +23 -8
  14. data/doc/getting_started.md +156 -85
  15. data/doc/metadata.md +80 -44
  16. data/doc/multiple_files.md +1 -1
  17. data/doc/paperclip.md +28 -9
  18. data/doc/plugins/add_metadata.md +112 -35
  19. data/doc/plugins/atomic_helpers.md +41 -3
  20. data/doc/plugins/backgrounding.md +12 -2
  21. data/doc/plugins/column.md +36 -7
  22. data/doc/plugins/default_url.md +6 -3
  23. data/doc/plugins/derivatives.md +83 -44
  24. data/doc/plugins/download_endpoint.md +5 -5
  25. data/doc/plugins/dynamic_storage.md +1 -1
  26. data/doc/plugins/entity.md +12 -4
  27. data/doc/plugins/form_assign.md +5 -5
  28. data/doc/plugins/included.md +25 -5
  29. data/doc/plugins/infer_extension.md +9 -0
  30. data/doc/plugins/instrumentation.md +1 -1
  31. data/doc/plugins/metadata_attributes.md +1 -0
  32. data/doc/plugins/mirroring.md +1 -1
  33. data/doc/plugins/model.md +8 -3
  34. data/doc/plugins/persistence.md +10 -1
  35. data/doc/plugins/remote_url.md +6 -1
  36. data/doc/plugins/remove_invalid.md +9 -1
  37. data/doc/plugins/sequel.md +1 -1
  38. data/doc/plugins/store_dimensions.md +10 -0
  39. data/doc/plugins/type_predicates.md +96 -0
  40. data/doc/plugins/upload_endpoint.md +1 -1
  41. data/doc/plugins/upload_options.md +1 -1
  42. data/doc/plugins/url_options.md +4 -4
  43. data/doc/plugins/validation.md +14 -4
  44. data/doc/plugins/versions.md +7 -7
  45. data/doc/processing.md +287 -123
  46. data/doc/refile.md +9 -9
  47. data/doc/release_notes/2.8.0.md +1 -1
  48. data/doc/release_notes/3.0.0.md +1 -1
  49. data/doc/release_notes/3.2.0.md +96 -0
  50. data/doc/release_notes/3.2.1.md +31 -0
  51. data/doc/release_notes/3.2.2.md +14 -0
  52. data/doc/release_notes/3.3.0.md +105 -0
  53. data/doc/release_notes/3.4.0.md +35 -0
  54. data/doc/securing_uploads.md +2 -2
  55. data/doc/storage/memory.md +19 -0
  56. data/doc/storage/s3.md +104 -77
  57. data/doc/testing.md +12 -2
  58. data/doc/upgrading_to_3.md +99 -53
  59. data/lib/shrine.rb +9 -8
  60. data/lib/shrine/attacher.rb +20 -10
  61. data/lib/shrine/attachment.rb +2 -2
  62. data/lib/shrine/plugins.rb +22 -0
  63. data/lib/shrine/plugins/activerecord.rb +3 -3
  64. data/lib/shrine/plugins/add_metadata.rb +20 -5
  65. data/lib/shrine/plugins/backgrounding.rb +2 -2
  66. data/lib/shrine/plugins/default_url.rb +1 -1
  67. data/lib/shrine/plugins/derivation_endpoint.rb +13 -8
  68. data/lib/shrine/plugins/derivatives.rb +59 -30
  69. data/lib/shrine/plugins/determine_mime_type.rb +5 -3
  70. data/lib/shrine/plugins/entity.rb +12 -11
  71. data/lib/shrine/plugins/instrumentation.rb +12 -18
  72. data/lib/shrine/plugins/mirroring.rb +8 -8
  73. data/lib/shrine/plugins/model.rb +3 -3
  74. data/lib/shrine/plugins/presign_endpoint.rb +16 -4
  75. data/lib/shrine/plugins/pretty_location.rb +1 -1
  76. data/lib/shrine/plugins/processing.rb +1 -1
  77. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  78. data/lib/shrine/plugins/remote_url.rb +3 -3
  79. data/lib/shrine/plugins/remove_attachment.rb +5 -0
  80. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  81. data/lib/shrine/plugins/sequel.rb +1 -1
  82. data/lib/shrine/plugins/store_dimensions.rb +4 -2
  83. data/lib/shrine/plugins/type_predicates.rb +113 -0
  84. data/lib/shrine/plugins/upload_endpoint.rb +10 -5
  85. data/lib/shrine/plugins/upload_options.rb +2 -2
  86. data/lib/shrine/plugins/url_options.rb +2 -2
  87. data/lib/shrine/plugins/validation.rb +9 -7
  88. data/lib/shrine/storage/linter.rb +4 -4
  89. data/lib/shrine/storage/memory.rb +5 -3
  90. data/lib/shrine/storage/s3.rb +117 -38
  91. data/lib/shrine/version.rb +1 -1
  92. data/shrine.gemspec +8 -8
  93. metadata +42 -34
@@ -22,8 +22,8 @@ class Shrine
22
22
  # Shorthand for `Attachment.new`.
23
23
  #
24
24
  # Shrine::Attachment[:image]
25
- def [](*args)
26
- new(*args)
25
+ def [](*args, **options)
26
+ new(*args, **options)
27
27
  end
28
28
  end
29
29
 
@@ -18,6 +18,28 @@ class Shrine
18
18
  plugin
19
19
  end
20
20
 
21
+ # Delegate call to the plugin in a way that works across Ruby versions.
22
+ def self.load_dependencies(plugin, uploader, *args, **kwargs, &block)
23
+ return unless plugin.respond_to?(:load_dependencies)
24
+
25
+ if kwargs.any?
26
+ plugin.load_dependencies(uploader, *args, **kwargs, &block)
27
+ else
28
+ plugin.load_dependencies(uploader, *args, &block)
29
+ end
30
+ end
31
+
32
+ # Delegate call to the plugin in a way that works across Ruby versions.
33
+ def self.configure(plugin, uploader, *args, **kwargs, &block)
34
+ return unless plugin.respond_to?(:configure)
35
+
36
+ if kwargs.any?
37
+ plugin.configure(uploader, *args, **kwargs, &block)
38
+ else
39
+ plugin.configure(uploader, *args, &block)
40
+ end
41
+ end
42
+
21
43
  # Register the given plugin with Shrine, so that it can be loaded using
22
44
  # `Shrine.plugin` with a symbol. Should be used by plugin files. Example:
23
45
  #
@@ -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
- instance_variable_set(:"@#{name}_attacher", nil)
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 |message|
79
- record.errors.add(name, *message)
78
+ errors.each do |(type, options)|
79
+ record.errors.add(name, type, **options.to_h)
80
80
  end
81
81
  end
82
82
 
@@ -9,8 +9,8 @@ class Shrine
9
9
  end
10
10
 
11
11
  module ClassMethods
12
- def add_metadata(name = nil, &block)
13
- opts[:add_metadata][:definitions] << [name, block]
12
+ def add_metadata(name = nil, **options, &block)
13
+ opts[:add_metadata][:definitions] << [name, options, block]
14
14
 
15
15
  metadata_method(name) if name
16
16
  end
@@ -40,10 +40,12 @@ class Shrine
40
40
  private
41
41
 
42
42
  def extract_custom_metadata(io, **options)
43
- opts[:add_metadata][:definitions].each do |name, block|
44
- result = instance_exec(io, options, &block)
43
+ opts[:add_metadata][:definitions].each do |name, definition_options, block|
44
+ result = instance_exec(io, **options, &block)
45
45
 
46
- if name
46
+ if result.nil? && definition_options[:skip_nil]
47
+ # Do not store this metadata
48
+ elsif name
47
49
  options[:metadata].merge! name.to_s => result
48
50
  else
49
51
  options[:metadata].merge! result.transform_keys(&:to_s) if result
@@ -54,6 +56,19 @@ class Shrine
54
56
  end
55
57
  end
56
58
  end
59
+
60
+ module AttacherMethods
61
+ def add_metadata(new_metadata, &block)
62
+ file!.add_metadata(new_metadata, &block)
63
+ set(file) # trigger model write
64
+ end
65
+ end
66
+
67
+ module FileMethods
68
+ def add_metadata(new_metadata, &block)
69
+ @metadata = @metadata.merge(new_metadata, &block)
70
+ end
71
+ end
57
72
  end
58
73
 
59
74
  register_plugin(:add_metadata, AddMetadata)
@@ -36,8 +36,8 @@ class Shrine
36
36
 
37
37
  module AttacherMethods
38
38
  # Inherits global hooks if defined.
39
- def initialize(*args)
40
- super
39
+ def initialize(**args)
40
+ super(**args)
41
41
  @destroy_block = self.class.destroy_block
42
42
  @promote_block = self.class.promote_block
43
43
  end
@@ -25,7 +25,7 @@ class Shrine
25
25
  def default_url(**options)
26
26
  return unless default_url_block
27
27
 
28
- url = instance_exec(options, &default_url_block)
28
+ url = instance_exec(**options, &default_url_block)
29
29
 
30
30
  [*default_url_host, url].join
31
31
  end
@@ -392,6 +392,7 @@ class Shrine
392
392
  options[:type] = request.params["type"] if request.params["type"]
393
393
  options[:disposition] = request.params["disposition"] if request.params["disposition"]
394
394
  options[:filename] = request.params["filename"] if request.params["filename"]
395
+ options[:version] = request.params["version"] if request.params["version"]
395
396
  options[:expires_in] = expires_in(request) if request.params["expires_at"]
396
397
 
397
398
  derivation = uploaded_file.derivation(name, *args, **options)
@@ -475,9 +476,9 @@ class Shrine
475
476
  file_response(derivative, env)
476
477
  end
477
478
 
478
- # Generates a Rack response triple from a local file using `Rack::File`.
479
- # Fills in `Content-Type` and `Content-Disposition` response headers from
480
- # derivation options and file extension of the derivation result.
479
+ # Generates a Rack response triple from a local file. Fills in
480
+ # `Content-Type` and `Content-Disposition` response headers from derivation
481
+ # options and file extension of the derivation result.
481
482
  def file_response(file, env)
482
483
  response = rack_file_response(file.path, env)
483
484
 
@@ -511,7 +512,7 @@ class Shrine
511
512
  end
512
513
 
513
514
  if upload_redirect
514
- redirect_url = uploaded_file.url(upload_redirect_url_options)
515
+ redirect_url = uploaded_file.url(**upload_redirect_url_options)
515
516
 
516
517
  [302, { "Location" => redirect_url }, []]
517
518
  else
@@ -528,10 +529,14 @@ class Shrine
528
529
  end
529
530
  end
530
531
 
531
- # We call `Rack::File` with no default `Content-Type`, and make sure we
532
+ # We call `Rack::Files` with no default `Content-Type`, and make sure we
532
533
  # stay compatible with both Rack 2.x and 1.6.x.
533
534
  def rack_file_response(path, env)
534
- server = Rack::File.new("", {}, nil)
535
+ if Rack.release >= "2.1"
536
+ server = Rack::Files.new("", {}, nil)
537
+ else
538
+ server = Rack::File.new("", {}, nil)
539
+ end
535
540
 
536
541
  if Rack.release > "2"
537
542
  server.serving(Rack::Request.new(env), path)
@@ -598,7 +603,7 @@ class Shrine
598
603
  def instrument_derivation(&block)
599
604
  return yield unless shrine_class.respond_to?(:instrument)
600
605
 
601
- shrine_class.instrument(:derivation, derivation: derivation, &block)
606
+ shrine_class.instrument(:derivation, { derivation: derivation }, &block)
602
607
  end
603
608
 
604
609
  # Massages the derivation result, ensuring it's opened in binary mode,
@@ -734,7 +739,7 @@ class Shrine
734
739
  def verify_signature(string, signature)
735
740
  if signature.nil?
736
741
  fail InvalidSignature, "missing \"signature\" param"
737
- elsif signature != generate_signature(string)
742
+ elsif !Rack::Utils.secure_compare(signature, generate_signature(string))
738
743
  fail InvalidSignature, "provided signature does not match the calculated signature"
739
744
  end
740
745
  end
@@ -4,8 +4,6 @@ class Shrine
4
4
  module Plugins
5
5
  # Documentation can be found on https://shrinerb.com/docs/plugins/derivatives
6
6
  module Derivatives
7
- NOOP_PROCESSOR = -> (*) { Hash.new }
8
-
9
7
  LOG_SUBSCRIBER = -> (event) do
10
8
  Shrine.logger.info "Derivatives (#{event.duration}ms) – #{{
11
9
  processor: event[:processor],
@@ -21,7 +19,7 @@ class Shrine
21
19
  end
22
20
 
23
21
  def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
24
- uploader.opts[:derivatives] ||= { processors: {}, storage: proc { store_key } }
22
+ uploader.opts[:derivatives] ||= { processors: {}, processor_settings: {}, storage: proc { store_key } }
25
23
  uploader.opts[:derivatives].merge!(opts)
26
24
 
27
25
  # instrumentation plugin integration
@@ -40,8 +38,8 @@ class Shrine
40
38
  def define_model_methods(name)
41
39
  super if defined?(super)
42
40
 
43
- define_method(:"#{name}_derivatives!") do |*args|
44
- send(:"#{name}_attacher").create_derivatives(*args)
41
+ define_method(:"#{name}_derivatives!") do |*args, **options|
42
+ send(:"#{name}_attacher").create_derivatives(*args, **options)
45
43
  end
46
44
  end
47
45
  end
@@ -52,20 +50,37 @@ class Shrine
52
50
  # Attacher.derivatives_processor :thumbnails do |original|
53
51
  # # ...
54
52
  # end
55
- def derivatives_processor(name = :default, &block)
53
+ #
54
+ # By default, Shrine will convert the source IO object into a file
55
+ # before it's passed to the processor block. You can set `download:
56
+ # false` to pass the source IO object to the processor block as is.
57
+ #
58
+ # Attacher.derivatives_processor :thumbnails, download: false do |original|
59
+ # # ...
60
+ # end
61
+ #
62
+ # This can be useful if you'd like to defer or avoid a possibly
63
+ # expensive download operation for processor logic that does not
64
+ # require it.
65
+ def derivatives_processor(name = :default, download: true, &block)
56
66
  if block
57
- shrine_class.opts[:derivatives][:processors][name.to_sym] = block
67
+ shrine_class.derivatives_options[:processors][name.to_sym] = block
68
+ shrine_class.derivatives_options[:processor_settings][name.to_sym] = { download: download }
58
69
  else
59
- processor = shrine_class.opts[:derivatives][:processors][name.to_sym]
60
- processor ||= NOOP_PROCESSOR if name == :default
61
-
62
- fail Error, "derivatives processor #{name.inspect} not registered" unless processor
63
-
64
- processor
70
+ shrine_class.derivatives_options[:processors].fetch(name.to_sym) do
71
+ fail Error, "derivatives processor #{name.inspect} not registered" unless name == :default
72
+ end
65
73
  end
66
74
  end
67
75
  alias derivatives derivatives_processor
68
76
 
77
+ # Returns settings for the given derivatives processor.
78
+ #
79
+ # Attacher.derivatives_processor_settings(:thumbnails) #=> { download: true }
80
+ def derivatives_processor_settings(name)
81
+ shrine_class.derivatives_options[:processor_settings][name.to_sym] || {}
82
+ end
83
+
69
84
  # Specifies default storage to which derivatives will be uploaded.
70
85
  #
71
86
  # Attacher.derivatives_storage :other_store
@@ -79,9 +94,9 @@ class Shrine
79
94
  # end
80
95
  def derivatives_storage(storage_key = nil, &block)
81
96
  if storage_key || block
82
- shrine_class.opts[:derivatives][:storage] = storage_key || block
97
+ shrine_class.derivatives_options[:storage] = storage_key || block
83
98
  else
84
- shrine_class.opts[:derivatives][:storage]
99
+ shrine_class.derivatives_options[:storage]
85
100
  end
86
101
  end
87
102
  end
@@ -147,6 +162,7 @@ class Shrine
147
162
  def promote(**options)
148
163
  super
149
164
  promote_derivatives
165
+ create_derivatives if create_derivatives_on_promote?
150
166
  end
151
167
 
152
168
  # Uploads any cached derivatives to permanent storage.
@@ -227,8 +243,6 @@ class Shrine
227
243
  # hash[:thumb] #=> #<Shrine::UploadedFile>
228
244
  def upload_derivatives(files, **options)
229
245
  map_derivative(files) do |path, file|
230
- path = derivative_path(path)
231
-
232
246
  upload_derivative(path, file, **options)
233
247
  end
234
248
  end
@@ -238,6 +252,7 @@ class Shrine
238
252
  # hash = attacher.upload_derivative(:thumb, thumb)
239
253
  # hash[:thumb] #=> #<Shrine::UploadedFile>
240
254
  def upload_derivative(path, file, storage: nil, **options)
255
+ path = derivative_path(path)
241
256
  storage ||= derivative_storage(path)
242
257
 
243
258
  file.open if file.is_a?(Tempfile) # refresh file descriptor
@@ -269,8 +284,10 @@ class Shrine
269
284
 
270
285
  source ||= file!
271
286
 
272
- if source.is_a?(UploadedFile)
273
- source.download do |file|
287
+ processor_settings = self.class.derivatives_processor_settings(processor_name) || {}
288
+
289
+ if processor_settings[:download]
290
+ shrine_class.with_file(source) do |file|
274
291
  _process_derivatives(processor_name, file, **options)
275
292
  end
276
293
  else
@@ -374,7 +391,7 @@ class Shrine
374
391
  # attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
375
392
  def set_derivatives(derivatives)
376
393
  self.derivatives = derivatives
377
- set file # trigger model writing
394
+ set file # trigger model write
378
395
  derivatives
379
396
  end
380
397
 
@@ -442,7 +459,7 @@ class Shrine
442
459
  # attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
443
460
  # attacher.change(file)
444
461
  # attacher.derivatives #=> {}
445
- def change(*args)
462
+ def change(*)
446
463
  result = super
447
464
  set_derivatives({})
448
465
  result
@@ -463,8 +480,8 @@ class Shrine
463
480
  # Iterates through nested derivatives and maps results.
464
481
  #
465
482
  # attacher.map_derivative(derivatives) { |path, file| ... }
466
- def map_derivative(*args, &block)
467
- shrine_class.map_derivative(*args, &block)
483
+ def map_derivative(derivatives, **options, &block)
484
+ shrine_class.map_derivative(derivatives, **options, &block)
468
485
  end
469
486
 
470
487
  private
@@ -473,7 +490,9 @@ class Shrine
473
490
  def _process_derivatives(processor_name, source, **options)
474
491
  processor = self.class.derivatives_processor(processor_name)
475
492
 
476
- result = instrument_derivatives(processor_name, options) do
493
+ return {} unless processor
494
+
495
+ result = instrument_derivatives(processor_name, source, options) do
477
496
  instance_exec(source, **options, &processor)
478
497
  end
479
498
 
@@ -485,20 +504,20 @@ class Shrine
485
504
  end
486
505
 
487
506
  # Sends a `derivatives.shrine` event for instrumentation plugin.
488
- def instrument_derivatives(processor_name, processor_options, &block)
507
+ def instrument_derivatives(processor_name, source, processor_options, &block)
489
508
  return yield unless shrine_class.respond_to?(:instrument)
490
509
 
491
- shrine_class.instrument(
492
- :derivatives,
510
+ shrine_class.instrument(:derivatives, {
493
511
  processor: processor_name,
494
512
  processor_options: processor_options,
495
- &block
496
- )
513
+ io: source,
514
+ attacher: self,
515
+ }, &block)
497
516
  end
498
517
 
499
518
  # Returns symbolized array or single key.
500
519
  def derivative_path(path)
501
- path = path.map { |key| key.is_a?(String) ? key.to_sym : key }
520
+ path = Array(path).map { |key| key.is_a?(String) ? key.to_sym : key }
502
521
  path = path.first if path.one?
503
522
  path
504
523
  end
@@ -520,6 +539,11 @@ class Shrine
520
539
  o2
521
540
  end
522
541
  end
542
+
543
+ # Whether to automatically create derivatives on promotion
544
+ def create_derivatives_on_promote?
545
+ shrine_class.derivatives_options[:create_on_promote]
546
+ end
523
547
  end
524
548
 
525
549
  module ClassMethods
@@ -576,6 +600,11 @@ class Shrine
576
600
  yield path, object
577
601
  end
578
602
  end
603
+
604
+ # Returns derivatives plugin options.
605
+ def derivatives_options
606
+ opts[:derivatives]
607
+ end
579
608
  end
580
609
 
581
610
  module FileMethods
@@ -124,6 +124,8 @@ class Shrine
124
124
  require "fastimage"
125
125
 
126
126
  type = FastImage.type(io)
127
+ return 'image/svg+xml' if type == :svg
128
+
127
129
  "image/#{type}" if type
128
130
  end
129
131
 
@@ -141,7 +143,7 @@ class Shrine
141
143
  require "mimemagic"
142
144
 
143
145
  mime = MimeMagic.by_magic(io)
144
- mime.type if mime
146
+ mime&.type
145
147
  end
146
148
 
147
149
  def extract_with_marcel(io, options)
@@ -158,7 +160,7 @@ class Shrine
158
160
 
159
161
  if filename = extract_filename(io)
160
162
  mime_type = MIME::Types.of(filename).first
161
- mime_type.content_type if mime_type
163
+ mime_type&.content_type
162
164
  end
163
165
  end
164
166
 
@@ -167,7 +169,7 @@ class Shrine
167
169
 
168
170
  if filename = extract_filename(io)
169
171
  info = MiniMime.lookup_by_filename(filename)
170
- info.content_type if info
172
+ info&.content_type
171
173
  end
172
174
  end
173
175
 
@@ -41,27 +41,27 @@ class Shrine
41
41
  end
42
42
 
43
43
  # Returns the URL to the attached file.
44
- define_method :"#{name}_url" do |*args|
45
- send(:"#{name}_attacher").url(*args)
44
+ define_method :"#{name}_url" do |*args, **options|
45
+ send(:"#{name}_attacher").url(*args, **options)
46
46
  end
47
47
 
48
48
  # Returns an attacher instance.
49
49
  define_method :"#{name}_attacher" do |**options|
50
- attachment.send(:attacher, self, options)
50
+ attachment.send(:attacher, self, **options)
51
51
  end
52
52
  end
53
53
 
54
54
  # Returns the class attacher instance with loaded entity. It's not
55
55
  # memoized because the entity object could be frozen.
56
- def attacher(record, options)
57
- attacher = class_attacher(options)
56
+ def attacher(record, **options)
57
+ attacher = class_attacher(**options)
58
58
  attacher.load_entity(record, @name)
59
59
  attacher
60
60
  end
61
61
 
62
62
  # Creates an instance of the corresponding attacher class with set
63
63
  # name.
64
- def class_attacher(options)
64
+ def class_attacher(**options)
65
65
  attacher = shrine_class::Attacher.new(**@options, **options)
66
66
  attacher.instance_variable_set(:@name, @name)
67
67
  attacher
@@ -112,9 +112,15 @@ class Shrine
112
112
  # attacher.file #=> #<Shrine::UploadedFile>
113
113
  def reload
114
114
  read
115
+ @previous = nil
115
116
  self
116
117
  end
117
118
 
119
+ # Loads attachment from the entity attribute.
120
+ def read
121
+ load_column(read_attribute)
122
+ end
123
+
118
124
  # Returns a hash with entity attribute name and column data.
119
125
  #
120
126
  # attacher.column_values
@@ -134,11 +140,6 @@ class Shrine
134
140
 
135
141
  private
136
142
 
137
- # Loads attachment from the entity attribute.
138
- def read
139
- load_column(read_attribute)
140
- end
141
-
142
143
  # Reads value from the entity attribute.
143
144
  def read_attribute
144
145
  record.public_send(attribute)