active_storage-async_variants 0.3.1 → 0.5.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +2 -22
  4. data/Rakefile +9 -1
  5. data/app/assets/javascripts/retry.js +11 -0
  6. data/app/assets/stylesheets/retry.css +98 -0
  7. data/app/controllers/active_storage/async_variants/states_controller.rb +62 -0
  8. data/app/views/active_storage/async_variants/states/_failed.html.erb +43 -0
  9. data/app/views/active_storage/async_variants/states/_pending.html.erb +1 -0
  10. data/app/views/active_storage/async_variants/states/_processed.html.erb +5 -0
  11. data/app/views/active_storage/async_variants/states/_processing.html.erb +9 -0
  12. data/app/views/active_storage/async_variants/states/show.html.erb +10 -0
  13. data/config/routes.rb +9 -0
  14. data/features/retry_flow.feature +20 -0
  15. data/features/retry_visibility.feature +23 -0
  16. data/features/state_rendering.feature +22 -0
  17. data/features/step_definitions/dummy_steps.rb +116 -0
  18. data/features/support/env.rb +28 -0
  19. data/gemfiles/rails_7.2.gemfile +4 -0
  20. data/gemfiles/rails_8.0.gemfile +4 -0
  21. data/gemfiles/rails_8.1.gemfile +4 -0
  22. data/lib/active_storage/async_variants/asset_tag_helper_extension.rb +38 -56
  23. data/lib/active_storage/async_variants/helper.rb +59 -0
  24. data/lib/active_storage/async_variants/preview_extension.rb +35 -32
  25. data/lib/active_storage/async_variants/variant_with_record_extension.rb +40 -30
  26. data/lib/active_storage/async_variants/version.rb +1 -1
  27. data/lib/active_storage/async_variants.rb +26 -15
  28. metadata +44 -4
  29. data/app/assets/javascripts/active_storage_async_variants.js +0 -169
  30. data/lib/active_storage/async_variants/representations_redirect_controller_extension.rb +0 -26
@@ -2,76 +2,58 @@
2
2
 
3
3
  module ActiveStorage
4
4
  module AsyncVariants
5
+ # Prepended onto ActionView::Helpers::AssetTagHelper. Adds `async:` and
6
+ # `direct:` options to image_tag/video_tag; routes unprocessed variants
7
+ # through a <turbo-frame> whose body lives in StatesController#show.
5
8
  module AssetTagHelperExtension
9
+ include ActiveStorage::AsyncVariants::Helper
10
+
6
11
  def image_tag(source, options = {})
7
- options = options.symbolize_keys
8
- async = options.delete(:async)
9
- direct = options.delete(:direct)
10
- return super if !async && !direct
11
- variant = AssetTagHelperExtension.coerce_variant!(source)
12
- src = AssetTagHelperExtension.resolve_src(variant, direct: direct)
13
- AssetTagHelperExtension.apply_async_data!(options, variant: variant, direct: direct) if async
14
- super(src, options)
12
+ async_variant_tag(:image, [source], options) do |urls, opts|
13
+ super(urls.first, opts)
14
+ end
15
15
  end
16
16
 
17
17
  def video_tag(*sources)
18
- options = sources.extract_options!.symbolize_keys
19
- async = options.delete(:async)
20
- direct = options.delete(:direct)
21
- return super(*sources, options) if !async && !direct
22
- variant = AssetTagHelperExtension.coerce_variant!(sources.first)
23
- sources[0] = AssetTagHelperExtension.resolve_src(variant, direct: direct)
24
- AssetTagHelperExtension.apply_async_data!(options, variant: variant, direct: direct) if async
25
- super(*sources, options)
18
+ options = sources.extract_options!
19
+ async_variant_tag(:video, sources, options) do |urls, opts|
20
+ super(*urls, opts)
21
+ end
26
22
  end
27
23
 
28
- class << self
29
- def coerce_variant!(source)
30
- unless source.is_a?(ActiveStorage::VariantWithRecord) || source.is_a?(ActiveStorage::Preview)
31
- raise ArgumentError, "image_tag/video_tag with async:/direct: requires an ActiveStorage::VariantWithRecord or Preview, got #{source.class}"
32
- end
33
- source
34
- end
24
+ private
35
25
 
