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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +2 -22
- data/Rakefile +9 -1
- data/app/assets/javascripts/retry.js +11 -0
- data/app/assets/stylesheets/retry.css +98 -0
- data/app/controllers/active_storage/async_variants/states_controller.rb +62 -0
- data/app/views/active_storage/async_variants/states/_failed.html.erb +43 -0
- data/app/views/active_storage/async_variants/states/_pending.html.erb +1 -0
- data/app/views/active_storage/async_variants/states/_processed.html.erb +5 -0
- data/app/views/active_storage/async_variants/states/_processing.html.erb +9 -0
- data/app/views/active_storage/async_variants/states/show.html.erb +10 -0
- data/config/routes.rb +9 -0
- data/features/retry_flow.feature +20 -0
- data/features/retry_visibility.feature +23 -0
- data/features/state_rendering.feature +22 -0
- data/features/step_definitions/dummy_steps.rb +116 -0
- data/features/support/env.rb +28 -0
- data/gemfiles/rails_7.2.gemfile +4 -0
- data/gemfiles/rails_8.0.gemfile +4 -0
- data/gemfiles/rails_8.1.gemfile +4 -0
- data/lib/active_storage/async_variants/asset_tag_helper_extension.rb +38 -56
- data/lib/active_storage/async_variants/helper.rb +59 -0
- data/lib/active_storage/async_variants/preview_extension.rb +35 -32
- data/lib/active_storage/async_variants/variant_with_record_extension.rb +40 -30
- data/lib/active_storage/async_variants/version.rb +1 -1
- data/lib/active_storage/async_variants.rb +26 -15
- metadata +44 -4
- data/app/assets/javascripts/active_storage_async_variants.js +0 -169
- 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
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
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-
|
|
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/
|
|
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
|