Resolve RepositoryLanguage#programming_language through stable language_id with transitional fallback
Related to #519895 (closed).
Problem
repository_languages references programming_languages by its id column. This value is not stable across GitLab instances, so when an organization moves to another cell, the copied repository_languages rows can point to a missing row (language name and color lookups crash) or to a different language (wrong data shown).
The language_id column is the stable identifier for this reference, but we can't switch to it outright: the backfill of repository_languages.language_id can only be finalized in 19.6, so self-managed instances running 19.3–19.5 can still have NULL values.
Proposal
Resolve the association through language_id when available and fall back to the legacy id otherwise:
belongs_to :programming_language # legacy, id-based
belongs_to :stable_programming_language,
class_name: 'ProgrammingLanguage',
foreign_key: :language_id,
primary_key: :language_id,
optional: true
def resolved_programming_language
stable_programming_language || programming_language
end
delegate :name, :color, to: :resolved_programming_languageThis behaves correctly everywhere without instance-type checks:
- Self-managed (pre-19.6): rows without
language_iduse the legacy association, which is always correct on a single cell. - GitLab.com and new cells:
language_idis fully backfilled, so the stable association wins, including for rows moved from another cell.
Preload both associations in the default_scope to avoid N+1 queries.
Follow-up in 19.6
See #614144 for removing the transitional legacy ID fallback after the backfill and NOT NULL validation are complete.