activestorage 6.1.3.1 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activestorage might be problematic. Click here for more details.

Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -181
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +25 -11
  5. data/app/assets/javascripts/activestorage.esm.js +844 -0
  6. data/app/assets/javascripts/activestorage.js +257 -376
  7. data/app/controllers/active_storage/base_controller.rb +1 -10
  8. data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
  9. data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
  10. data/app/controllers/active_storage/representations/base_controller.rb +18 -0
  11. data/app/controllers/active_storage/representations/proxy_controller.rb +7 -11
  12. data/app/controllers/active_storage/representations/redirect_controller.rb +7 -7
  13. data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
  14. data/app/controllers/concerns/active_storage/set_current.rb +3 -3
  15. data/app/controllers/concerns/active_storage/streaming.rb +65 -0
  16. data/app/javascript/activestorage/ujs.js +1 -1
  17. data/app/models/active_storage/attachment.rb +36 -3
  18. data/app/models/active_storage/blob/representable.rb +8 -6
  19. data/app/models/active_storage/blob.rb +27 -28
  20. data/app/models/active_storage/current.rb +12 -2
  21. data/app/models/active_storage/preview.rb +6 -4
  22. data/app/models/active_storage/record.rb +1 -1
  23. data/app/models/active_storage/variant.rb +4 -7
  24. data/app/models/active_storage/variant_record.rb +2 -0
  25. data/app/models/active_storage/variant_with_record.rb +10 -6
  26. data/app/models/active_storage/variation.rb +2 -2
  27. data/config/routes.rb +10 -10
  28. data/db/migrate/20170806125915_create_active_storage_tables.rb +29 -8
  29. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
  30. data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
  31. data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
  32. data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
  33. data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
  34. data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
  35. data/lib/active_storage/analyzer.rb +8 -4
  36. data/lib/active_storage/attached/changes/create_many.rb +7 -3
  37. data/lib/active_storage/attached/changes/create_one.rb +1 -1
  38. data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
  39. data/lib/active_storage/attached/changes/delete_many.rb +1 -1
  40. data/lib/active_storage/attached/changes/delete_one.rb +1 -1
  41. data/lib/active_storage/attached/changes/detach_many.rb +18 -0
  42. data/lib/active_storage/attached/changes/detach_one.rb +24 -0
  43. data/lib/active_storage/attached/changes/purge_many.rb +27 -0
  44. data/lib/active_storage/attached/changes/purge_one.rb +27 -0
  45. data/lib/active_storage/attached/changes.rb +7 -1
  46. data/lib/active_storage/attached/many.rb +27 -15
  47. data/lib/active_storage/attached/model.rb +31 -5
  48. data/lib/active_storage/attached/one.rb +32 -27
  49. data/lib/active_storage/downloader.rb +2 -2
  50. data/lib/active_storage/engine.rb +29 -1
  51. data/lib/active_storage/errors.rb +3 -0
  52. data/lib/active_storage/fixture_set.rb +76 -0
  53. data/lib/active_storage/gem_version.rb +4 -4
  54. data/lib/active_storage/previewer/video_previewer.rb +1 -1
  55. data/lib/active_storage/previewer.rb +14 -5
  56. data/lib/active_storage/reflection.rb +12 -2
  57. data/lib/active_storage/service/azure_storage_service.rb +1 -1
  58. data/lib/active_storage/service/configurator.rb +1 -1
  59. data/lib/active_storage/service/disk_service.rb +13 -18
  60. data/lib/active_storage/service/gcs_service.rb +91 -7
  61. data/lib/active_storage/service/mirror_service.rb +1 -1
  62. data/lib/active_storage/service/registry.rb +1 -1
  63. data/lib/active_storage/service/s3_service.rb +4 -4
  64. data/lib/active_storage/service.rb +3 -3
  65. data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
  66. data/lib/active_storage/transformers/transformer.rb +1 -1
  67. data/lib/active_storage.rb +5 -1
  68. data/lib/tasks/activestorage.rake +5 -1
  69. metadata +32 -22
  70. data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -1,5 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ActiveStorage::Current < ActiveSupport::CurrentAttributes #:nodoc:
