active_storage-cloud_transformations 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9ffa075ea0830d2c76a32bd0ebe9e86799859e0bc741ce3128b217b7dc25b79
4
- data.tar.gz: bfcd4614b55c2cba811263826d178d661bcefb91f50d53996721ab8792ee38d6
3
+ metadata.gz: 438e440734f18e87ae9dd8df79b2cfecf1dc4f331a2cabbb8c124ae9a261a88b
4
+ data.tar.gz: c68578ee96799bbca6ff5b7372986d0856ebe54884b0c25a10d7454b1a2bc132
5
5
  SHA512:
6
- metadata.gz: a2fafbe7f41f231d20303261ed9863e56ba33b7de17ba009374934e8d93fbcddd7091a58f572fad3667a1efdd189ffa608e6a68a9b8386f1cc559d635ee7906d
7
- data.tar.gz: 40b5993c2cc8933c6bae0cd9f61c144aefdc3996b0356889beb882fe387e69c21d339ac5a663e00f4ebe6d4c8f308930b4e9e20c9b5849ade857d85505b72043
6
+ metadata.gz: f1cf577c3d076ce7cf6cbdc1e06b138c99bd3d1bd3f46307b3411857f2230100b50f4519457d61d4d184fc17fc05823b8807cfd83bef6e158ae52446f4cae97b
7
+ data.tar.gz: 254f1204b8439c1e7ac506d273f03339c58f5ace31b8049f0b6126bc728651e567d89a1832bda308088537fd42178bde614222dcabf9b58496c7240e75828315
data/.env.example ADDED
@@ -0,0 +1,6 @@
1
+ # S3 credentials for testing
2
+ AWS_S3_URI=s3://YOUR_ACCESS_KEY_ID:YOUR_SECRET_ACCESS_KEY@YOUR_REGION.amazonaws.com/YOUR_BUCKET
3
+
4
+ # Optional: Crucible transformation service endpoint
5
+ # Tests will mock the endpoint without this
6
+ # CRUCIBLE_ENDPOINT=https://crucible.example.com/
@@ -0,0 +1,26 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ fail-fast: false
8
+ max-parallel: 1
9
+ matrix:
10
+ gemfile: ['rails_7.2', 'rails_8.0', 'rails_8.1']
11
+ ruby-version: ['3.2', '3.3', '3.4']
12
+
13
+ env:
14
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
15
+ AWS_S3_URI: ${{ secrets.AWS_S3_URI }}
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Ruby ${{ matrix.ruby-version }}
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby-version }}
23
+ bundler-cache: true
24
+
25
+ - name: Run tests
26
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -5,11 +5,11 @@
5
5
  /doc/
6
6
  /pkg/
7
7
  /spec/reports/
8
- /spec/storage.yml
9
8
  /tmp/
10
9
  /Gemfile.lock
11
10
  /.ruby-version
12
11
  /.ruby-gemset
13
12
  /.byebug_history
14
13
  .rspec_status
15
-
14
+ /.env
15
+ /.env.local
data/Appraisals CHANGED
@@ -1,8 +1,12 @@
1
- appraise "rails-6.1" do
2
- gem "rails", "6.1"
1
+ appraise "rails-7.2" do
2
+ gem "rails", "7.2"
3
3
  end
4
4
 
5
- appraise "rails-7.0" do
6
- gem "rails", "7.0"
5
+ appraise "rails-8.0" do
6
+ gem "rails", "8.0"
7
+ end
8
+
9
+ appraise "rails-8.1" do
10
+ gem "rails", "8.1"
7
11
  end
8
12
 