36
- def resolve_src(variant, direct:)
37
- if direct && variant.async_state == "processed"
38
- direct_url(variant)
39
- else
40
- # Idempotent: enqueue ProcessJob if no record exists yet so a
41
- # never-touched variant doesn't sit pending forever. Gated on
42
- # bucket-backed services -- on Disk/Test the gem defers to
43
- # vanilla ActiveStorage, and #processed would run a synchronous
44
- # transform (e.g. vips on an mp4) that the external transformer
45
- # is supposed to handle.
46
- variant.processed if variant.blob.bucket_backed?
47
- polymorphic_url(variant)
48
- end
49
- end
26
+ def async_variant_tag(kind, sources, options)
27
+ options = options.symbolize_keys
28
+ async = options.delete(:async)
29
+ direct = options.delete(:direct)
30
+ return yield(sources, options) if !async && !direct
50
31
 
51
- def apply_async_data!(options, variant:, direct:)
52
- data = (options[:data] || {}).symbolize_keys
53
- controllers = data[:controller].to_s.split
54
- controllers << "async-variant" unless controllers.include?("async-variant")
55
- data[:controller] = controllers.join(" ")
56
- data[:async_variant_src_value] = polymorphic_url(variant)
57
- data[:async_variant_state_value] = variant.async_state
58
- data[:async_variant_direct_value] = direct_url(variant) if direct && variant.async_state == "processed"
59
- options[:data] = data
60
- end
32
+ variant, *rest = sources
33
+ assert_async_variant!(variant)
61
34
 
62
- def direct_url(variant)
63
- if cdn = ActiveStorage::AsyncVariants.cdn_host
64
- "#{cdn}/#{variant.key}"
65
- else
66
- variant.image.url
67
- end
35
+ if async && !async_variant_processed_inline?(variant)
36
+ async_variant_turbo_frame(variant, kind:, direct:, html_options: options)
37
+ else
38
+ yield [async_variant_resolved_src(variant, direct:), *rest], options
68
39
  end
40
+ end
69
41
 
70
- def polymorphic_url(variant)
71
- url_options = ActiveStorage::Current.url_options || Rails.application.routes.default_url_options
72
- Rails.application.routes.url_helpers.polymorphic_url(variant, **url_options)
42
+ def assert_async_variant!(source)
43
+ unless source.is_a?(ActiveStorage::VariantWithRecord) || source.is_a?(ActiveStorage::Preview)
44
+ raise ArgumentError, "image_tag/video_tag with async:/direct: requires an ActiveStorage::VariantWithRecord or Preview, got #{source.class}"
73
45
  end
74
46
  end
47
+
48
+ def async_variant_turbo_frame(variant, kind:, direct:, html_options:)
49
+ content_tag(
50
+ :"turbo-frame",
51
+ "",
52
+ id: async_variant_frame_id(variant),
53
+ src: async_variant_frame_src(variant, kind: kind, direct: direct, html_options: html_options),
54
+ refresh: "morph",
55
+ )
56
+ end
75
57
  end
76
58
  end