4
- attribute :host
3
+ class ActiveStorage::Current < ActiveSupport::CurrentAttributes # :nodoc:
4
+ attribute :url_options
5
+
6
+ def host=(host)
7
+ ActiveSupport::Deprecation.warn("ActiveStorage::Current.host= is deprecated, instead use ActiveStorage::Current.url_options=")
8
+ self.url_options = { host: host }
9
+ end
10
+
11
+ def host
12
+ ActiveSupport::Deprecation.warn("ActiveStorage::Current.host is deprecated, instead use ActiveStorage::Current.url_options")
13
+ self.url_options&.dig(:host)
14
+ end
5
15
  end
@@ -66,9 +66,6 @@ class ActiveStorage::Preview
66
66
  end
67
67
  end
68
68
 
69
- alias_method :service_url, :url
70
- deprecate service_url: :url
71
-
72
69
  # Returns a combination key of the blob and the variation that together identifies a specific variant.
73
70
  def key
74
71
  if processed?
@@ -78,6 +75,11 @@ class ActiveStorage::Preview
78
75
  end
79
76
  end
80
77
 
78
+ # Downloads the file associated with this preview's variant. If no block is
79
+ # given, the entire file is read into memory and returned. That'll use a lot
80
+ # of RAM for very large files. If a block is given, then the download is
81
+ # streamed and yielded in chunks. Raises ActiveStorage::Preview::UnprocessedError
82
+ # if the preview has not been processed yet.
81
83
  def download(&block)
82
84
  if processed?
83
85
  variant.download(&block)
@@ -93,7 +95,7 @@ class ActiveStorage::Preview
93
95
 
94
96
  def process
95
97
  previewer.preview(service_name: blob.service_name) do |attachable|
96
- ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
98
+ ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
97
99
  image.attach(attachable)
98
100
  end
99
101
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ActiveStorage::Record < ActiveRecord::Base #:nodoc:
3
+ class ActiveStorage::Record < ActiveRecord::Base # :nodoc:
4
4
  self.abstract_class = true
5
5
  end
6
6
 
@@ -42,7 +42,7 @@
42
42
  # You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the
43
43
  # ImageProcessing gem (such as +resize_to_limit+):
44
44
  #
45
- # avatar.variant(resize_to_limit: [800, 800], monochrome: true, rotate: "-90")
45
+ # avatar.variant(resize_to_limit: [800, 800], colourspace: "b-w", rotate: "-90")
46
46
  #
47
47
  # Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations:
48
48
  #
@@ -67,7 +67,7 @@ class ActiveStorage::Variant
67
67
 
68
68
  # Returns a combination key of the blob and the variation that together identifies a specific variant.
69
69
  def key
70
- "variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}"
70
+ "variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
71
71
  end
72
72
 
73
73
  # Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
@@ -79,9 +79,6 @@ class ActiveStorage::Variant
79
79
  service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
80
80
  end
81
81
 
82
- alias_method :service_url, :url
83
- deprecate service_url: :url
84
-
85
82
  # Downloads the file associated with this variant. If no block is given, the entire file is read into memory and returned.
86
83
  # That'll use a lot of RAM for very large files. If a block is given, then the download is streamed and yielded in chunks.
87
84
  def download(&block)
@@ -89,12 +86,12 @@ class ActiveStorage::Variant
89
86
  end
90
87
 
91
88
  def filename
92
- ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format}"
89
+ ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
93
90
  end
94
91
 
95
92
  alias_method :content_type_for_serving, :content_type
96
93
 
97
- def forced_disposition_for_serving #:nodoc:
94
+ def forced_disposition_for_serving # :nodoc:
98
95
  nil
99
96
  end
100
97
 
@@ -6,3 +6,5 @@ class ActiveStorage::VariantRecord < ActiveStorage::Record
6
6
  belongs_to :blob
7
7
  has_one_attached :image
8
8
  end
9
+
10
+ ActiveSupport.run_load_hooks :active_storage_variant_record, ActiveStorage::VariantRecord
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Like an ActiveStorage::Variant, but keeps detail about the variant in the database as an
4
+ # ActiveStorage::VariantRecord. This is only used if `ActiveStorage.track_variants` is enabled.
3
5
  class ActiveStorage::VariantWithRecord
4
6
  attr_reader :blob, :variation
7
+ delegate :service, to: :blob
5
8
 
6
9
  def initialize(blob, variation)
7
10
  @blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
@@ -26,14 +29,11 @@ class ActiveStorage::VariantWithRecord
26
29
 
27
30
  delegate :key, :url, :download, to: :image, allow_nil: true