data/CLAUDE.md ADDED
@@ -0,0 +1,118 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ `active_storage-cloud_transformations` is a Ruby gem that extends Rails' ActiveStorage to delegate image variant and video preview generation to external cloud services (via AWS Lambda API endpoints) rather than processing on-server. This enables more efficient handling of resource-intensive image/video transformations.
8
+
9
+ **Key Components:**
10
+ - `Variant` class: Handles image and video variant generation via cloud processing
11
+ - `Preview` class: Generates preview images for video files via cloud service
12
+ - Test suite uses a dummy Rails application to test the gem in a realistic environment
13
+
14
+ ## Development Setup
15
+
16
+ **Ruby version:** 3.3.4 (specified in .ruby-version)
17
+
18
+ **Install dependencies:**
19
+ ```bash
20
+ bundle install
21
+ bin/setup
22
+ ```
23
+
24
+ ## Common Commands
25
+
26
+ **Run all tests:**
27
+ ```bash
28
+ bundle exec rake spec
29
+ ```
30
+
31
+ **Run a specific test file:**
32
+ ```bash
33
+ bundle exec rspec spec/active_storage-cloud_transformations_spec.rb
34
+ ```
35
+
36
+ **Run a specific test:**
37
+ ```bash
38
+ bundle exec rspec spec/active_storage-cloud_transformations_spec.rb:5
39
+ ```
40
+
41
+ **Interactive console:**
42
+ ```bash
43
+ bin/console
44
+ ```
45
+
46
+ **Install gem locally for testing:**
47
+ ```bash
48
+ bundle exec rake install
49
+ ```
50
+
51
+ **Run linter/formatter (if configured):**
52
+ Uses RSpec configuration in `.rspec` with documentation format and color output enabled.
53
+
54
+ ## Architecture Notes
55
+
56
+ ### Cloud API Integration
57
+
58
+ Both `Variant` and `Preview` classes post transformation requests to an AWS Lambda API endpoint at `https://huuabwxpqf.execute-api.us-west-2.amazonaws.com/prod/`. They expect:
59
+ - Image variants: POST to `/image/variant`
60
+ - Video variants: POST to `/video/variant`
61
+ - Video previews: POST to `/video/preview`
62
+ - Expected response: HTTP 201 (or 504 for timeouts when `ignore_timeouts: true`)
63
+
64
+ ### Key Processing Patterns
65
+
66
+ 1. **Variant Processing** (`Variant#process`):
67
+ - Creates an output blob with placeholder byte_size and checksum
68
+ - Sets `metadata[:analyzed] = true` to suppress automatic analysis
69
+ - Calls the cloud service via `run_crucible_job`
70
+ - Schedules `ActiveStorage::AnalyzeJob` for post-processing
71
+ - Handles race conditions with retry on `RecordNotUnique`
72
+
73
+ 2. **Variant Reprocessing** (`Variant#reprocess`):
74
+ - Reprocesses an existing variant record using the cloud service
75
+ - Useful when original blob changes or service needs re-execution
76
+
77
+ 3. **Preview Processing** (`Preview#process` and `Preview#reprocess`):
78
+ - Creates both a preview image blob and a variant of that image
79
+ - Sets `metadata[:analyzed] = true` on both blobs
80
+ - Calls cloud service to generate the preview
81
+ - Schedules analysis jobs for both generated blobs
82
+
83
+ ### Test Configuration & Strategy
84
+
85
+ **Test Environment:**
86
+ - Tests make real HTTP calls to the AWS Lambda API endpoint and S3
87
+ - Requires valid AWS credentials: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_S3_BUCKET`
88
+ - The Lambda endpoint must be accessible and functional for tests to pass
89
+
90
+ **Test Fixtures & Helpers:**
91
+ - Uses RSpec with `focus` filter enabled (set `focus: true` on examples to isolate)
92
+ - Requires a dummy Rails application in `spec/dummy/`
93
+ - Test storage service configurations loaded from `spec/storage.yml` (S3 service)
94
+ - ActiveStorage verifier and current host set in `spec_helper.rb`
95
+ - Custom matchers available in `spec/support/time_duration_matchers.rb`:
96
+ - `take_more_than(duration)` - asserts a block takes longer than the specified duration
97
+ - `take_less_than(duration)` - asserts a block completes faster than the specified duration
98
+
99
+ ## Important Implementation Details
100
+
101
+ - Transformation parameters are extracted from `variation.transformations`, specifically `resize_to_limit`, `rotation`, and `format`
102
+ - Output blobs are created with placeholder checksum (0) since cloud service calculates the actual value
103
+ - The gem suppresses ActiveStorage's automatic blob analysis by pre-setting `metadata[:analyzed]`
104
+ - HTTP requests use `Net::HTTP` directly for cloud API communication
105
+ - The gem extends `ActiveStorage::VariantWithRecord` and `ActiveStorage::Preview`
106
+
107
+ ## Dependencies
108
+
109
+ Key dependencies (see `Gemfile`):
110
+ - `rails`: Rails framework with ActiveStorage
111
+ - `rspec`: Testing framework
112
+ - `aws-sdk-s3`: For S3 storage service support
113
+ - `appraisal`: For testing against multiple Rails versions (see `Appraisals` file)
114
+ - `sqlite3`: Test database
115
+
116
+ ## Git Workflow
117
+
118
+ The main branch is `master`. Work on feature branches and create pull requests for review.
data/Gemfile CHANGED
@@ -1,14 +1,3 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in active_storage-cloud_transformations.gemspec
4
3
  gemspec
5
-
6
- gem "rake"
7
- gem "rspec", "~> 3.0"
8
- gem "sqlite3", "~> 1.4"
9
- gem "bootsnap", require: false
10
- gem "rails"
11
- gem "byebug"
12
- gem "aws-sdk-s3"
13
- gem "appraisal"
14
-
data/README.md CHANGED
@@ -1,40 +1,184 @@
1
1
  # ActiveStorage::CloudTransformations
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_storage/cloud_transformations`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ A Rails gem that extends ActiveStorage to generate image variants and video previews via external cloud services (like AWS Lambda) instead of processing them locally on your server.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ ## Features
6
6
 
