One home per rule: error bodies, mime types, asset paths, ordering

Four small duplications, each of which had already drifted or was one edit away
from it.

**The JSON error body.** The host's `ApiController` was a line-by-line copy of
the engine's four `rescue_from` blocks plus `resolve_mime`. It is a
`WarpEngine::ApiErrorRendering` concern now, included by both, so "what a
failure looks like on the wire" is decided once.

**Mime resolution.** The same three lines lived in the engine's API controller
and the host's. `WarpEngine::Storage.mime_for` owns it — next to the adapter
that hands out the files.

**Finding the asset behind a path.** `DownloadService` and `FileService` each
escaped `%` and `_` by hand and ran their own `LIKE` — the second one without
the exact-match attempt the first one had. `ReleaseAsset.for_relative_path` is
the one lookup: the absolute path first, then a suffix match anchored at `/`
rather than the old `%path%`, which could match a different file whose name
merely contained this one. Two now-unreachable helpers
(`DownloadService.base_path`, `FileService.base_path`) go with it.

**Ordering.** `PlatformLink` and `SoftwareImage` ordered inside their
`default_scope`, which leaks into every association and aggregate — the proof
was already in the tree: `admin/images.rb` had to write
`SoftwareImage.unscope(:order).distinct.pluck(:image_id)`, because MySQL will
not order a DISTINCT by a column it does not select. Ordering is a scope you
ask for now (`ordered`), the two places that need it ask (`for_platform`, the
`software_images` association, so the API's image order is unchanged), and the
`unscope` is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-23 09:01:53 +02:00
co-authored by Claude Opus 5
parent 6024ef71c3
commit e9594a663d
8 changed files with 54 additions and 42 deletions
+12 -2
View File
@@ -28,11 +28,21 @@ RSpec.describe WarpEngine::PlatformLink, type: :model do
expect(WarpEngine::PlatformLink.all).to eq([active])
end
it "orders by position" do
it "does not order: ordering is asked for, not inherited" do
second = create(:platform_link, position: 2)
first = create(:platform_link, position: 1)
expect(WarpEngine::PlatformLink.all).to eq([first, second])
expect(WarpEngine::PlatformLink.ordered).to eq([ first, second ])
expect(WarpEngine::PlatformLink.all.to_sql).not_to include("ORDER BY")
end
end
describe ".ordered" do
it "is what .for_platform uses, so the API keeps its link order" do
second = create(:platform_link, platform: "tic80", position: 2)
first = create(:platform_link, platform: "tic80", position: 1)
expect(WarpEngine::PlatformLink.for_platform("tic80")).to eq([ first, second ])
end
end