28
31
 
29
- alias_method :service_url, :url
30
- deprecate service_url: :url
31
-
32
32
  private
33
33
  def transform_blob
34
34
  blob.open do |input|
35
35
  variation.transform(input) do |output|
36
- yield io: output, filename: "#{blob.filename.base}.#{variation.format}",
36
+ yield io: output, filename: "#{blob.filename.base}.#{variation.format.downcase}",
37
37
  content_type: variation.content_type, service_name: blob.service.name
38
38
  end
39
39
  end
@@ -41,7 +41,7 @@ class ActiveStorage::VariantWithRecord
41
41
 
42
42
  def create_or_find_record(image:)
43
43
  @record =
44
- ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
44
+ ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
45
45
  blob.variant_records.create_or_find_by!(variation_digest: variation.digest) do |record|
46
46
  record.image.attach(image)
47
47
  end
@@ -49,6 +49,10 @@ class ActiveStorage::VariantWithRecord
49
49
  end
50
50
 
51
51
  def record
52
- @record ||= blob.variant_records.find_by(variation_digest: variation.digest)
52
+ @record ||= if blob.variant_records.loaded?
53
+ blob.variant_records.find { |v| v.variation_digest == variation.digest }
54
+ else
55
+ blob.variant_records.find_by(variation_digest: variation.digest)
56
+ end
53
57
  end
54
58
  end
@@ -8,7 +8,7 @@ require "mini_mime"
8
8
  # In case you do need to use this directly, it's instantiated using a hash of transformations where
9
9
  # the key is the command and the value is the arguments. Example:
10
10
  #
11
- # ActiveStorage::Variation.new(resize_to_limit: [100, 100], monochrome: true, trim: true, rotate: "-90")
11
+ # ActiveStorage::Variation.new(resize_to_limit: [100, 100], colourspace: "b-w", rotate: "-90", saver: { trim: true })
12
12
  #
13
13
  # The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
14
14
  class ActiveStorage::Variation
@@ -75,7 +75,7 @@ class ActiveStorage::Variation
75
75
  end
76
76
 
77
77
  def digest
78
- Digest::SHA1.base64digest Marshal.dump(transformations)
78
+ OpenSSL::Digest::SHA1.base64digest Marshal.dump(transformations)
79
79
  end
80
80
 
81
81
  private
data/config/routes.rb CHANGED
@@ -16,11 +16,7 @@ Rails.application.routes.draw do
16
16
  end
17
17
 
18
18
  direct :rails_representation do |representation, options|
19
- signed_blob_id = representation.blob.signed_id
20
- variation_key = representation.variation.key
21
- filename = representation.blob.filename
22
-
23
- route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
19
+ route_for(ActiveStorage.resolve_model_to_route, representation, options)
24
20
  end
25
21
 
26
22
  resolve("ActiveStorage::Variant") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
@@ -28,22 +24,24 @@ Rails.application.routes.draw do
28
24
  resolve("ActiveStorage::Preview") { |preview, options| route_for(ActiveStorage.resolve_model_to_route, preview, options) }
29
25
 
30
26
  direct :rails_blob do |blob, options|
31
- route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
27
+ route_for(ActiveStorage.resolve_model_to_route, blob, options)
32
28
  end
33
29
 
34
30
  resolve("ActiveStorage::Blob") { |blob, options| route_for(ActiveStorage.resolve_model_to_route, blob, options) }
35
31
  resolve("ActiveStorage::Attachment") { |attachment, options| route_for(ActiveStorage.resolve_model_to_route, attachment.blob, options) }
36
32
 
37
33
  direct :rails_storage_proxy do |model, options|
34
+ expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
35
+
38
36
  if model.respond_to?(:signed_id)
39
37
  route_for(
40
38
  :rails_service_blob_proxy,
41
- model.signed_id,
39
+ model.signed_id(expires_in: expires_in),
42
40
  model.filename,
43
41
  options
44
42
  )
45
43
  else
46
- signed_blob_id = model.blob.signed_id
44
+ signed_blob_id = model.blob.signed_id(expires_in: expires_in)
47
45
  variation_key = model.variation.key
48
46
  filename = model.blob.filename
49
47
 
@@ -58,15 +56,17 @@ Rails.application.routes.draw do
58
56
  end
59
57
 
60
58
  direct :rails_storage_redirect do |model, options|