7
- ## Installation
7
+ - **Offload image and video processing** to cloud services, reducing server load
8
+ - **Support for variants** - Transform images with custom dimensions, formats, and rotations
9
+ - **Support for previews** - Generate preview images from video files
10
+ - **Flexible configuration** - Easily point to your own cloud transformation service
11
+ - **Per-instance endpoints** - Route different requests to different endpoints based on your model logic
12
+ - **Presigned URLs** - Remove the need for your cloud service to have direct S3 access
13
+ - **Non-blocking** - Processing happens asynchronously without blocking request handling
14
+ - **Rails 7.2+** - Works with modern Rails and ActiveStorage
8
15
 
9
- Add this line to your application's Gemfile:
16
+ ## Why Cloud Transformations?
17
+
18
+ Processing large images and videos locally consumes significant CPU resources. This gem lets you delegate that work to dedicated cloud services, allowing your Rails application to focus on what it does best. You get:
19
+
20
+ - Reduced server CPU usage
21
+ - Faster request handling
22
+ - Scalable processing as your media grows
23
+
24
+ ## Configuration
25
+
26
+ ### Global Configuration
27
+
28
+ Create an initializer at `config/initializers/active_storage_cloud_transformations.rb`:
10
29
 
11
30
  ```ruby
12
- gem 'active_storage-cloud_transformations'
31
+ ActiveStorage::CloudTransformations.configure do |config|
32
+ # The cloud service endpoint (default: "https://huuabwxpqf.execute-api.us-west-2.amazonaws.com/prod")
33
+ config.crucible_endpoint = ENV.fetch("CRUCIBLE_ENDPOINT", "https://crucible.example.com/prod")
34
+
35
+ # Use presigned URLs instead of S3 paths (default: false)
36
+ # When enabled, your cloud service doesn't need direct S3 access
37
+ config.use_presigned_urls = true
38
+
39
+ # Presigned URL expiration in seconds (default: 3600)
40
+ config.presigned_url_expiration = 7200
41
+ end
13
42
  ```
14
43
 
15
- And then execute:
44
+ ### Per-Instance Endpoint Configuration
16
45
 
17
- $ bundle install
46
+ You can configure different endpoints for different model instances by defining a `crucible_endpoint` method on your model:
18
47
 
19
- Or install it yourself as:
48
+ ```ruby
49
+ class User < ApplicationRecord
50
+ has_one_attached :avatar
51
+
52
+ def crucible_endpoint
53
+ # Route premium users to a different endpoint
54
+ if premium?
55
+ "https://premium.crucible.example.com/prod"
56
+ elsif region == "eu"
57
+ "https://eu.crucible.example.com/prod"
58
+ else
59
+ # Return nil to use the global config
60
+ nil
61
+ end
62
+ end
63
+ end
64
+ ```
20
65
 
21
- $ gem install active_storage-cloud_transformations
66
+ When processing attachments, the gem will:
67
+ 1. Check if the model defines a `crucible_endpoint` method
68
+ 2. Use that endpoint if it returns a non-nil value
69
+ 3. Fall back to the global `config.crucible_endpoint` otherwise
22
70
 
23
71
  ## Usage
24
72
 
