active_shrine 0.6.1 → 0.7.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 +4 -0
- data/docs/superpowers/plans/2026-04-14-attachment-url-primitive.md +235 -0
- data/docs/superpowers/plans/2026-04-14-attachment-url-primitive.md.tasks.json +19 -0
- data/docs/superpowers/specs/2026-04-14-attachment-url-primitive-design.md +118 -0
- data/lib/active_shrine/attachment.rb +13 -2
- data/lib/active_shrine/version.rb +1 -1
- metadata +33 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0330c7363b9c8b14e0f718886933c33dff3998618da11b47e5d14e813def269
|
|
4
|
+
data.tar.gz: eda5c702640dd7c6b884ffd6aea3bf9246dffb729100d8d762bb109a6e35e79e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 31e7f98809b76810e2056389ef8512c1f990864d6ec05324bee267b00f9381aace0182fb1a4fb1043d54617d42ab45ccae12199af112df55beeff016115ce307
|
|
7
|
+
data.tar.gz: 9010a2da61a53f475fc24b258892a179848dcd93e36fe38f7e3a257d451b0ad43977497d5a63e4f68c95b30f47f63a9f6764324a5780de56c8e40315b416677d
|
data/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Attachment URL Primitive Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (recommended) or superpowers-extended-cc:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Enhance `ActiveShrine::Attachment#url` to accept a variant and a `strict:` kwarg, replacing the caller-side boilerplate for Shrine's two-phase upload lifecycle.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A single small change to `Attachment#url` (lib/active_shrine/attachment.rb:40). Non-strict default returns cache URL during pending; strict mode returns nil until promotion. Variant-missing falls back to original. The delegation in `Attached::One` / `Attached::Many` (via `delegate_missing_to :attachment, allow_nil: true`) propagates the new signature automatically — no changes needed there.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby, Shrine, Minitest, Combustion.
|
|
10
|
+
|
|
11
|
+
**User Verification:** NO — no user verification required. Automated tests cover the full matrix.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## File Structure
|
|
16
|
+
|
|
17
|
+
- **Modify:** `lib/active_shrine/attachment.rb` — enhance the `url` method in `AttachmentMethods`.
|
|
18
|
+
- **Modify:** `test/internal/config/initializers/shrine.rb` — add the `derivatives` plugin so tests can exercise variant URLs.
|
|
19
|
+
- **Create:** `test/attachment_test.rb` — covers the full strict/non-strict × cached/stored × variant matrix.
|
|
20
|
+
|
|
21
|
+
## Task 1: Enable derivatives in test shrine config
|
|
22
|
+
|
|
23
|
+
**Goal:** Add the Shrine `derivatives` plugin to the test environment so tests can attach, promote, and read variant URLs.
|
|
24
|
+
|
|
25
|
+
**Files:**
|
|
26
|
+
- Modify: `test/internal/config/initializers/shrine.rb`
|
|
27
|
+
|
|
28
|
+
**Acceptance Criteria:**
|
|
29
|
+
- [ ] `Shrine.plugin :derivatives` is loaded in the test init file.
|
|
30
|
+
- [ ] `bundle exec rake test` still runs (no regressions in existing tests).
|
|
31
|
+
|
|
32
|
+
**Verify:** `bundle exec rake test` → existing tests pass.
|
|
33
|
+
|
|
34
|
+
**Steps:**
|
|
35
|
+
|
|
36
|
+
- [ ] **Step 1: Add the derivatives plugin**
|
|
37
|
+
|
|
38
|
+
Edit `test/internal/config/initializers/shrine.rb`. Add this line after the existing plugin calls (e.g. after `Shrine.plugin :refresh_metadata`):
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
Shrine.plugin :derivatives
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- [ ] **Step 2: Run existing tests**
|
|
45
|
+
|
|
46
|
+
Run: `bundle exec rake test`
|
|
47
|
+
Expected: All existing tests continue to pass.
|
|
48
|
+
|
|
49
|
+
- [ ] **Step 3: Commit**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git add test/internal/config/initializers/shrine.rb
|
|
53
|
+
git commit -m "test: enable Shrine derivatives plugin in test config"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Task 2: Implement `Attachment#url` with variant + strict support
|
|
57
|
+
|
|
58
|
+
**Goal:** Replace the no-arg `url` method with a signature that accepts a variant and a `strict:` kwarg, with TDD driving the full behavior matrix.
|
|
59
|
+
|
|
60
|
+
**Files:**
|
|
61
|
+
- Create: `test/attachment_test.rb`
|
|
62
|
+
- Modify: `lib/active_shrine/attachment.rb` (method at line 40-42)
|
|
63
|
+
|
|
64
|
+
**Acceptance Criteria:**
|
|
65
|
+
- [ ] `Attachment#url` accepts `(variant = nil, strict: false)`.
|
|
66
|
+
- [ ] Non-strict, no variant, stored → returns original storage URL.
|
|
67
|
+
- [ ] Non-strict, no variant, cached → returns cache URL.
|
|
68
|
+
- [ ] Non-strict, variant, stored, derivative present → returns variant URL.
|
|
69
|
+
- [ ] Non-strict, variant, stored, derivative absent → falls back to original URL.
|
|
70
|
+
- [ ] Non-strict, variant, cached → falls back to cache URL.
|
|
71
|
+
- [ ] Strict, no variant, stored → returns original URL.
|
|
72
|
+
- [ ] Strict, no variant, cached → returns nil.
|
|
73
|
+
- [ ] Strict, variant, stored, derivative present → returns variant URL.
|
|
74
|
+
- [ ] Strict, variant, stored, derivative absent → falls back to original URL.
|
|
75
|
+
- [ ] Strict, variant, cached → returns nil.
|
|
76
|
+
- [ ] No existing caller that passes zero args is broken.
|
|
77
|
+
|
|
78
|
+
**Verify:** `bundle exec rake test TEST=test/attachment_test.rb` → all 11 cases pass.
|
|
79
|
+
|
|
80
|
+
**Steps:**
|
|
81
|
+
|
|
82
|
+
- [ ] **Step 1: Write the failing test file**
|
|
83
|
+
|
|
84
|
+
Create `test/attachment_test.rb` with the full matrix. This uses `TestModel` (has_one_attached :file), attaches a real uploaded file, and toggles between cached and stored states by saving the record (which triggers promotion via the inline background blocks in the test setup — the test forces synchronous promotion by calling `file_attacher.promote` directly so we don't depend on ActiveJob).
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
require "test_helper"
|
|
88
|
+
require "stringio"
|
|
89
|
+
|
|
90
|
+
class AttachmentUrlTest < Minitest::Test
|
|
91
|
+
def setup
|
|
92
|
+
@model = TestModel.create!
|
|
93
|
+
io = StringIO.new("hello world")
|
|
94
|
+
io.define_singleton_method(:original_filename) { "hello.txt" }
|
|
95
|
+
io.define_singleton_method(:content_type) { "text/plain" }
|
|
96
|
+
@model.file = io
|
|
97
|
+
@model.save!
|
|
98
|
+
@attachment = @model.file_attachment
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def teardown
|
|
102
|
+
@model&.file_attacher&.destroy
|
|
103
|
+
@model&.destroy
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# --- helpers -------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
def promote!
|
|
109
|
+
@attachment.file_attacher.promote
|
|
110
|
+
@attachment.save!
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def add_thumb_derivative!
|
|
114
|
+
promote!
|
|
115
|
+
thumb_io = StringIO.new("thumb bytes")
|
|
116
|
+
@attachment.file_attacher.add_derivatives(thumb: thumb_io)
|
|
117
|
+
@attachment.save!
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# --- non-strict (default) ------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def test_non_strict_no_variant_stored_returns_original_url
|
|
123
|
+
promote!
|
|
124
|
+
assert_match %r{/uploads/}, @attachment.url
|
|
125
|
+
refute_match %r{/uploads/cache/}, @attachment.url
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_non_strict_no_variant_cached_returns_cache_url
|
|
129
|
+
assert_match %r{/uploads/cache/}, @attachment.url
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_non_strict_variant_stored_with_derivative_returns_variant_url
|
|
133
|
+
add_thumb_derivative!
|
|
134
|
+
assert_match %r{/uploads/.+/thumb}, @attachment.url(:thumb)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_non_strict_variant_stored_without_derivative_falls_back_to_original
|
|
138
|
+
promote!
|
|
139
|
+
url = @attachment.url(:thumb)
|
|
140
|
+
assert_match %r{/uploads/}, url
|
|
141
|
+
refute_match %r{thumb}, url
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_non_strict_variant_cached_falls_back_to_cache_url
|
|
145
|
+
assert_match %r{/uploads/cache/}, @attachment.url(:thumb)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# --- strict --------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
def test_strict_no_variant_stored_returns_original_url
|
|
151
|
+
promote!
|
|
152
|
+
assert_match %r{/uploads/}, @attachment.url(strict: true)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_strict_no_variant_cached_returns_nil
|
|
156
|
+
assert_nil @attachment.url(strict: true)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_strict_variant_stored_with_derivative_returns_variant_url
|
|
160
|
+
add_thumb_derivative!
|
|
161
|
+
assert_match %r{/uploads/.+/thumb}, @attachment.url(:thumb, strict: true)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_strict_variant_stored_without_derivative_falls_back_to_original
|
|
165
|
+
promote!
|
|
166
|
+
url = @attachment.url(:thumb, strict: true)
|
|
167
|
+
assert_match %r{/uploads/}, url
|
|
168
|
+
refute_match %r{thumb}, url
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_strict_variant_cached_returns_nil
|
|
172
|
+
assert_nil @attachment.url(:thumb, strict: true)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
178
|
+
|
|
179
|
+
Run: `bundle exec rake test TEST=test/attachment_test.rb`
|
|
180
|
+
Expected: failures — some pass (the no-arg stored case already works), but tests passing kwargs or variants will fail with `ArgumentError: wrong number of arguments` because the current `url` method takes zero arguments.
|
|
181
|
+
|
|
182
|
+
- [ ] **Step 3: Update `Attachment#url`**
|
|
183
|
+
|
|
184
|
+
In `lib/active_shrine/attachment.rb`, replace the existing method (lines 40-42):
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
def url
|
|
188
|
+
file_url
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
with:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
# Returns a URL for this attachment.
|
|
196
|
+
#
|
|
197
|
+
# @param variant [Symbol, nil] derivative name (e.g. :thumb). If the
|
|
198
|
+
# derivative does not exist, falls back to the original.
|
|
199
|
+
# @param strict [Boolean] when true, returns nil unless the file has
|
|
200
|
+
# been promoted to permanent storage. Defaults to false, which returns
|
|
201
|
+
# the cache URL during the pending window so uploads are visible
|
|
202
|
+
# immediately.
|
|
203
|
+
def url(variant = nil, strict: false)
|
|
204
|
+
attacher = file_attacher
|
|
205
|
+
return nil if strict && !attacher.stored?
|
|
206
|
+
|
|
207
|
+
(variant && attacher.url(variant)) || attacher.url
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
212
|
+
|
|
213
|
+
Run: `bundle exec rake test TEST=test/attachment_test.rb`
|
|
214
|
+
Expected: all 11 test cases pass.
|
|
215
|
+
|
|
216
|
+
- [ ] **Step 5: Run the full test suite for regressions**
|
|
217
|
+
|
|
218
|
+
Run: `bundle exec rake test`
|
|
219
|
+
Expected: no regressions in existing tests (the new signature is backward-compatible — existing `url` callers pass zero args and still work).
|
|
220
|
+
|
|
221
|
+
- [ ] **Step 6: Commit**
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
git add lib/active_shrine/attachment.rb test/attachment_test.rb
|
|
225
|
+
git commit -m "feat: support variant and strict mode in Attachment#url"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Self-Review
|
|
231
|
+
|
|
232
|
+
- **Spec coverage:** both design decisions from the spec (enhance rather than add, lenient default, variant fallback to original) are covered in Task 2. Test matrix matches the spec's "Tests" section 1:1.
|
|
233
|
+
- **Placeholder scan:** no TBD/TODO; all code blocks are concrete.
|
|
234
|
+
- **Type consistency:** `variant`, `strict:`, `file_attacher`, `stored?` used consistently across plan, code, and tests.
|
|
235
|
+
- **Verification requirement scan:** NO — spec contains no human-in-the-loop verification requirement.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"planPath": "docs/superpowers/plans/2026-04-14-attachment-url-primitive.md",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"subject": "Task 1: Enable derivatives in test shrine config",
|
|
7
|
+
"status": "pending",
|
|
8
|
+
"description": "**Goal:** Add the Shrine `derivatives` plugin to the test environment so tests can exercise variant URLs.\n\n**Files:**\n- Modify: `test/internal/config/initializers/shrine.rb`\n\n**Acceptance Criteria:**\n- [ ] `Shrine.plugin :derivatives` is loaded in the test init file.\n- [ ] Existing test suite still passes.\n\n**Verify:** `bundle exec rake test` → existing tests pass.\n\n```json:metadata\n{\"files\": [\"test/internal/config/initializers/shrine.rb\"], \"verifyCommand\": \"bundle exec rake test\", \"acceptanceCriteria\": [\"derivatives plugin loaded\", \"existing tests pass\"], \"requiresUserVerification\": false}\n```"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": 2,
|
|
12
|
+
"subject": "Task 2: Implement Attachment#url with variant + strict support",
|
|
13
|
+
"status": "pending",
|
|
14
|
+
"blockedBy": [1],
|
|
15
|
+
"description": "**Goal:** Replace the no-arg `url` method with a signature that accepts a variant and a `strict:` kwarg, driven by the full behavior matrix in tests.\n\n**Files:**\n- Create: `test/attachment_test.rb`\n- Modify: `lib/active_shrine/attachment.rb` (method at line 40-42)\n\n**Verify:** `bundle exec rake test TEST=test/attachment_test.rb` → all 11 cases pass.\n\n```json:metadata\n{\"files\": [\"test/attachment_test.rb\", \"lib/active_shrine/attachment.rb\"], \"verifyCommand\": \"bundle exec rake test TEST=test/attachment_test.rb\", \"acceptanceCriteria\": [\"new signature accepts variant and strict\", \"non-strict fallback chain works\", \"strict returns nil until stored\", \"no regressions in existing callers\"], \"requiresUserVerification\": false}\n```"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"lastUpdated": "2026-04-14"
|
|
19
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Attachment URL primitive
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
`ActiveShrine::Attachment#url` (lib/active_shrine/attachment.rb:40) delegates
|
|
6
|
+
to Shrine's `file_url` with no variant handling and no awareness of Shrine's
|
|
7
|
+
two-phase upload lifecycle (cache → promotion → stored). Callers end up
|
|
8
|
+
reimplementing the same safety logic repeatedly:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
def image_url(variant = nil)
|
|
12
|
+
attacher = image_attachment&.file_attacher
|
|
13
|
+
return nil unless attacher&.stored?
|
|
14
|
+
variant ? (attacher.url(variant) || attacher.url) : attacher.url
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This duplicates three concerns that belong in the gem:
|
|
19
|
+
|
|
20
|
+
1. Skipping the cache phase.
|
|
21
|
+
2. Falling back to the original when a derivative is missing.
|
|
22
|
+
3. Passing a variant through at all.
|
|
23
|
+
|
|
24
|
+
## Design
|
|
25
|
+
|
|
26
|
+
Enhance `Attachment#url` to accept a variant and a `strict:` kwarg. No new
|
|
27
|
+
method — there should be one way to get a URL for an attachment.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# Returns a URL for this attachment.
|
|
31
|
+
#
|
|
32
|
+
# @param variant [Symbol, nil] derivative name (e.g. :thumb). If the
|
|
33
|
+
# derivative does not exist, falls back to the original.
|
|
34
|
+
# @param strict [Boolean] when true, returns nil unless the file has been
|
|
35
|
+
# promoted to permanent storage. Defaults to false, which returns the
|
|
36
|
+
# cache URL during the pending window so uploads are visible immediately.
|
|
37
|
+
def url(variant = nil, strict: false)
|
|
38
|
+
attacher = file_attacher
|
|
39
|
+
return nil if strict && !attacher.stored?
|
|
40
|
+
|
|
41
|
+
(variant && attacher.url(variant)) || attacher.url
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Fallback chain
|
|
46
|
+
|
|
47
|
+
Non-strict (default):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
variant URL (if stored & derivative exists)
|
|
51
|
+
→ original storage URL (if stored)
|
|
52
|
+
→ cache URL (if still pending)
|
|
53
|
+
→ nil (nothing attached)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Strict:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
variant URL (if derivative exists)
|
|
60
|
+
→ original storage URL
|
|
61
|
+
→ nil (anything not yet promoted)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Call sites
|
|
65
|
+
|
|
66
|
+
`Attached::One` and `Attached::Many` use `delegate_missing_to :attachment,
|
|
67
|
+
allow_nil: true` (lib/active_shrine/attached/one.rb:28), so the new signature
|
|
68
|
+
flows through automatically:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
user.avatar.url # original; cache URL during pending
|
|
72
|
+
user.avatar.url(:thumb) # thumb → original → cache → nil
|
|
73
|
+
user.avatar.url(:thumb, strict: true) # nil until promoted
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Design decisions
|
|
77
|
+
|
|
78
|
+
- **Lenient default, strict opt-in.** Active Storage and CarrierWave return
|
|
79
|
+
a usable URL immediately after attach. Defaulting to nil-until-promoted
|
|
80
|
+
would surprise users by making uploads appear to vanish during background
|
|
81
|
+
processing. The strict mode exists for callers who explicitly want to hide
|
|
82
|
+
half-processed attachments.
|
|
83
|
+
- **Enhance `url`, don't add a new method.** Avoids two APIs doing almost
|
|
84
|
+
the same thing. The existing no-arg `url` behavior is preserved.
|
|
85
|
+
- **Variant-missing falls back to original.** Derivatives are often
|
|
86
|
+
aspirational — added after the fact, or missing because processing failed.
|
|
87
|
+
A broken `<img>` tag is worse than a wrong-size image.
|
|
88
|
+
- **No app-wide default for `strict:`.** Per-call only. Global config invites
|
|
89
|
+
action-at-a-distance bugs, and the kwarg is cheap at the call site.
|
|
90
|
+
|
|
91
|
+
## Tests
|
|
92
|
+
|
|
93
|
+
Exercise the full matrix in `spec/active_shrine/attachment_spec.rb` (or
|
|
94
|
+
equivalent). Required cases:
|
|
95
|
+
|
|
96
|
+
**Non-strict (default):**
|
|
97
|
+
- No variant, stored → original URL.
|
|
98
|
+
- No variant, cached (not stored) → cache URL.
|
|
99
|
+
- Variant, stored, derivative exists → variant URL.
|
|
100
|
+
- Variant, stored, derivative missing → original URL (fallback).
|
|
101
|
+
- Variant, cached → cache URL (fallback through variant/original).
|
|
102
|
+
|
|
103
|
+
**Strict:**
|
|
104
|
+
- No variant, stored → original URL.
|
|
105
|
+
- No variant, cached → nil.
|
|
106
|
+
- Variant, stored, derivative exists → variant URL.
|
|
107
|
+
- Variant, stored, derivative missing → original URL.
|
|
108
|
+
- Variant, cached → nil.
|
|
109
|
+
|
|
110
|
+
Tests should use Shrine's memory storage and derivatives plugin to exercise
|
|
111
|
+
real stored/cached/derivative transitions rather than mocking the attacher.
|
|
112
|
+
|
|
113
|
+
## Out of scope
|
|
114
|
+
|
|
115
|
+
- Changing `Attached::One`/`Attached::Many` — delegation handles it.
|
|
116
|
+
- Any new helper methods (`derivative_url`, `safe_url`, etc.).
|
|
117
|
+
- Configuration hooks or initializers.
|
|
118
|
+
- Changes to `representable?`, `content_type`, or other attachment metadata.
|
|
@@ -37,8 +37,19 @@ module ActiveShrine
|
|
|
37
37
|
before_save :maybe_store_record
|
|
38
38
|
|
|
39
39
|
module AttachmentMethods
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# Returns a URL for this attachment.
|
|
41
|
+
#
|
|
42
|
+
# @param variant [Symbol, nil] derivative name (e.g. :thumb). If the
|
|
43
|
+
# derivative does not exist, falls back to the original.
|
|
44
|
+
# @param strict [Boolean] when true, returns nil unless the file has
|
|
45
|
+
# been promoted to permanent storage. Defaults to false, which
|
|
46
|
+
# returns the cache URL during the pending window so uploads are
|
|
47
|
+
# visible immediately.
|
|
48
|
+
def url(variant = nil, strict: false)
|
|
49
|
+
attacher = file_attacher
|
|
50
|
+
return nil if strict && !attacher.stored?
|
|
51
|
+
|
|
52
|
+
(variant && attacher.url(variant)) || attacher.url
|
|
42
53
|
end
|
|
43
54
|
|
|
44
55
|
def content_type
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_shrine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Radioactive Labs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-04-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: railties
|
|
@@ -150,6 +150,34 @@ dependencies:
|
|
|
150
150
|
- - ">="
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
152
|
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: marcel
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: mini_mime
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">="
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0'
|
|
153
181
|
description: Write a longer description or delete this line.
|
|
154
182
|
email:
|
|
155
183
|
executables: []
|
|
@@ -165,6 +193,9 @@ files:
|
|
|
165
193
|
- README.md
|
|
166
194
|
- Rakefile
|
|
167
195
|
- config.ru
|
|
196
|
+
- docs/superpowers/plans/2026-04-14-attachment-url-primitive.md
|
|
197
|
+
- docs/superpowers/plans/2026-04-14-attachment-url-primitive.md.tasks.json
|
|
198
|
+
- docs/superpowers/specs/2026-04-14-attachment-url-primitive-design.md
|
|
168
199
|
- gemfiles/rails_7.1.gemfile
|
|
169
200
|
- gemfiles/rails_8.gemfile
|
|
170
201
|
- lib/active_shrine.rb
|