77
59
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module AsyncVariants
5
+ # View/controller helper methods. Included in the AssetTagHelperExtension
6
+ # (so image_tag/video_tag can use them) and added to StatesController via
7
+ # `helper ActiveStorage::AsyncVariants::Helper` (so the state partials can
8
+ # use them).
9
+ module Helper
10
+ # In test with non-bucket-backed services, the gem defers to vanilla
11
+ # ActiveStorage (synchronous vips transform) -- inline rendering keeps
12
+ # those environments simple. Otherwise, only inline a normal <img> when
13
+ # the variant has reached the processed terminal state.
14
+ def async_variant_processed_inline?(variant)
15
+ !variant.blob.bucket_backed? || variant.async_state == "processed"
16
+ end
17
+
18
+ def async_variant_resolved_src(variant, direct:)
19
+ if direct && variant.async_state == "processed"
20
+ async_variant_direct_url(variant)
21
+ else
22
+ variant.processed if variant.blob.bucket_backed?
23
+ async_variant_representation_path(variant)
24
+ end
25
+ end
26
+
27
+ def async_variant_frame_id(variant)
28
+ digest = variant.variation.digest.gsub(/[^a-zA-Z0-9_-]/, "")
29
+ "async-variant-#{variant.blob.id}-#{digest}"
30
+ end
31
+
32
+ def async_variant_frame_src(variant, kind:, direct:, html_options: {})
33
+ async_variant_state_path(
34
+ signed_blob_id: variant.blob.signed_id,
35
+ variation_key: variant.variation.key,
36
+ kind:,
37
+ direct:,
38
+ opts: html_options.slice(*PASS_THROUGH_HTML_OPTIONS),
39
+ )
40
+ end
41
+
42
+ def async_variant_direct_url(variant)
43
+ if cdn = ActiveStorage::AsyncVariants.cdn_host
44
+ "#{cdn}/#{variant.key}"
45
+ else
46
+ variant.image.url
47
+ end
48
+ end
49
+
50
+ def async_variant_representation_path(variant)
51
+ Rails.application.routes.url_helpers.rails_blob_representation_path(
52
+ variant.blob.signed_id,
53
+ variant.variation.key,
54
+ variant.blob.filename.to_s,
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
@@ -3,18 +3,27 @@
3
3
  module ActiveStorage
4
4
  module AsyncVariants
5
5
  module PreviewExtension
6
- # Enqueue (or no-op if already done) the same ProcessJob the
7
- # VariantWithRecord path uses, so Preview-side and VariantWithRecord-side
8
- # processing converge on a single record-and-job machinery rather than
9
- # the gem's earlier two-path design (one writing to preview_image's
10
- # variant_records, one to the original blob's).
6
+ # Block vanilla ActiveStorage's synchronous preview transform on
7
+ # async-backed services; rely on the auto-enqueue (AttachmentExtension)
8
+ # path -- and the public #enqueue! -- to dispatch ProcessJob.
11
9
  def processed
12
- if async_preview?
13
- enqueue_async_preview unless preview_variant_processed?
14
- self
15
- else
16
- super
10
+ async_preview? ? self : super
11
+ end
12
+
13
+ def enqueue!
14
+ if result = find_named_async_variant
15
+ attachment, variant_name, _ = result
16
+
17
+ blob.variant_records.create!(
18
+ variation_digest: variation.digest,
19
+ state: "pending",
20
+ )
21
+ ActiveStorage::AsyncVariants::ProcessJob.perform_later(
22
+ attachment.record, attachment.name, variant_name.to_s,
23
+ )
17
24
  end
25
+ rescue ActiveRecord::RecordNotUnique
26
+ # another caller (or a leftover record) wins; their job handles it
18
27
  end
19
28
 
20
29
  def processed?
@@ -52,14 +61,29 @@ module ActiveStorage
52
61
  # Variations rebuilt from the redirect URL only carry transformations --
53
62
  # :transformer / :processing / :failed are stripped at Variation#initialize
54
63
  # and not embedded in the URL key. Recover them via the digest-keyed
55
- # registry that VariationExtension warms on every view-side variant call.
64
+ # registry that VariationExtension warms on every view-side variant call,
65
+ # or fall back to scanning attached named variants when the registry is
66
+ # cold (autoloader hasn't touched the consumer model yet).
56
67
  def resolved_async_options
57
68
  @resolved_async_options ||=
58
69
  variation.async_options.presence ||
59
70
  ActiveStorage::AsyncVariants::Registry[variation.digest] ||
71
+ find_named_async_variant&.dig(2) ||
60
72
  {}
61
73
  end
62
74
 
75
+ def find_named_async_variant
76
+ target = variation.transformations.to_json
77
+ blob.attachments.each do |attachment|
78
+ attachment.send(:named_variants).each do |name, _|
79
+ candidate = attachment.variant(name.to_sym)
80
+ next unless candidate.variation.transformations.to_json == target
81
+ return [attachment, name, candidate.variation.async_options] if candidate.variation.async_options[:transformer].present?
82
+ end
83
+ end
84
+ nil
85
+ end
86
+
63
87
  def preview_variant_processed?
64
88
  find_preview_variant_record&.state == "processed"
65
89
  end
@@ -71,27 +95,6 @@ module ActiveStorage
71
95
  blob.variant_records.find_by(variation_digest: variation.digest)
72
96
  end
73
97
 
74
- # Delegate to the named-variant VariantWithRecord so we go through the
75
- # exact same enqueue_processing + ProcessJob machinery as direct
76
- # variant calls. Skips silently if no matching named variant exists,
77
- # which can happen for raw transformations that don't correspond to
78
- # any declared async variant. Also skipped on non-bucket services,
79
- # where the gem defers to vanilla ActiveStorage and dispatching here
80
- # would synchronously transform via vips (broken for video blobs).
81
- def enqueue_async_preview
82
- return unless blob.bucket_backed?
83
- target = variation.transformations.to_json
84
- blob.attachments.each do |attachment|
85
- attachment.send(:named_variants).each do |name, _|
86
- candidate = attachment.variant(name.to_sym)
87
- next unless candidate.variation.transformations.to_json == target
88
- next unless candidate.variation.async_options[:processing].present?
89
- candidate.processed
90
- return
91
- end
92
- end
93
- end
94
-
95
98
  def fallback_preview_url(...)
96
99
  case active_fallback
97
100
  when :original then blob.url(...)
@@ -3,13 +3,27 @@
3
3
  module ActiveStorage
4
4
  module AsyncVariants
5
5
  module VariantWithRecordExtension
6
+ # Block vanilla ActiveStorage's synchronous transform on bucket-backed
7
+ # services; rely on the auto-enqueue (AttachmentExtension) path -- and the
8
+ # public #enqueue! -- to dispatch ProcessJob.
6
9
  def processed
7
- if blob.bucket_backed?
8
- enqueue_processing unless processed? || processing?
9
- self
10
- else
11
- super
10
+ blob.bucket_backed? ? self : super
11
+ end
12
+
13
+ def enqueue!
14
+ if result = find_named_async_variant
15
+ attachment, variant_name, _ = result
16
+
17
+ blob.variant_records.create!(
18
+ variation_digest: variation.digest,
19
+ state: "pending",
20
+ )
21
+ ActiveStorage::AsyncVariants::ProcessJob.perform_later(
22
+ attachment.record, attachment.name, variant_name.to_s,
23
+ )
12
24
  end
25
+ rescue ActiveRecord::RecordNotUnique
26
+ # another caller (or a leftover record) wins; their job handles it
13
27
  end
14
28
 
15
29
  def url(...)
@@ -62,6 +76,7 @@ module ActiveStorage
62
76
  @resolved_async_options ||=
63
77
  variation.async_options.presence ||
64
78
  ActiveStorage::AsyncVariants::Registry[variation.digest] ||
79
+ find_named_async_variant&.dig(2) ||
65
80
  {}
66
81
  end
67
82
 
@@ -73,33 +88,29 @@ module ActiveStorage
73
88
  end
74
89
  end
75
90
 
76
- def enqueue_processing
77
- result = find_named_async_variant
78
- return unless result
79
- attachment, variant_name, _ = result
80
-
81
- return if async_record
82
-
83
- blob.variant_records.create!(
84
- variation_digest: variation.digest,
85
- state: "pending",
86
- )
87
-
88
- ActiveStorage::AsyncVariants::ProcessJob.perform_later(
89
- attachment.record, attachment.name, variant_name.to_s,
90
- )
91
- rescue ActiveRecord::RecordNotUnique
92
- # another caller won the race; their job will handle processing
93
- end
94
-
95
- # Cold-path scan: only used by enqueue_processing, which needs the
96
- # (attachment.record, attachment.name, variant_name) tuple to dispatch
97
- # ProcessJob -- more than the digest registry stores. Hot-path URL
98
- # resolution goes through Registry, not this method.
91
+ # Cold-path scan: used by enqueue! (which needs the attachment +
92
+ # variant_name to dispatch ProcessJob) and by resolved_async_options as
93
+ # a fallback when the Registry is cold (e.g. in dev, when a
94
+ # RepresentationsRedirectController request hits a worker that hasn't
95
+ # autoloaded the consumer model yet).
96
+ #
97
+ # Walks one level through preview_image attachments so a request for
98
+ # a Variant of a video's extracted preview frame can still find the
99
+ # named variant declared on the parent record's source-video field.
99
100
  def find_named_async_variant
100
101
  target = variation.transformations.to_json
102
+ scan_for_named_variant(blob, target)
103
+ end
104
+
105
+ def scan_for_named_variant(blob_to_scan, target, depth: 0)
106
+ blob_to_scan.attachments.each do |attachment|
107
+ if attachment.name == "preview_image" && attachment.record_type == "ActiveStorage::Blob" && depth < 1
108
+ source = ActiveStorage::Blob.find_by(id: attachment.record_id)
109
+ result = source && scan_for_named_variant(source, target, depth: depth + 1)
110
+ return result if result
111
+ next
112
+ end
101
113
 
102
- blob.attachments.each do |attachment|
103
114
  attachment.send(:named_variants).each do |name, _|
104
115
  candidate = attachment.variant(name.to_sym)
105
116
  if candidate.variation.transformations.to_json == target
@@ -107,7 +118,6 @@ module ActiveStorage
107
118
  end
108
119
  end
109
120
  end
110
-
111
121
  nil
112
122
  end
113
123
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveStorage
4
4
  module AsyncVariants
5
- VERSION = "0.3.1"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "turbo-rails"
4
+ require "isolate_assets"
3
5
  require_relative "async_variants/version"
6
+ require_relative "async_variants/helper"
4
7
  require_relative "async_variants/transformer"
5
8
  require_relative "async_variants/registry"
6
9
  require_relative "async_variants/blob_extension"
@@ -11,22 +14,34 @@ require_relative "async_variants/preview_extension"
11
14
  require_relative "async_variants/attachment_extension"
12
15
  require_relative "async_variants/reflection_extension"
13
16
  require_relative "async_variants/process_job"
14
- require_relative "async_variants/representations_redirect_controller_extension"
15
17
  require_relative "async_variants/asset_tag_helper_extension"
16
18
 
17
19
  module ActiveStorage
18
20
  module AsyncVariants
21
+ # HTML attributes round-tripped through the state-endpoint URL so the
22
+ # eventual processed-state render can apply them to the inner <img>/<video>.
23
+ PASS_THROUGH_HTML_OPTIONS = %i[alt width height controls autoplay preload].freeze
24
+
19
25
  mattr_accessor :cdn_host
26
+ mattr_accessor :retry_visible_proc, default: ->(_view) { false }
20
27
 
21
- class Engine < ::Rails::Engine
22
- # :nocov:
23
- initializer "active_storage_async_variants.assets" do |app|
24
- if app.config.respond_to?(:assets)
25
- app.config.assets.precompile += %w[active_storage_async_variants.js]
26
- end
27
- end
28
- # :nocov:
28
+ # Lets the host app plug its auth chain (and thus `current_user`) into the
29
+ # gem's StatesController. Set to a string so resolution is deferred until
30
+ # the host's class is autoloadable. Defaults to ActionController::Base.
31
+ mattr_accessor :parent_controller, default: "ActionController::Base"
32
+
33
+ # Gates the failed-state retry affordance; the block runs in the view context.
34
+ def self.retry_visible_if(&block)
35
+ self.retry_visible_proc = block
36
+ end
29
37
 
38
+ def self.retry_visible?(view)
39
+ !!view.instance_exec(&retry_visible_proc)
40
+ rescue StandardError
41
+ false
42
+ end
43
+
44
+ class Engine < ::Rails::Engine
30
45
  # Prepend the core model/reflection extensions before eager_load runs
31
46
  # so that models' has_X_attached blocks (and the Variation.wrap calls
32
47
  # they trigger via reflection.variant) go through our hooks. The
@@ -46,9 +61,6 @@ module ActiveStorage
46
61
  # the first one loads.
47
62
  ActiveStorage::AsyncVariants.prepend_model_extensions!
48
63
 
49
- ActiveStorage::Representations::RedirectController.prepend(
50
- ActiveStorage::AsyncVariants::RepresentationsRedirectControllerExtension
51
- )
52
64
  ActionView::Helpers::AssetTagHelper.prepend(
53
65
  ActiveStorage::AsyncVariants::AssetTagHelperExtension
54
66
  )
@@ -56,9 +68,6 @@ module ActiveStorage
56
68
  end
57
69
 
58
70
  def self.prepend_model_extensions!
59
- return if @model_extensions_prepended
60
- @model_extensions_prepended = true
61
-
62
71
  require "active_storage/reflection"
63
72
  ActiveStorage::Reflection::HasAttachedReflection.prepend(
64
73
  ActiveStorage::AsyncVariants::ReflectionExtension
@@ -83,6 +92,8 @@ module ActiveStorage
83
92
  )
84
93
  end
85
94
 
95
+ Assets = IsolateAssets.register(namespace: self, engine: Engine, route_name: :async_variant_asset)
96
+
86
97
  def self.callback_token_for(variant_record)
87
98
  ActiveStorage.verifier.generate(variant_record.id, purpose: :async_variant_callback)
88
99
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_storage-async_variants
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-05-29 00:00:00.000000000 Z
10
+ date: 2026-06-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activestorage
@@ -23,6 +23,34 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '7.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: turbo-rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: isolate_assets
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0.4'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0.4'
26
54
  email:
27
55
  - micah@botandrose.com
28
56
  executables: []
@@ -38,9 +66,21 @@ files:
38
66
  - LICENSE.txt
39
67
  - README.md
40
68
  - Rakefile
41
- - app/assets/javascripts/active_storage_async_variants.js
69
+ - app/assets/javascripts/retry.js
70
+ - app/assets/stylesheets/retry.css
42
71
  - app/controllers/active_storage/async_variants/callbacks_controller.rb
72
+ - app/controllers/active_storage/async_variants/states_controller.rb
73
+ - app/views/active_storage/async_variants/states/_failed.html.erb
74
+ - app/views/active_storage/async_variants/states/_pending.html.erb
75
+ - app/views/active_storage/async_variants/states/_processed.html.erb
76
+ - app/views/active_storage/async_variants/states/_processing.html.erb
77
+ - app/views/active_storage/async_variants/states/show.html.erb
43
78
  - config/routes.rb
79
+ - features/retry_flow.feature
80
+ - features/retry_visibility.feature
81
+ - features/state_rendering.feature
82
+ - features/step_definitions/dummy_steps.rb
83
+ - features/support/env.rb
44
84
  - gemfiles/rails_7.2.gemfile
45
85
  - gemfiles/rails_8.0.gemfile
46
86
  - gemfiles/rails_8.1.gemfile
@@ -48,11 +88,11 @@ files:
48
88
  - lib/active_storage/async_variants/asset_tag_helper_extension.rb
49
89
  - lib/active_storage/async_variants/attachment_extension.rb
50
90
  - lib/active_storage/async_variants/blob_extension.rb
91
+ - lib/active_storage/async_variants/helper.rb
51
92
  - lib/active_storage/async_variants/preview_extension.rb
52
93
  - lib/active_storage/async_variants/process_job.rb
53
94
  - lib/active_storage/async_variants/reflection_extension.rb
54
95
  - lib/active_storage/async_variants/registry.rb
55
- - lib/active_storage/async_variants/representations_redirect_controller_extension.rb
56
96
  - lib/active_storage/async_variants/transformer.rb
57
97
  - lib/active_storage/async_variants/variant_record_extension.rb
58
98
  - lib/active_storage/async_variants/variant_with_record_extension.rb