Files
music_library/backlog/completed/ml-10 - Break-Records-→-Artists-→-Collection-→-Records-static-alias-cycle.md
T
Claudio Ortolina 839277de94 Done cleanup
2026-04-25 22:27:58 +01:00

4.0 KiB

id, title, status, assignee, created_date, updated_date, labels, dependencies, references, priority, ordinal
id title status assignee created_date updated_date labels dependencies references priority ordinal
ML-10 Break Records → Artists → Collection → Records static alias cycle Done
2026-04-20 08:49 2026-04-24 09:49
https://github.com/cloud8421/music_library/issues/173
low 1000

Description

GitHub: created 2026-04-16 · updated 2026-04-16

Summary

A three-way compile-connected alias cycle exists between the three context facades. mix xref graph --format cycles surfaces it. No runtime cycle (the Records → Artists edge is Oban-async).

Evidence

Records → Artists:  lib/music_library/records.ex:10 (alias)
  Artists.refresh_artist_info_async/1   records.ex:387
  Artists.prune_artist_info_async/1     records.ex:417

Artists → Collection: lib/music_library/artists.ex:10 (alias)
  Collection.collected_artist_ids/0     artists.ex:29 (used inside get_similar_artists/1)

Collection → Records: lib/music_library/collection.ex:9 (alias) + line 7 (import)
  Records.search_records/4, Records.search_records_count/2, Records.essential_fields/0
  import MusicLibrary.Records, only: [order_alphabetically: 0]

The compile-time edge in the cycle is Collection → Records via import ..., only: [order_alphabetically: 0]. order_alphabetically is a macro (records.ex:56), so the import is unavoidably compile-time. mix xref graph --format cycles --label compile reports this as a cycle of 15 nodes (3 contexts + Records.Similarity + 11 workers), meaning any change to any of those 15 modules triggers recompilation of the entire cycle.

Breaking any other edge in the triangle (including Artists → Collection) removes the cycle without needing to eliminate the macro import.

Relation to #112

#112 fixed the earlier Records ↔ Artists bidirectional dependency by moving collected_artist_ids/0 from Artists to Collection. That fix made the shape Records → Artists → Collection → Records — unidirectional in hops, but still a compile-connected cycle.

Fix

Parametrize Artists.get_similar_artists/1 to receive collected_artist_ids instead of fetching inline:

# in Artists
def get_similar_artists(artist, collected_artist_ids) do
  # ... use the set passed in
end

# in the caller (ArtistLive.Show)
collected = Collection.collected_artist_ids()
similar = Artists.get_similar_artists(artist, collected)

This removes the Artists → Collection edge without moving any behaviour. Within lib/music_library/, only Artists aliases Collection — everything else referencing Collection lives in lib/music_library_web/ (outside the cycle), so this single change is sufficient to break the cycle.

Acceptance Criteria

  • mix xref graph --format cycles --label compile reports no cycle involving Records/Artists/Collection

  • ArtistLive.Show tests still pass

  • #1 mix xref graph --format cycles --label compile reports no cycle involving Records/Artists/Collection

  • #2 ArtistLive.Show tests still pass

Final Summary

Removed alias MusicLibrary.Collection from MusicLibrary.Artists and changed get_similar_artists/1 to get_similar_artists/2, taking the collected artist id set as an argument instead of fetching it inline. The only caller (ArtistLive.Show) now computes the set via Collection.collected_artist_ids() and passes it in.

mix xref graph --format cycles --label compile now reports no cycles. The 3-way cycle is gone, and the 15-module compile-connected cycle that previously forced recompilation cascades is broken. A 14-module runtime-only cycle (Records ↔ Artists via Oban-async edges + workers) remains as expected and does not trigger recompilation.

Full test suite (836 tests including 10 in ArtistLive.ShowTest) passes. Format and Credo clean.