25
- TODO: Write usage instructions here
73
+ ### Image Variants
74
+
75
+ Generate image variants just like you normally would with ActiveStorage. The cloud service will handle the transformation:
76
+
77
+ ```ruby
78
+ @user = User.find(1)
79
+
80
+ # Generate a variant (will be processed by cloud service)
81
+ @user.avatar.variant(resize_to_limit: [100, 100]).processed
82
+ ```
83
+
84
+ ### Video Previews
85
+
86
+ Generate preview images from video files:
87
+
88
+ ```ruby
89
+ @video = Video.find(1)
90
+
91
+ # Generate a preview image from the video
92
+ @video.file.preview(resize_to_limit: [160, 160]).processed
93
+
94
+ # Access the generated preview image
95
+ image_url = @video.file.preview_image.url
96
+ ```
97
+
98
+ ### Supported Transformations
99
+
100
+ The cloud service receives the following information via HTTP POST:
101
+
102
+ **For image variants:**
103
+ - `blob_url` - URL to the source image (presigned GET URL or S3 path)
104
+ - `dimensions` - Target dimensions (e.g., "100x100")
105
+ - `rotation` - Rotation angle in degrees
106
+ - `variant_url` - Where to upload the processed variant (presigned PUT URL or S3 path)
107
+ - `format` - Output format (e.g., "webp", "jpeg")
108
+
109
+ **For video previews:**
110
+ - `blob_url` - URL to the source video (presigned GET URL or S3 path)
111
+ - `dimensions` - Dimensions for the preview image
112
+ - `rotation` - Rotation angle in degrees
113
+ - `preview_image_url` - Where to upload the generated preview image (presigned PUT URL or S3 path)
114
+ - `preview_image_variant_url` - Where to upload the preview image variant (presigned PUT URL or S3 path)
115
+
116
+ **Note:** When `use_presigned_urls` is enabled, all URLs include AWS signature query parameters. When disabled, URLs are S3 paths without query parameters, requiring your cloud service to have S3 credentials.
117
+
118
+ ## How It Works
119
+
120
+ 1. When you request a variant or preview, ActiveStorage creates blob records
121
+ 2. This gem intercepts the process and makes an HTTP POST request to your cloud service
122
+ 3. The request includes:
123
+ - Source media URL (presigned GET URL or S3 path)
124
+ - Destination URL for the processed file (presigned PUT URL or S3 path)
125
+ - Transformation parameters (dimensions, format, rotation, etc.)
126
+ 4. Your cloud service performs the transformation:
127
+ - **With presigned URLs:** Downloads from the GET URL, transforms, uploads to the PUT URL
128
+ - **Without presigned URLs:** Accesses S3 directly using its own credentials
129
+ 5. The gem receives a 201 confirmation and marks the variant as processed
130
+ 6. Future requests for the same variant are served from the cache (already in S3)
131
+
132
+ ### Presigned URLs vs S3 Paths
133
+
134
+ **Presigned URLs (recommended):**
135
+ - Your cloud service doesn't need S3 credentials
136
+ - More secure - temporary, scoped access
137
+ - Works with any HTTP client
138
+ - Enable with `config.use_presigned_urls = true`
139
+
140
+ **S3 Paths (legacy):**
141
+ - Your cloud service needs AWS credentials with S3 access
142
+ - URLs are simple paths like `s3://bucket/key`
143
+ - Default behavior for backward compatibility
144
+
145
+ ## Storage Requirements
146
+
147
+ This gem requires **S3 as the storage service** for your Rails application. It does _not_ work with the local disk service.
148
+
149
+ **For your Rails application:**
150
+ - Configure ActiveStorage to use S3
151
+ - Needs S3 credentials to generate presigned URLs (when enabled) or direct S3 access
152
+
153
+ **For your cloud transformation service:**
154
+ - **With presigned URLs enabled:** No S3 credentials needed - the service uses presigned URLs
155
+ - **Without presigned URLs:** Requires AWS credentials with S3 read/write access
26
156
 
27
157
  ## Development
28
158
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
159
+ ### Running Tests Locally
30
160
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
161
+ 1. Copy the example environment file:
162
+ ```bash
163
+ cp .env.example .env
164
+ ```
32
165
 
33
- ## Contributing
166
+ 2. Edit `.env` with your S3 credentials:
167
+ ```bash
168
+ AWS_S3_URI=s3://YOUR_ACCESS_KEY_ID:YOUR_SECRET_ACCESS_KEY@YOUR_REGION.amazonaws.com/YOUR_BUCKET
169
+ ```
170
+
171
+ 3. Run the tests:
172
+ ```bash
173
+ bundle exec rspec
174
+ ```
34
175
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_storage-cloud_transformations.
176
+ By default, requests are mocked in tests. Set `CRUCIBLE_ENDPOINT` in your `.env` file to point to a real service for full integration testing.
177
+
178
+ ## Contributing
36
179
 
180
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/botandrose/active_storage-cloud_transformations](https://github.com/botandrose/active_storage-cloud_transformations).
37
181
 
38
182
  ## License
39
183
 
40
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
184
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
@@ -9,7 +9,6 @@ Gem::Specification.new do |spec|
9
9
  spec.summary = %q{Generate ActiveStorage Variants and Previews via external cloud services, rather than on-server.}
10
10
  spec.homepage = "https://github.com/botandrose/active_storage-cloud_transformations"
11
11
  spec.license = "MIT"
12
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
12
 
14
13
  spec.metadata["homepage_uri"] = spec.homepage
15
14
 
@@ -22,5 +21,15 @@ Gem::Specification.new do |spec|
22
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
22
  spec.require_paths = ["lib"]
24
23
 
25
- spec.add_dependency "activestorage", ">=6.1"
24
+ spec.add_dependency "activestorage", ">=7.2"
25
+
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "sqlite3"
29
+ spec.add_development_dependency "bootsnap"
30
+ spec.add_development_dependency "rails"
31
+ spec.add_development_dependency "byebug"
32
+ spec.add_development_dependency "aws-sdk-s3"
33
+ spec.add_development_dependency "appraisal"
34
+ spec.add_development_dependency "dotenv"
26
35
  end
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "7.2"
6
+
7
+ gemspec path: "../"