Custom locators can override model class derivation

rails/globalid

GlobalID now lets custom locators define a model_class(gid) method, giving developers control over how a GID is mapped to a Ruby class. This change removes the assumption that the model name in a GID must match a top‑level constant, while retaining compatibility for existing locators.

背景

Historically GlobalID derived the model class by constantizing the model name extracted from the GID, which forced a one‑to‑one naming convention between GIDs and Ruby classes. Issue #80 highlighted a real‑world scenario where services are being split and GIDs refer to namespaced models that no longer exist in the top‑level namespace. Developers needed a way to map those names to the appropriate local classes without renaming or duplicating constants. By allowing locators to override the derivation logic, the library now supports more flexible service‑oriented architectures.

技術的な変更

The core change adds a hook for locators to provide their own model_class(gid) implementation, while preserving the previous constantize behavior as a fallback. This hook is invoked from GlobalID#model_class, which now consults the associated locator before falling back.

@@
   def model_class
     @model_class ||= begin
-      model = model_name.constantize
+      locator = Locator.locator_for(self)
+      model = begin
+        locator.model_class(self)
+      rescue NoMethodError
+        if locator.respond_to?(:model_class)
+          raise
+        else
+          GlobalID.deprecator.warn <<~MSG.squish
+            Your locator #{locator.class.name} does not implement the
+            `model_class` method. Please add a `model_class(gid)` method
+            to your locator or inherit from `GlobalID::Locator::BaseLocator`.
+          MSG
+          model_name.constantize
+        end
+      end
       if model <= GlobalID
         raise ArgumentError, "GlobalID and SignedGlobalID cannot be used as model_class."
       end
*** End Patch
*** End Patch

In lib/global_id/locator.rb, the default BaseLocator and BlockLocator classes now implement a basic model_class method that simply constantizes the GID's model name. The visibility of locator_for was also adjusted to be public, enabling the new call path.

@@
-      private
-        def locator_for(gid)
-          @locators.fetch(normalize_app(gid.app)) { default_locator }
-        end
+      def locator_for(gid)
+        @locators.fetch(normalize_app(gid.app)) { default_locator }
+      end
@@
-      class BaseLocator
+      class BaseLocator
+        def model_class(gid)
+          gid.model_name.constantize
+        end
@@
-        class BlockLocator < Locator
+        class BlockLocator < Locator
+          def model_class(gid)
+            gid.model_name.constantize
+          end

The README was updated to document the new capability, showing how to inherit from BaseLocator and override model_class. Sample code demonstrates redirecting remote model names like User to local classes such as RemoteUser.

#### Custom Model Class Derivation

```ruby
class RemoteLocator < GlobalID::Locator::BaseLocator
  def model_class(gid)
    case gid.model_name
    when 'User'    then RemoteUser
    when 'Profile' then RemoteProfile
    else
      super
    end
  end
end

Test suite additions verify three scenarios: a locator that implements `model_class`, a locator that inherits the default implementation, and a legacy locator that triggers the deprecation warning. These tests ensure both the new happy path and the backward‑compatibility path behave as intended.

```ruby:test/cases/global_locator_test.rb
@@
   test 'locator with custom model_class derivation' do
@@
   test 'locator without model_class method shows deprecation warning' do
@@

設計判断

The team chose to extend the existing Locator abstraction rather than introducing a separate mapping API. By adding model_class to BaseLocator and BlockLocator, existing custom locators can gain the new capability simply by inheriting from the provided base class. This minimizes the surface area of change and keeps the public contract familiar to current users.

When a locator does not implement model_class, the library falls back to the historic constantize approach but emits a deprecation warning. This design preserves runtime compatibility for legacy code while nudging developers toward the newer, more explicit inheritance pattern. The warning message explicitly references the missing method and recommends inheriting from BaseLocator, providing clear migration guidance.

Recommending inheritance from GlobalID::Locator::BaseLocator (or UnscopedLocator for ActiveRecord models) balances flexibility with safety. It allows custom logic for name translation without sacrificing the default fast path, and it keeps the locator interface cohesive by centralising related responsibilities (locate, locate_many, model_class) within a single class hierarchy.

まとめ

By allowing custom locators to override model_class(gid), GlobalID now supports arbitrary name-to‑class mappings, facilitating modular service architectures and legacy integrations. The change is backward compatible, guarded by a deprecation warning, and encourages a clean inheritance‑based extension pattern for future locator implementations.

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
ab1e5776

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文がタイトル直下にあり、背景・技術的な変更・まとめの各セクションが揃っているため、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのファイル名付きシンタックスハイライトは正しく使用されていますが、PRリンクが "[PR #203](...)" となっており、要求されている "[#203](URL)" 形式になっていません。リンクテキストを #203 のみにすれば合格になります。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

対象はRailsエンジニア向けで、専門用語や実装詳細に焦点が当たっており、初心者向けの過度な説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは要旨を述べる冒頭パラグラフから始まり、具体的なコードや説明が続き、最後にまとめがある構成です。段落はトピックセンテンスで始まり、1段落1トピック、長さも適切で空行で区切られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは提供されたDiffと一致しており、削除・追加行ともに正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている用語(custom locators、model_class、constantize、deprecation warning、BaseLocator など)はPRで使われているものと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的説明はPRの内容とDiffに基づいており、ロジックや挙動の説明に矛盾や誤りはありません。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

全ての主張はPRタイトル・説明・Diffで裏付けられており、外部の推測や捏造は見当たりません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号 #203 と Issue #80 が正しく記載されており、他の数値や固有名詞の誤りはありません。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の目的「カスタムロケータが model_class を上書きできる」ことを正確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事はPR情報以外の外部知識(バージョンサポート、LTS など)を追加していません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現は使用されておらず、PRの記述と食い違う表現もありません。