59
+ expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
60
+
61
61
  if model.respond_to?(:signed_id)
62
62
  route_for(
63
63
  :rails_service_blob,
64
- model.signed_id,
64
+ model.signed_id(expires_in: expires_in),
65
65
  model.filename,
66
66
  options
67
67
  )
68
68
  else
69
- signed_blob_id = model.blob.signed_id
69
+ signed_blob_id = model.blob.signed_id(expires_in: expires_in)
70
70
  variation_key = model.variation.key
71
71
  filename = model.blob.filename
72
72
 
@@ -1,6 +1,9 @@
1
1
  class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
2
2
  def change
3
- create_table :active_storage_blobs do |t|
3
+ # Use Active Record's configured type for primary and foreign keys
4
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
5
+
6
+ create_table :active_storage_blobs, id: primary_key_type do |t|
4
7
  t.string :key, null: false
5
8
  t.string :filename, null: false
6
9
  t.string :content_type
@@ -8,28 +11,46 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
8
11
  t.string :service_name, null: false
9
12
  t.bigint :byte_size, null: false
10
13
  t.string :checksum, null: false
11
- t.datetime :created_at, null: false
14
+
15
+ if connection.supports_datetime_with_precision?
16
+ t.datetime :created_at, precision: 6, null: false
17
+ else
18
+ t.datetime :created_at, null: false
19
+ end
12
20
 
13
21
  t.index [ :key ], unique: true
14
22
  end
15
23
 
16
- create_table :active_storage_attachments do |t|
24
+ create_table :active_storage_attachments, id: primary_key_type do |t|
17
25
  t.string :name, null: false
18
- t.references :record, null: false, polymorphic: true, index: false
19
- t.references :blob, null: false
26
+ t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
27
+ t.references :blob, null: false, type: foreign_key_type
20
28
 
21
- t.datetime :created_at, null: false
29
+ if connection.supports_datetime_with_precision?
30
+ t.datetime :created_at, precision: 6, null: false
31
+ else
32
+ t.datetime :created_at, null: false
33
+ end
22
34
 
23
35
  t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
24
36
  t.foreign_key :active_storage_blobs, column: :blob_id
25
37
  end
26
38
 
27
- create_table :active_storage_variant_records do |t|
28
- t.belongs_to :blob, null: false, index: false
39
+ create_table :active_storage_variant_records, id: primary_key_type do |t|
40
+ t.belongs_to :blob, null: false, index: false, type: foreign_key_type
29
41
  t.string :variation_digest, null: false
30
42
 
31
43
  t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
32
44
  t.foreign_key :active_storage_blobs, column: :blob_id
33
45
  end
34
46
  end
47
+
48
+ private
49
+ def primary_and_foreign_key_types
50
+ config = Rails.configuration.generators
51
+ setting = config.options[config.orm][:primary_key_type]
52
+ primary_key_type = setting || :primary_key
53
+ foreign_key_type = setting || :bigint
54
+ [primary_key_type, foreign_key_type]
55
+ end
35
56
  end
@@ -1,11 +1,24 @@
1
1
  class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
2
2
  def change
3
- create_table :active_storage_variant_records do |t|
4
- t.belongs_to :blob, null: false, index: false
3
+ # Use Active Record's configured type for primary key
4
+ create_table :active_storage_variant_records, id: primary_key_type do |t|
5
+ t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
5
6
  t.string :variation_digest, null: false
6
7
 
7
8
  t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
8
9
  t.foreign_key :active_storage_blobs, column: :blob_id
9
10
  end
10
11
  end
