activestorage 0.1 → 5.2.0.beta1

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/README.md +94 -25
  4. data/app/assets/javascripts/activestorage.js +1 -0
  5. data/app/controllers/active_storage/blobs_controller.rb +16 -0
  6. data/app/controllers/active_storage/direct_uploads_controller.rb +23 -0
  7. data/app/controllers/active_storage/disk_controller.rb +51 -0
  8. data/app/controllers/active_storage/previews_controller.rb +12 -0
  9. data/app/controllers/active_storage/variants_controller.rb +16 -0
  10. data/app/javascript/activestorage/blob_record.js +54 -0
  11. data/app/javascript/activestorage/blob_upload.js +35 -0
  12. data/app/javascript/activestorage/direct_upload.js +42 -0
  13. data/app/javascript/activestorage/direct_upload_controller.js +67 -0
  14. data/app/javascript/activestorage/direct_uploads_controller.js +50 -0
  15. data/app/javascript/activestorage/file_checksum.js +53 -0
  16. data/app/javascript/activestorage/helpers.js +42 -0
  17. data/app/javascript/activestorage/index.js +11 -0
  18. data/app/javascript/activestorage/ujs.js +75 -0
  19. data/app/jobs/active_storage/analyze_job.rb +8 -0
  20. data/app/jobs/active_storage/base_job.rb +5 -0
  21. data/app/jobs/active_storage/purge_job.rb +11 -0
  22. data/app/models/active_storage/attachment.rb +35 -0
  23. data/app/models/active_storage/blob.rb +313 -0
  24. data/app/models/active_storage/filename.rb +73 -0
  25. data/app/models/active_storage/filename/parameters.rb +36 -0
  26. data/app/models/active_storage/preview.rb +90 -0
  27. data/app/models/active_storage/variant.rb +86 -0
  28. data/app/models/active_storage/variation.rb +67 -0
  29. data/config/routes.rb +43 -0
  30. data/lib/active_storage.rb +37 -2
  31. data/lib/active_storage/analyzer.rb +33 -0
  32. data/lib/active_storage/analyzer/image_analyzer.rb +36 -0
  33. data/lib/active_storage/analyzer/null_analyzer.rb +13 -0
  34. data/lib/active_storage/analyzer/video_analyzer.rb +79 -0
  35. data/lib/active_storage/attached.rb +28 -22
  36. data/lib/active_storage/attached/macros.rb +89 -16
  37. data/lib/active_storage/attached/many.rb +53 -21
  38. data/lib/active_storage/attached/one.rb +74 -20
  39. data/lib/active_storage/downloading.rb +26 -0
  40. data/lib/active_storage/engine.rb +72 -0
  41. data/lib/active_storage/gem_version.rb +17 -0
  42. data/lib/active_storage/log_subscriber.rb +52 -0
  43. data/lib/active_storage/previewer.rb +58 -0
  44. data/lib/active_storage/previewer/pdf_previewer.rb +17 -0
  45. data/lib/active_storage/previewer/video_previewer.rb +23 -0
  46. data/lib/active_storage/service.rb +112 -24
  47. data/lib/active_storage/service/azure_storage_service.rb +124 -0
  48. data/lib/active_storage/service/configurator.rb +32 -0
  49. data/lib/active_storage/service/disk_service.rb +103 -44
  50. data/lib/active_storage/service/gcs_service.rb +87 -29
  51. data/lib/active_storage/service/mirror_service.rb +38 -22
  52. data/lib/active_storage/service/s3_service.rb +83 -38
  53. data/lib/active_storage/version.rb +10 -0
  54. data/lib/tasks/activestorage.rake +4 -15
  55. metadata +64 -108
  56. data/.gitignore +0 -1
  57. data/Gemfile +0 -11
  58. data/Gemfile.lock +0 -235
  59. data/Rakefile +0 -11
  60. data/activestorage.gemspec +0 -21
  61. data/lib/active_storage/attachment.rb +0 -30
  62. data/lib/active_storage/blob.rb +0 -80
  63. data/lib/active_storage/disk_controller.rb +0 -28
  64. data/lib/active_storage/download.rb +0 -90
  65. data/lib/active_storage/filename.rb +0 -31
  66. data/lib/active_storage/migration.rb +0 -28
  67. data/lib/active_storage/purge_job.rb +0 -10
  68. data/lib/active_storage/railtie.rb +0 -56
  69. data/lib/active_storage/storage_services.yml +0 -27
  70. data/lib/active_storage/verified_key_with_expiration.rb +0 -24
  71. data/test/attachments_test.rb +0 -95
  72. data/test/blob_test.rb +0 -28
  73. data/test/database/create_users_migration.rb +0 -7
  74. data/test/database/setup.rb +0 -6
  75. data/test/disk_controller_test.rb +0 -34
  76. data/test/filename_test.rb +0 -36
  77. data/test/service/.gitignore +0 -1
  78. data/test/service/configurations-example.yml +0 -11
  79. data/test/service/disk_service_test.rb +0 -8
  80. data/test/service/gcs_service_test.rb +0 -20
  81. data/test/service/mirror_service_test.rb +0 -50
  82. data/test/service/s3_service_test.rb +0 -11
  83. data/test/service/shared_service_tests.rb +0 -68
  84. data/test/test_helper.rb +0 -28
  85. data/test/verified_key_with_expiration_test.rb +0 -19
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
4
+ # A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
5
+ class ActiveStorage::Filename
6
+ include Comparable
7
+
8
+ def initialize(filename)
9
+ @filename = filename
10
+ end
11
+
12
+ # Returns the part of the filename preceding any extension.
13
+ #
14
+ # ActiveStorage::Filename.new("racecar.jpg").base # => "racecar"
15
+ # ActiveStorage::Filename.new("racecar").base # => "racecar"
16
+ # ActiveStorage::Filename.new(".gitignore").base # => ".gitignore"
17
+ def base
18
+ File.basename @filename, extension_with_delimiter
19
+ end
20
+
21
+ # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at the
22
+ # beginning) with the dot that precedes it. If the filename has no extension, an empty string is returned.
23
+ #
24
+ # ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter # => ".jpg"
25
+ # ActiveStorage::Filename.new("racecar").extension_with_delimiter # => ""
26
+ # ActiveStorage::Filename.new(".gitignore").extension_with_delimiter # => ""
27
+ def extension_with_delimiter
28
+ File.extname @filename
29
+ end
30
+
31
+ # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at
32
+ # the beginning). If the filename has no extension, an empty string is returned.
33
+ #
34
+ # ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter # => "jpg"
35
+ # ActiveStorage::Filename.new("racecar").extension_without_delimiter # => ""
36
+ # ActiveStorage::Filename.new(".gitignore").extension_without_delimiter # => ""
37
+ def extension_without_delimiter
38
+ extension_with_delimiter.from(1).to_s
39
+ end
40
+
41
+ alias_method :extension, :extension_without_delimiter
42
+
43
+ # Returns the sanitized filename.
44
+ #
45
+ # ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg"
46
+ # ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg"
47
+ #
48
+ # Characters considered unsafe for storage (e.g. \, $, and the RTL override character) are replaced with a dash.
49
+ def sanitized
50
+ @filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
51
+ end
52
+
53
+ def parameters
54
+ Parameters.new self
55
+ end
56
+
57
+ # Returns the sanitized version of the filename.
58
+ def to_s
59
+ sanitized.to_s
60
+ end
61
+
62
+ def as_json(*)
63
+ to_s
64
+ end
65
+
66
+ def to_json
67
+ to_s
68
+ end
69
+
70
+ def <=>(other)
71
+ to_s.downcase <=> other.to_s.downcase
72
+ end
73
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveStorage::Filename::Parameters
4
+ attr_reader :filename
5
+
6
+ def initialize(filename)
7
+ @filename = filename
8
+ end
9
+
10
+ def combined
11
+ "#{ascii}; #{utf8}"
12
+ end
13
+
14
+ TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
15
+
16
+ def ascii
17
+ 'filename="' + percent_escape(I18n.transliterate(filename.sanitized), TRADITIONAL_ESCAPED_CHAR) + '"'
18
+ end
19
+
20
+ RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
21
+
22
+ def utf8
23
+ "filename*=UTF-8''" + percent_escape(filename.sanitized, RFC_5987_ESCAPED_CHAR)
24
+ end
25
+
26
+ def to_s
27
+ combined
28
+ end
29
+
30
+ private
31
+ def percent_escape(string, pattern)
32
+ string.gsub(pattern) do |char|
33
+ char.bytes.map { |byte| "%%%02X" % byte }.join
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Some non-image blobs can be previewed: that is, they can be presented as images. A video blob can be previewed by
4
+ # extracting its first frame, and a PDF blob can be previewed by extracting its first page.
5
+ #
6
+ # A previewer extracts a preview image from a blob. Active Storage provides previewers for videos and PDFs:
7
+ # ActiveStorage::Previewer::VideoPreviewer and ActiveStorage::Previewer::PDFPreviewer. Build custom previewers by
8
+ # subclassing ActiveStorage::Previewer and implementing the requisite methods. Consult the ActiveStorage::Previewer
9
+ # documentation for more details on what's required of previewers.
10
+ #
11
+ # To choose the previewer for a blob, Active Storage calls +accept?+ on each registered previewer in order. It uses the
12
+ # first previewer for which +accept?+ returns true when given the blob. In a Rails application, add or remove previewers
13
+ # by manipulating +Rails.application.config.active_storage.previewers+ in an initializer:
14
+ #
15
+ # Rails.application.config.active_storage.previewers
16
+ # # => [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
17
+ #
18
+ # # Add a custom previewer for Microsoft Office documents:
19
+ # Rails.application.config.active_storage.previewers << DOCXPreviewer
20
+ # # => [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer, DOCXPreviewer ]
21
+ #
22
+ # Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
23
+ #
24
+ # The built-in previewers rely on third-party system libraries:
25
+ #
26
+ # * {ffmpeg}[https://www.ffmpeg.org]
27
+ # * {mupdf}[https://mupdf.com]
28
+ #
29
+ # These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
30
+ # install and use third-party software, make sure you understand the licensing implications of doing so.
31
+ class ActiveStorage::Preview
32
+ class UnprocessedError < StandardError; end
33
+
34
+ attr_reader :blob, :variation
35
+
36
+ def initialize(blob, variation_or_variation_key)
37
+ @blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
38
+ end
39
+
40
+ # Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
41
+ #
42
+ # blob.preview(resize: "100x100").processed.service_url
43
+ #
44
+ # Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview
45
+ # image is stored with the blob, it is only generated once.
46
+ def processed
47
+ process unless processed?
48
+ self
49
+ end
50
+
51
+ # Returns the blob's attached preview image.
52
+ def image
53
+ blob.preview_image
54
+ end
55
+
56
+ # Returns the URL of the preview's variant on the service. Raises ActiveStorage::Preview::UnprocessedError if the
57
+ # preview has not been processed yet.
58
+ #
59
+ # This method synchronously processes a variant of the preview image, so do not call it in views. Instead, generate
60
+ # a stable URL that redirects to the short-lived URL returned by this method.
61
+ def service_url(**options)
62
+ if processed?
63
+ variant.service_url(options)
64
+ else
65
+ raise UnprocessedError
66
+ end
67
+ end
68
+
69
+ private
70
+ def processed?
71
+ image.attached?
72
+ end
73
+
74
+ def process
75
+ previewer.preview { |attachable| image.attach(attachable) }
76
+ end
77
+
78
+ def variant
79
+ ActiveStorage::Variant.new(image, variation).processed
80
+ end
81
+
82
+
83
+ def previewer
84
+ previewer_class.new(blob)
85
+ end
86
+
87
+ def previewer_class
88
+ ActiveStorage.previewers.detect { |klass| klass.accept?(blob) }
89
+ end
90
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Image blobs can have variants that are the result of a set of transformations applied to the original.
4
+ # These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
5
+ # original.
6
+ #
7
+ # Variants rely on {MiniMagick}[https://github.com/minimagick/minimagick] for the actual transformations
8
+ # of the file, so you must add <tt>gem "mini_magick"</tt> to your Gemfile if you wish to use variants.
9
+ #
10
+ # Note that to create a variant it's necessary to download the entire blob file from the service and load it
11
+ # into memory. The larger the image, the more memory is used. Because of this process, you also want to be
12
+ # considerate about when the variant is actually processed. You shouldn't be processing variants inline in a
13
+ # template, for example. Delay the processing to an on-demand controller, like the one provided in
14
+ # ActiveStorage::VariantsController.
15
+ #
16
+ # To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
17
+ # by Active Storage like so:
18
+ #
19
+ # <%= image_tag url_for(Current.user.avatar.variant(resize: "100x100")) %>
20
+ #
21
+ # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::VariantsController
22
+ # can then produce on-demand.
23
+ #
24
+ # When you do want to actually produce the variant needed, call +processed+. This will check that the variant
25
+ # has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
26
+ # the transformations, upload the variant to the service, and return itself again. Example:
27
+ #
28
+ # avatar.variant(resize: "100x100").processed.service_url
29
+ #
30
+ # This will create and process a variant of the avatar blob that's constrained to a height and width of 100.
31
+ # Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
32
+ #
33
+ # A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. You can
34
+ # combine as many as you like freely:
35
+ #
36
+ # avatar.variant(resize: "100x100", monochrome: true, flip: "-90")
37
+ class ActiveStorage::Variant
38
+ attr_reader :blob, :variation
39
+ delegate :service, to: :blob
40
+
41
+ def initialize(blob, variation_or_variation_key)
42
+ @blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
43
+ end
44
+
45
+ # Returns the variant instance itself after it's been processed or an existing processing has been found on the service.
46
+ def processed
47
+ process unless processed?
48
+ self
49
+ end
50
+
51
+ # Returns a combination key of the blob and the variation that together identifies a specific variant.
52
+ def key
53
+ "variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}"
54
+ end
55
+
56
+ # Returns the URL of the variant on the service. This URL is intended to be short-lived for security and not used directly
57
+ # with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL.
58
+ # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And
59
+ # it allows permanent URLs that redirect to the +service_url+ to be cached in the view.
60
+ #
61
+ # Use <tt>url_for(variant)</tt> (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL
62
+ # for a variant that points to the ActiveStorage::VariantsController, which in turn will use this +service_call+ method
63
+ # for its redirection.
64
+ def service_url(expires_in: service.url_expires_in, disposition: :inline)
65
+ service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type
66
+ end
67
+
68
+ # Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
69
+ def image
70
+ self
71
+ end
72
+
73
+ private
74
+ def processed?
75
+ service.exist?(key)
76
+ end
77
+
78
+ def process
79
+ service.upload key, transform(service.download(blob.key))
80
+ end
81
+
82
+ def transform(io)
83
+ require "mini_magick"
84
+ File.open MiniMagick::Image.read(io).tap { |image| variation.transform(image) }.path
85
+ end
86
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A set of transformations that can be applied to a blob to create a variant. This class is exposed via
4
+ # the ActiveStorage::Blob#variant method and should rarely be used directly.
5
+ #
6
+ # In case you do need to use this directly, it's instantiated using a hash of transformations where
7
+ # the key is the command and the value is the arguments. Example:
8
+ #
9
+ # ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90")
10
+ #
11
+ # A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
12
+ class ActiveStorage::Variation
13
+ attr_reader :transformations
14
+
15
+ class << self
16
+ # Returns a Variation instance based on the given variator. If the variator is a Variation, it is
17
+ # returned unmodified. If it is a String, it is passed to ActiveStorage::Variation.decode. Otherwise,
18
+ # it is assumed to be a transformations Hash and is passed directly to the constructor.
19
+ def wrap(variator)
20
+ case variator
21
+ when self
22
+ variator
23
+ when String
24
+ decode variator
25
+ else
26
+ new variator
27
+ end
28
+ end
29
+
30
+ # Returns a Variation instance with the transformations that were encoded by +encode+.
31
+ def decode(key)
32
+ new ActiveStorage.verifier.verify(key, purpose: :variation)
33
+ end
34
+
35
+ # Returns a signed key for the +transformations+, which can be used to refer to a specific
36
+ # variation in a URL or combined key (like <tt>ActiveStorage::Variant#key</tt>).
37
+ def encode(transformations)
38
+ ActiveStorage.verifier.generate(transformations, purpose: :variation)
39
+ end
40
+ end
41
+
42
+ def initialize(transformations)
43
+ @transformations = transformations
44
+ end
45
+
46
+ # Accepts an open MiniMagick image instance, like what's returned by <tt>MiniMagick::Image.read(io)</tt>,
47
+ # and performs the +transformations+ against it. The transformed image instance is then returned.
48
+ def transform(image)
49
+ transformations.each do |(method, argument)|
50
+ if eligible_argument?(argument)
51
+ image.public_send(method, argument)
52
+ else
53
+ image.public_send(method)
54
+ end
55
+ end
56
+ end
57
+
58
+ # Returns a signed key for all the +transformations+ that this variation was instantiated with.
59
+ def key
60
+ self.class.encode(transformations)
61
+ end
62
+
63
+ private
64
+ def eligible_argument?(argument)
65
+ argument.present? && argument != true
66
+ end
67
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob, internal: true
5
+
6
+ direct :rails_blob do |blob, options|
7
+ route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
8
+ end
9
+
10
+ resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob) }
11
+ resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
12
+
13
+
14
+ get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation, internal: true
15
+
16
+ direct :rails_variant do |variant, options|
17
+ signed_blob_id = variant.blob.signed_id
18
+ variation_key = variant.variation.key
19
+ filename = variant.blob.filename
20
+
21
+ route_for(:rails_blob_variation, signed_blob_id, variation_key, filename, options)
22
+ end
23
+
24
+ resolve("ActiveStorage::Variant") { |variant, options| route_for(:rails_variant, variant, options) }
25
+
26
+
27
+ get "/rails/active_storage/previews/:signed_blob_id/:variation_key/*filename" => "active_storage/previews#show", as: :rails_blob_preview, internal: true
28
+
29
+ direct :rails_preview do |preview, options|
30
+ signed_blob_id = preview.blob.signed_id
31
+ variation_key = preview.variation.key
32
+ filename = preview.blob.filename
33
+
34
+ route_for(:rails_blob_preview, signed_blob_id, variation_key, filename, options)
35
+ end
36
+
37
+ resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_preview, preview, options) }
38
+
39
+
40
+ get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service, internal: true
41
+ put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service, internal: true
42
+ post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads, internal: true
43
+ end
@@ -1,9 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Copyright (c) 2017 David Heinemeier Hansson
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
1
26
  require "active_record"
2
- require "active_storage/railtie" if defined?(Rails)
27
+ require "active_support"
28
+ require "active_support/rails"
29
+ require "active_storage/version"
3
30
 
4
31
  module ActiveStorage
5
32
  extend ActiveSupport::Autoload
6
33
 
7
- autoload :Blob
34
+ autoload :Attached
8
35
  autoload :Service
36
+ autoload :Previewer
37
+ autoload :Analyzer
38
+
39
+ mattr_accessor :logger
40
+ mattr_accessor :verifier
41
+ mattr_accessor :queue
42
+ mattr_accessor :previewers, default: []
43
+ mattr_accessor :analyzers, default: []
9
44
  end