active_storage-async_variants 0.1.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 +7 -0
- data/.github/workflows/ci.yml +27 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Appraisals +11 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +228 -0
- data/Rakefile +8 -0
- data/app/controllers/active_storage/async_variants/callbacks_controller.rb +25 -0
- data/config/routes.rb +7 -0
- data/gemfiles/rails_7.2.gemfile +13 -0
- data/gemfiles/rails_7.2.gemfile.lock +269 -0
- data/gemfiles/rails_8.0.gemfile +13 -0
- data/gemfiles/rails_8.0.gemfile.lock +266 -0
- data/gemfiles/rails_8.1.gemfile +13 -0
- data/gemfiles/rails_8.1.gemfile.lock +269 -0
- data/lib/active_storage/async_variants/attachment_extension.rb +24 -0
- data/lib/active_storage/async_variants/process_job.rb +86 -0
- data/lib/active_storage/async_variants/transformer.rb +19 -0
- data/lib/active_storage/async_variants/variant_with_record_extension.rb +60 -0
- data/lib/active_storage/async_variants/variation_extension.rb +27 -0
- data/lib/active_storage/async_variants/version.rb +7 -0
- data/lib/active_storage/async_variants.rb +38 -0
- data/log/.gitkeep +0 -0
- data/sig/active_storage/async_variants.rbs +6 -0
- metadata +82 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4395d49e7c04b9206145e2c8dbe0bf3f4466ed736054ec5e9fc3b8fc7dea3a1c
|
|
4
|
+
data.tar.gz: 1cf4f1090be29b6ff6a7a50be05ecaa2bcc79f50de9a203ec26d99c9d8e58fc8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1d696905633e0039f8489377d41ea120fd89d5fccfcadbd8872981ea916d9032395f5f66091eba170de40f03547b7b93c90457e6796f15868ae3e7d3ecf937c4
|
|
7
|
+
data.tar.gz: 21c13be811dc6c6b9b19123f6ef8c9c23c1e2cbc0360ee9f79a88b4bf83d023f514d61964e75b749c2b073ad19e7db52f878a2b55de8c90b4018ff831a4d3f2d
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
ruby: ["3.3", "3.4", "4.0"]
|
|
16
|
+
rails: ["rails-7.2", "rails-8.0", "rails-8.1"]
|
|
17
|
+
exclude:
|
|
18
|
+
- ruby: "4.0"
|
|
19
|
+
rails: "rails-7.2"
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: ruby/setup-ruby@v1
|
|
23
|
+
with:
|
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
|
25
|
+
bundler-cache: true
|
|
26
|
+
- run: bundle exec appraisal ${{ matrix.rails }} bundle install
|
|
27
|
+
- run: bundle exec appraisal ${{ matrix.rails }} rake
|
data/.ruby-gemset
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
active_storage-async_variants
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby-3.4.2
|
data/Appraisals
ADDED
data/CHANGELOG.md
ADDED
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What This Is
|
|
6
|
+
|
|
7
|
+
A Rails engine gem that extends Active Storage with async-safe variant processing. Solves the problem where slow transformations (e.g., video transcoding) block requests or fail silently. The `fallback:` option on a variant definition opts it into async processing.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bundle exec rake # Run full test suite (default task is :spec)
|
|
13
|
+
bundle exec rspec # Run all specs
|
|
14
|
+
bundle exec rspec spec/active_storage/async_variants_spec.rb # Run the main spec file
|
|
15
|
+
bundle exec rspec spec/active_storage/async_variants_spec.rb -e "description" # Run specific example
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
The gem works by prepending extension modules onto Active Storage classes:
|
|
21
|
+
|
|
22
|
+
- **`VariationExtension`** → `ActiveStorage::Variation` — extracts async options (`fallback:`, `transformer:`, `max_retries:`) from variant config before passing the rest to standard Active Storage
|
|
23
|
+
- **`AttachmentExtension`** → `ActiveStorage::Attachment` — hooks into `transform_variants_later` to enqueue `ProcessJob` for variants with `fallback:`
|
|
24
|
+
- **`VariantWithRecordExtension`** → `ActiveStorage::VariantWithRecord` — overrides URL generation to serve fallback while processing; adds state query methods (`ready?`, `processing?`, `pending?`, `failed?`)
|
|
25
|
+
- **`ProcessJob`** — background job that determines transformer type (inline vs external) and processes accordingly
|
|
26
|
+
|
|
27
|
+
### Transformer Types
|
|
28
|
+
|
|
29
|
+
- **Inline**: implements `process(file, **options)` → blocks worker, returns `{ io:, content_type:, filename: }`
|
|
30
|
+
- **External**: implements `initiate(source_url:, destination_url:, callback_url:, **options)` → frees worker immediately, external service POSTs to callback URL when done
|
|
31
|
+
|
|
32
|
+
The gem detects which type by checking if `process` is overridden on the transformer class.
|
|
33
|
+
|
|
34
|
+
### Callback Endpoint
|
|
35
|
+
|
|
36
|
+
`CallbacksController` mounted at `/active_storage/async_variants/callbacks/:token` receives webhook POSTs from external services. Tokens are signed with `ActiveStorage.verifier`.
|
|
37
|
+
|
|
38
|
+
### State Machine
|
|
39
|
+
|
|
40
|
+
`VariantRecord.state`: `pending` → `processing` → `processed` (success) or `failed` (with error message and attempt count)
|
|
41
|
+
|
|
42
|
+
## Testing
|
|
43
|
+
|
|
44
|
+
- Uses RSpec with a dummy Rails app in `spec/dummy/`
|
|
45
|
+
- SQLite in-memory database defined in `spec/support/active_record.rb`
|
|
46
|
+
- Jobs use `:test` queue adapter (not executed automatically — use `perform_enqueued_jobs` to run them)
|
|
47
|
+
- Test helpers `create_variant_record` and `simulate_processed_variant` are defined in `spec/support/active_record.rb`
|
|
48
|
+
- There is one main spec file: `spec/active_storage/async_variants_spec.rb`
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Micah Geisel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# ActiveStorage::AsyncVariants
|
|
2
|
+
|
|
3
|
+
Extends Active Storage with pluggable per-variant transformers, async-safe variant processing, and failure handling.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
Active Storage's variant system assumes transformations are fast and reliable -- like generating an image thumbnail. But some transformations are slow (transcoding a 1GB video to 720p VP9) and fallible (the transcode may permanently fail). When you use `process: :later`, Active Storage enqueues a background job, but if the variant is requested before the job finishes, it falls through to synchronous processing -- blocking the request for minutes or timing out entirely. And if the transformation fails, the error bubbles up with no tracking or retry limits.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "active_storage-async_variants"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bin/rails active_storage_async_variants:install:migrations
|
|
17
|
+
bin/rails db:migrate
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Add `fallback:` to any named variant to opt into the async pipeline:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class User < ApplicationRecord
|
|
26
|
+
has_one_attached :video do |attachable|
|
|
27
|
+
attachable.variant :web,
|
|
28
|
+
transformer: VideoTranscoder,
|
|
29
|
+
codec: "vp9",
|
|
30
|
+
resolution: "720p",
|
|
31
|
+
fallback: :original
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The presence of `fallback:` is what opts a variant into async processing. Without it, variants behave exactly as they do in standard Active Storage. The `transformer:` option is independent -- you can use a custom transformer synchronously, or use the default transformer asynchronously:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
has_one_attached :video do |attachable|
|
|
40
|
+
# Async with custom transformer (video transcode)
|
|
41
|
+
attachable.variant :web,
|
|
42
|
+
transformer: VideoTranscoder,
|
|
43
|
+
codec: "vp9",
|
|
44
|
+
fallback: :original
|
|
45
|
+
|
|
46
|
+
# Async with default transformer (large image resize that's too slow for inline)
|
|
47
|
+
attachable.variant :thumbnail,
|
|
48
|
+
resize_to_limit: [200, 200],
|
|
49
|
+
fallback: :original
|
|
50
|
+
|
|
51
|
+
# Sync with custom transformer (fast custom processing, no fallback needed)
|
|
52
|
+
attachable.variant :watermarked,
|
|
53
|
+
transformer: WatermarkStamper
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
In views, use the same Active Storage helpers:
|
|
58
|
+
|
|
59
|
+
```erb
|
|
60
|
+
<%= video_tag user.video.variant(:web).url %>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If the variant is still processing, this serves the original video. Once processing completes, it serves the transcoded variant.
|
|
64
|
+
|
|
65
|
+
## Writing a Transformer
|
|
66
|
+
|
|
67
|
+
Transformers come in two flavors: **inline** (the job blocks until processing completes) and **external** (the job kicks off remote work and a webhook signals completion).
|
|
68
|
+
|
|
69
|
+
### External Transformers (recommended for slow work)
|
|
70
|
+
|
|
71
|
+
An external transformer delegates to a remote service and returns immediately, freeing up the job worker. The remote service uploads the result directly to storage and hits a callback URL when done.
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
class LambdaTranscoder < ActiveStorage::AsyncVariants::Transformer
|
|
75
|
+
def initiate(source_url:, destination_url:, callback_url:, **options)
|
|
76
|
+
Http.post("https://transcode.example.com/jobs",
|
|
77
|
+
source_url: source_url,
|
|
78
|
+
destination_url: destination_url,
|
|
79
|
+
callback_url: callback_url,
|
|
80
|
+
codec: options[:codec],
|
|
81
|
+
resolution: options[:resolution],
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The gem calls `initiate` with:
|
|
88
|
+
- `source_url` -- a presigned GET URL for the original file
|
|
89
|
+
- `destination_url` -- a presigned PUT URL where the result should be uploaded
|
|
90
|
+
- `callback_url` -- a signed webhook URL to POST to when done
|
|
91
|
+
|
|
92
|
+
The remote service does its work (which could take minutes or hours), uploads the result to `destination_url`, then POSTs to `callback_url`:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
POST <callback_url>
|
|
96
|
+
Content-Type: application/json
|
|
97
|
+
|
|
98
|
+
{ "status": "success", "content_type": "video/webm", "byte_size": 52428800 }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or on failure:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
POST <callback_url>
|
|
105
|
+
Content-Type: application/json
|
|
106
|
+
|
|
107
|
+
{ "status": "failed", "error": "ffmpeg exited with status 1" }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The callback URL is signed -- no authentication is needed on the caller's side.
|
|
111
|
+
|
|
112
|
+
### Callback Endpoint
|
|
113
|
+
|
|
114
|
+
The gem mounts a callback endpoint at:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
POST /active_storage/async_variants/callbacks/:token
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The `:token` is a signed, single-use token that identifies the variant record. The gem generates this URL and passes it to `initiate` as `callback_url`. Your external service just POSTs to it -- no API keys or authentication headers required.
|
|
121
|
+
|
|
122
|
+
Expected request body:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{ "status": "success", "content_type": "video/webm", "byte_size": 52428800 }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{ "status": "failed", "error": "ffmpeg exited with status 1" }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Inline Transformers (simpler, blocks the worker)
|
|
133
|
+
|
|
134
|
+
For cases where you're running the transformation locally (e.g., ffmpeg on the same machine), an inline transformer blocks until done:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
class LocalTranscoder < ActiveStorage::AsyncVariants::Transformer
|
|
138
|
+
def process(file, **options)
|
|
139
|
+
output = Tempfile.new(["output", ".webm"])
|
|
140
|
+
system("ffmpeg", "-i", file.path,
|
|
141
|
+
"-c:v", "libvpx-vp9",
|
|
142
|
+
"-vf", "scale=-2:#{options[:resolution]&.delete("p") || 720}",
|
|
143
|
+
"-c:a", "libopus",
|
|
144
|
+
output.path,
|
|
145
|
+
exception: true,
|
|
146
|
+
)
|
|
147
|
+
{ io: output, content_type: "video/webm", filename: "video.webm" }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The `process` method receives the source file and all non-reserved options from the variant definition. It returns a hash with `io:`, `content_type:`, and `filename:`.
|
|
153
|
+
|
|
154
|
+
The gem determines the mode by which method the transformer implements: `initiate` for external, `process` for inline.
|
|
155
|
+
|
|
156
|
+
## Checking Variant State
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
variant = user.video.variant(:web)
|
|
160
|
+
|
|
161
|
+
variant.ready? # => true if processed successfully
|
|
162
|
+
variant.processing? # => true if job is running or external service is working
|
|
163
|
+
variant.pending? # => true if job is enqueued
|
|
164
|
+
variant.failed? # => true if permanently failed
|
|
165
|
+
variant.error # => error message string, or nil
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Fallback Options
|
|
169
|
+
|
|
170
|
+
The `fallback:` option controls what gets served while a variant is processing (or after it fails):
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
# Serve the original unprocessed file
|
|
174
|
+
attachable.variant :web, fallback: :original
|
|
175
|
+
|
|
176
|
+
# Return nil -- let the view handle it
|
|
177
|
+
attachable.variant :web, fallback: :blank
|
|
178
|
+
|
|
179
|
+
# Custom fallback
|
|
180
|
+
attachable.variant :web,
|
|
181
|
+
fallback: -> (blob) { "/placeholders/processing.svg" }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Failure Handling
|
|
185
|
+
|
|
186
|
+
By default, a variant is retried 3 times before being marked as permanently failed. Configure per-variant:
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
attachable.variant :web,
|
|
190
|
+
transformer: VideoTranscoder,
|
|
191
|
+
codec: "vp9",
|
|
192
|
+
resolution: "720p",
|
|
193
|
+
fallback: :original,
|
|
194
|
+
max_retries: 5
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Inspect failures:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
variant = user.video.variant(:web)
|
|
201
|
+
variant.failed? # => true
|
|
202
|
+
variant.error # => "ffmpeg exited with status 1: ..."
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## How It Works
|
|
206
|
+
|
|
207
|
+
### External transformer flow
|
|
208
|
+
|
|
209
|
+
1. User uploads a file to an attachment that has async variants defined
|
|
210
|
+
2. After attachment, a background job is enqueued for each async variant
|
|
211
|
+
3. `VariantRecord` is created with state `pending`
|
|
212
|
+
4. The job calls the transformer's `initiate` method with presigned source/destination URLs and a signed callback URL, then exits -- the worker is free
|
|
213
|
+
5. The external service processes the file, uploads the result to the destination URL
|
|
214
|
+
6. The external service POSTs to the callback URL with success/failure status
|
|
215
|
+
7. The gem's callback endpoint transitions the `VariantRecord` to `processed` or `failed`
|
|
216
|
+
8. When a view requests the variant URL, the gem checks state and serves the variant or the fallback
|
|
217
|
+
|
|
218
|
+
### Inline transformer flow
|
|
219
|
+
|
|
220
|
+
1-3. Same as above
|
|
221
|
+
4. The job calls the transformer's `process` method, blocking until complete
|
|
222
|
+
5. On success, the output is uploaded, the `VariantRecord` transitions to `processed`
|
|
223
|
+
6. On failure, the error is recorded and the job is re-enqueued (up to `max_retries`)
|
|
224
|
+
7. When a view requests the variant URL, the gem checks state and serves the variant or the fallback
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT
|
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveStorage
|
|
4
|
+
module AsyncVariants
|
|
5
|
+
class CallbacksController < ActionController::API
|
|
6
|
+
def create
|
|
7
|
+
variant_record_id = ActiveStorage.verifier.verify(params[:token], purpose: :async_variant_callback)
|
|
8
|
+
variant_record = ActiveStorage::VariantRecord.find(variant_record_id)
|
|
9
|
+
|
|
10
|
+
case params[:status]
|
|
11
|
+
when "success"
|
|
12
|
+
variant_record.update!(state: "processed")
|
|
13
|
+
when "failed"
|
|
14
|
+
variant_record.update!(state: "failed", error: params[:error])
|
|
15
|
+
else
|
|
16
|
+
head :unprocessable_entity and return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
head :ok
|
|
20
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
21
|
+
head :unauthorized
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ..
|
|
3
|
+
specs:
|
|
4
|
+
active_storage-async_variants (0.1.0)
|
|
5
|
+
activestorage (>= 7.1)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
actioncable (7.2.3)
|
|
11
|
+
actionpack (= 7.2.3)
|
|
12
|
+
activesupport (= 7.2.3)
|
|
13
|
+
nio4r (~> 2.0)
|
|
14
|
+
websocket-driver (>= 0.6.1)
|
|
15
|
+
zeitwerk (~> 2.6)
|
|
16
|
+
actionmailbox (7.2.3)
|
|
17
|
+
actionpack (= 7.2.3)
|
|
18
|
+
activejob (= 7.2.3)
|
|
19
|
+
activerecord (= 7.2.3)
|
|
20
|
+
activestorage (= 7.2.3)
|
|
21
|
+
activesupport (= 7.2.3)
|
|
22
|
+
mail (>= 2.8.0)
|
|
23
|
+
actionmailer (7.2.3)
|
|
24
|
+
actionpack (= 7.2.3)
|
|
25
|
+
actionview (= 7.2.3)
|
|
26
|
+
activejob (= 7.2.3)
|
|
27
|
+
activesupport (= 7.2.3)
|
|
28
|
+
mail (>= 2.8.0)
|
|
29
|
+
rails-dom-testing (~> 2.2)
|
|
30
|
+
actionpack (7.2.3)
|
|
31
|
+
actionview (= 7.2.3)
|
|
32
|
+
activesupport (= 7.2.3)
|
|
33
|
+
cgi
|
|
34
|
+
nokogiri (>= 1.8.5)
|
|
35
|
+
racc
|
|
36
|
+
rack (>= 2.2.4, < 3.3)
|
|
37
|
+
rack-session (>= 1.0.1)
|
|
38
|
+
rack-test (>= 0.6.3)
|
|
39
|
+
rails-dom-testing (~> 2.2)
|
|
40
|
+
rails-html-sanitizer (~> 1.6)
|
|
41
|
+
useragent (~> 0.16)
|
|
42
|
+
actiontext (7.2.3)
|
|
43
|
+
actionpack (= 7.2.3)
|
|
44
|
+
activerecord (= 7.2.3)
|
|
45
|
+
activestorage (= 7.2.3)
|
|
46
|
+
activesupport (= 7.2.3)
|
|
47
|
+
globalid (>= 0.6.0)
|
|
48
|
+
nokogiri (>= 1.8.5)
|
|
49
|
+
actionview (7.2.3)
|
|
50
|
+
activesupport (= 7.2.3)
|
|
51
|
+
builder (~> 3.1)
|
|
52
|
+
cgi
|
|
53
|
+
erubi (~> 1.11)
|
|
54
|
+
rails-dom-testing (~> 2.2)
|
|
55
|
+
rails-html-sanitizer (~> 1.6)
|
|
56
|
+
activejob (7.2.3)
|
|
57
|
+
activesupport (= 7.2.3)
|
|
58
|
+
globalid (>= 0.3.6)
|
|
59
|
+
activemodel (7.2.3)
|
|
60
|
+
activesupport (= 7.2.3)
|
|
61
|
+
activerecord (7.2.3)
|
|
62
|
+
activemodel (= 7.2.3)
|
|
63
|
+
activesupport (= 7.2.3)
|
|
64
|
+
timeout (>= 0.4.0)
|
|
65
|
+
activestorage (7.2.3)
|
|
66
|
+
actionpack (= 7.2.3)
|
|
67
|
+
activejob (= 7.2.3)
|
|
68
|
+
activerecord (= 7.2.3)
|
|
69
|
+
activesupport (= 7.2.3)
|
|
70
|
+
marcel (~> 1.0)
|
|
71
|
+
activesupport (7.2.3)
|
|
72
|
+
base64
|
|
73
|
+
benchmark (>= 0.3)
|
|
74
|
+
bigdecimal
|
|
75
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
76
|
+
connection_pool (>= 2.2.5)
|
|
77
|
+
drb
|
|
78
|
+
i18n (>= 1.6, < 2)
|
|
79
|
+
logger (>= 1.4.2)
|
|
80
|
+
minitest (>= 5.1)
|
|
81
|
+
securerandom (>= 0.3)
|
|
82
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
83
|
+
appraisal (2.5.0)
|
|
84
|
+
bundler
|
|
85
|
+
rake
|
|
86
|
+
thor (>= 0.14.0)
|
|
87
|
+
base64 (0.3.0)
|
|
88
|
+
benchmark (0.5.0)
|
|
89
|
+
bigdecimal (4.0.1)
|
|
90
|
+
builder (3.3.0)
|
|
91
|
+
cgi (0.5.1)
|
|
92
|
+
concurrent-ruby (1.3.6)
|
|
93
|
+
connection_pool (3.0.2)
|
|
94
|
+
crass (1.0.6)
|
|
95
|
+
date (3.5.1)
|
|
96
|
+
diff-lcs (1.6.2)
|
|
97
|
+
drb (2.2.3)
|
|
98
|
+
erb (6.0.2)
|
|
99
|
+
erubi (1.13.1)
|
|
100
|
+
globalid (1.3.0)
|
|
101
|
+
activesupport (>= 6.1)
|
|
102
|
+
i18n (1.14.8)
|
|
103
|
+
concurrent-ruby (~> 1.0)
|
|
104
|
+
io-console (0.8.2)
|
|
105
|
+
irb (1.17.0)
|
|
106
|
+
pp (>= 0.6.0)
|
|
107
|
+
prism (>= 1.3.0)
|
|
108
|
+
rdoc (>= 4.0.0)
|
|
109
|
+
reline (>= 0.4.2)
|
|
110
|
+
logger (1.7.0)
|
|
111
|
+
loofah (2.25.0)
|
|
112
|
+
crass (~> 1.0.2)
|
|
113
|
+
nokogiri (>= 1.12.0)
|
|
114
|
+
mail (2.9.0)
|
|
115
|
+
logger
|
|
116
|
+
mini_mime (>= 0.1.1)
|
|
117
|
+
net-imap
|
|
118
|
+
net-pop
|
|
119
|
+
net-smtp
|
|
120
|
+
marcel (1.1.0)
|
|
121
|
+
mini_mime (1.1.5)
|
|
122
|
+
minitest (6.0.2)
|
|
123
|
+
drb (~> 2.0)
|
|
124
|
+
prism (~> 1.5)
|
|
125
|
+
net-imap (0.6.3)
|
|
126
|
+
date
|
|
127
|
+
net-protocol
|
|
128
|
+
net-pop (0.1.2)
|
|
129
|
+
net-protocol
|
|
130
|
+
net-protocol (0.2.2)
|
|
131
|
+
timeout
|
|
132
|
+
net-smtp (0.5.1)
|
|
133
|
+
net-protocol
|
|
134
|
+
nio4r (2.7.5)
|
|
135
|
+
nokogiri (1.19.1-aarch64-linux-gnu)
|
|
136
|
+
racc (~> 1.4)
|
|
137
|
+
nokogiri (1.19.1-aarch64-linux-musl)
|
|
138
|
+
racc (~> 1.4)
|
|
139
|
+
nokogiri (1.19.1-arm-linux-gnu)
|
|
140
|
+
racc (~> 1.4)
|
|
141
|
+
nokogiri (1.19.1-arm-linux-musl)
|
|
142
|
+
racc (~> 1.4)
|
|
143
|
+
nokogiri (1.19.1-arm64-darwin)
|
|
144
|
+
racc (~> 1.4)
|
|
145
|
+
nokogiri (1.19.1-x86_64-darwin)
|
|
146
|
+
racc (~> 1.4)
|
|
147
|
+
nokogiri (1.19.1-x86_64-linux-gnu)
|
|
148
|
+
racc (~> 1.4)
|
|
149
|
+
nokogiri (1.19.1-x86_64-linux-musl)
|
|
150
|
+
racc (~> 1.4)
|
|
151
|
+
pp (0.6.3)
|
|
152
|
+
prettyprint
|
|
153
|
+
prettyprint (0.2.0)
|
|
154
|
+
prism (1.9.0)
|
|
155
|
+
psych (5.3.1)
|
|
156
|
+
date
|
|
157
|
+
stringio
|
|
158
|
+
racc (1.8.1)
|
|
159
|
+
rack (3.2.5)
|
|
160
|
+
rack-session (2.1.1)
|
|
161
|
+
base64 (>= 0.1.0)
|
|
162
|
+
rack (>= 3.0.0)
|
|
163
|
+
rack-test (2.2.0)
|
|
164
|
+
rack (>= 1.3)
|
|
165
|
+
rackup (2.3.1)
|
|
166
|
+
rack (>= 3)
|
|
167
|
+
rails (7.2.3)
|
|
168
|
+
actioncable (= 7.2.3)
|
|
169
|
+
actionmailbox (= 7.2.3)
|
|
170
|
+
actionmailer (= 7.2.3)
|
|
171
|
+
actionpack (= 7.2.3)
|
|
172
|
+
actiontext (= 7.2.3)
|
|
173
|
+
actionview (= 7.2.3)
|
|
174
|
+
activejob (= 7.2.3)
|
|
175
|
+
activemodel (= 7.2.3)
|
|
176
|
+
activerecord (= 7.2.3)
|
|
177
|
+
activestorage (= 7.2.3)
|
|
178
|
+
activesupport (= 7.2.3)
|
|
179
|
+
bundler (>= 1.15.0)
|
|
180
|
+
railties (= 7.2.3)
|
|
181
|
+
rails-dom-testing (2.3.0)
|
|
182
|
+
activesupport (>= 5.0.0)
|
|
183
|
+
minitest
|
|
184
|
+
nokogiri (>= 1.6)
|
|
185
|
+
rails-html-sanitizer (1.7.0)
|
|
186
|
+
loofah (~> 2.25)
|
|
187
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
188
|
+
railties (7.2.3)
|
|
189
|
+
actionpack (= 7.2.3)
|
|
190
|
+
activesupport (= 7.2.3)
|
|
191
|
+
cgi
|
|
192
|
+
irb (~> 1.13)
|
|
193
|
+
rackup (>= 1.0.0)
|
|
194
|
+
rake (>= 12.2)
|
|
195
|
+
thor (~> 1.0, >= 1.2.2)
|
|
196
|
+
tsort (>= 0.2)
|
|
197
|
+
zeitwerk (~> 2.6)
|
|
198
|
+
rake (13.3.1)
|
|
199
|
+
rdoc (7.2.0)
|
|
200
|
+
erb
|
|
201
|
+
psych (>= 4.0.0)
|
|
202
|
+
tsort
|
|
203
|
+
reline (0.6.3)
|
|
204
|
+
io-console (~> 0.5)
|
|
205
|
+
rspec (3.13.2)
|
|
206
|
+
rspec-core (~> 3.13.0)
|
|
207
|
+
rspec-expectations (~> 3.13.0)
|
|
208
|
+
rspec-mocks (~> 3.13.0)
|
|
209
|
+
rspec-core (3.13.6)
|
|
210
|
+
rspec-support (~> 3.13.0)
|
|
211
|
+
rspec-expectations (3.13.5)
|
|
212
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
213
|
+
rspec-support (~> 3.13.0)
|
|
214
|
+
rspec-mocks (3.13.8)
|
|
215
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
216
|
+
rspec-support (~> 3.13.0)
|
|
217
|
+
rspec-rails (8.0.3)
|
|
218
|
+
actionpack (>= 7.2)
|
|
219
|
+
activesupport (>= 7.2)
|
|
220
|
+
railties (>= 7.2)
|
|
221
|
+
rspec-core (~> 3.13)
|
|
222
|
+
rspec-expectations (~> 3.13)
|
|
223
|
+
rspec-mocks (~> 3.13)
|
|
224
|
+
rspec-support (~> 3.13)
|
|
225
|
+
rspec-support (3.13.7)
|
|
226
|
+
securerandom (0.4.1)
|
|
227
|
+
sqlite3 (2.9.1-aarch64-linux-gnu)
|
|
228
|
+
sqlite3 (2.9.1-aarch64-linux-musl)
|
|
229
|
+
sqlite3 (2.9.1-arm-linux-gnu)
|
|
230
|
+
sqlite3 (2.9.1-arm-linux-musl)
|
|
231
|
+
sqlite3 (2.9.1-arm64-darwin)
|
|
232
|
+
sqlite3 (2.9.1-x86_64-darwin)
|
|
233
|
+
sqlite3 (2.9.1-x86_64-linux-gnu)
|
|
234
|
+
sqlite3 (2.9.1-x86_64-linux-musl)
|
|
235
|
+
stringio (3.2.0)
|
|
236
|
+
thor (1.5.0)
|
|
237
|
+
timeout (0.6.0)
|
|
238
|
+
tsort (0.2.0)
|
|
239
|
+
tzinfo (2.0.6)
|
|
240
|
+
concurrent-ruby (~> 1.0)
|
|
241
|
+
useragent (0.16.11)
|
|
242
|
+
websocket-driver (0.8.0)
|
|
243
|
+
base64
|
|
244
|
+
websocket-extensions (>= 0.1.0)
|
|
245
|
+
websocket-extensions (0.1.5)
|
|
246
|
+
zeitwerk (2.7.5)
|
|
247
|
+
|
|
248
|
+
PLATFORMS
|
|
249
|
+
aarch64-linux-gnu
|
|
250
|
+
aarch64-linux-musl
|
|
251
|
+
arm-linux-gnu
|
|
252
|
+
arm-linux-musl
|
|
253
|
+
arm64-darwin
|
|
254
|
+
x86_64-darwin
|
|
255
|
+
x86_64-linux-gnu
|
|
256
|
+
x86_64-linux-musl
|
|
257
|
+
|
|
258
|
+
DEPENDENCIES
|
|
259
|
+
active_storage-async_variants!
|
|
260
|
+
appraisal
|
|
261
|
+
irb
|
|
262
|
+
rails (~> 7.2.0)
|
|
263
|
+
rake (~> 13.0)
|
|
264
|
+
rspec (~> 3.0)
|
|
265
|
+
rspec-rails
|
|
266
|
+
sqlite3
|
|
267
|
+
|
|
268
|
+
BUNDLED WITH
|
|
269
|
+
2.6.2
|