12
+
13
+ private
14
+ def primary_key_type
15
+ config = Rails.configuration.generators
16
+ config.options[config.orm][:primary_key_type] || :primary_key
17
+ end
18
+
19
+ def blobs_primary_key_type
20
+ pkey_name = connection.primary_key(:active_storage_blobs)
21
+ pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }
22
+ pkey_column.bigint? ? :bigint : pkey_column.type
23
+ end
11
24
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ # Extracts duration (seconds) and bit_rate (bits/s) from an audio blob.
5
+ #
6
+ # Example:
7
+ #
8
+ # ActiveStorage::Analyzer::AudioAnalyzer.new(blob).metadata
9
+ # # => { duration: 5.0, bit_rate: 320340 }
10
+ #
11
+ # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
12
+ class Analyzer::AudioAnalyzer < Analyzer
13
+ def self.accept?(blob)
14
+ blob.audio?
15
+ end
16
+
17
+ def metadata
18
+ { duration: duration, bit_rate: bit_rate }.compact
19
+ end
20
+
21
+ private
22
+ def duration
23
+ duration = audio_stream["duration"]
24
+ Float(duration) if duration
25
+ end
26
+
27
+ def bit_rate
28
+ bit_rate = audio_stream["bit_rate"]
29
+ Integer(bit_rate) if bit_rate
30
+ end
31
+
32
+ def audio_stream
33
+ @audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
34
+ end
35
+
36
+ def streams
37
+ probe["streams"] || []
38
+ end
39
+
40
+ def probe
41
+ @probe ||= download_blob_to_tempfile { |file| probe_from(file) }
42
+ end
43
+
44
+ def probe_from(file)
45
+ instrument(File.basename(ffprobe_path)) do
46
+ IO.popen([ ffprobe_path,
47
+ "-print_format", "json",
48
+ "-show_streams",
49
+ "-show_format",
50
+ "-v", "error",
51
+ file.path
52
+ ]) do |output|
53
+ JSON.parse(output.read)
54
+ end
55
+ end
56
+ rescue Errno::ENOENT
57
+ logger.info "Skipping audio analysis because FFmpeg isn't installed"
58
+ {}
59
+ end
60
+
61
+ def ffprobe_path
62
+ ActiveStorage.paths[:ffprobe] || "ffprobe"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ # This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
5
+ # the {ImageMagick}[http://www.imagemagick.org] system library.
6
+ class Analyzer::ImageAnalyzer::ImageMagick < Analyzer::ImageAnalyzer
7
+ def self.accept?(blob)
8
+ super && ActiveStorage.variant_processor == :mini_magick
9
+ end
10
+
11
+ private
12
+ def read_image
13
+ download_blob_to_tempfile do |file|
14
+ require "mini_magick"
15
+
16
+ image = instrument("mini_magick") do
17
+ MiniMagick::Image.new(file.path)
18
+ end
19
+
20
+ if image.valid?
21
+ yield image
22
+ else
23
+ logger.info "Skipping image analysis because ImageMagick doesn't support the file"
24
+ {}
25
+ end
26
+ end
27
+ rescue LoadError
28
+ logger.info "Skipping image analysis because the mini_magick gem isn't installed"
29
+ {}
30
+ rescue MiniMagick::Error => error
31
+ logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
32
+ {}
33
+ end
34
+
35
+ def rotated_image?(image)
36
+ %w[ RightTop LeftBottom TopRight BottomLeft ].include?(image["%[orientation]"])
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ # This analyzer relies on the third-party {ruby-vips}[https://github.com/libvips/ruby-vips] gem. Ruby-vips requires
5
+ # the {libvips}[https://libvips.github.io/libvips/] system library.
6
+ class Analyzer::ImageAnalyzer::Vips < Analyzer::ImageAnalyzer
7
+ def self.accept?(blob)
8
+ super && ActiveStorage.variant_processor == :vips
9
+ end
10
+
11
+ private
12
+ def read_image
13
+ download_blob_to_tempfile do |file|
14
+ require "ruby-vips"
15
+
16
+ image = instrument("vips") do
17
+ ::Vips::Image.new_from_file(file.path, access: :sequential)
18
+ end
19
+
20
+ if valid_image?(image)
21
+ yield image
22
+ else
23
+ logger.info "Skipping image analysis because Vips doesn't support the file"
24
+ {}
25
+ end
26
+ end
27
+ rescue LoadError
28
+ logger.info "Skipping image analysis because the ruby-vips gem isn't installed"
29
+ {}
30
+ rescue ::Vips::Error => error
31
+ logger.error "Skipping image analysis due to an Vips error: #{error.message}"
32
+ {}
33
+ end
34
+
35
+ ROTATIONS = /Right-top|Left-bottom|Top-right|Bottom-left/
36
+ def rotated_image?(image)
37
+ ROTATIONS === image.get("exif-ifd0-Orientation")
38
+ rescue ::Vips::Error
39
+ false
40
+ end
41
+
42
+ def valid_image?(image)
43
+ image.avg
44
+ true
45
+ rescue ::Vips::Error
46
+ false
47
+ end
48
+ end
49
+ end