[rails/rails] Arel::TreeManagerがテーブルのengineを自動的に使用可能に

rails/rails

背景と課題

Railsで複数のデータベースアダプターを使用する際、ArelのTreeManagerサブクラス(InsertManagerUpdateManagerDeleteManagerSelectManager)でto_sqlを呼び出すと、常にTable.engine(デフォルトではActiveRecord::Base)を使用してSQLをコンパイルしていました。

これにより、異なるデータベースアダプターを使用するモデルでは、明示的にengineを渡す必要がありました。

Arel::InsertManager.new(Model.arel_table).to_sql(Model)

この冗長な記述は、特にマルチデータベース環境での開発体験を低下させる要因となっていました。

実装内容

TreeManagerの基底クラス変更

TreeManagerクラスに新しいイニシャライザーを追加し、渡されたArel::Tableインスタンスを保持するようになりました。

def initialize(table = nil)
  @table = table
end

to_sqlメソッドの改善

to_sqlメソッドが、明示的にengineが渡されない場合に、保持しているテーブルのklass(モデルクラス)を自動的に使用するようになりました。

def to_sql(engine = nil)
  unless engine
    table = @table.is_a?(Nodes::JoinSource) ? @table.left : @table
    engine = table&.klass || Table.engine
  end

  collector = Arel::Collectors::SQLString.new
  engine.with_connection do |connection|
    connection.visitor.accept(@ast, collector).value
  end
end

重要なポイントは、JoinSourceの場合は左側のテーブルを取得し、そのテーブルのklassをengineとして使用することです。klassが存在しない場合は、従来通りTable.engineにフォールバックします。

各Managerクラスの更新

DeleteManagerInsertManagerSelectManagerUpdateManagerの各クラスで、親クラスのinitializeを呼び出すように変更されました。

def initialize(table = nil)
  super

  @ast = Nodes::InsertStatement.new(table)
end

Tableクラスの拡張

Arel::Tableクラスにklassリーダーを追加し、テーブルに関連付けられたモデルクラスにアクセスできるようになりました。

attr_reader :table_alias, :klass

使用例

この変更により、以下のようにシンプルな記述が可能になりました。

# 変更後: engineを明示的に渡す必要がない
Arel::InsertManager.new(Model.arel_table).to_sql

# 内部的にModel.connectionが使用される

マルチデータベース環境でも、各モデルのコネクションが自動的に使用されるため、コードがより簡潔になります。

class Primary < ActiveRecord::Base
  connects_to database: { writing: :primary }
end

class Secondary < ActiveRecord::Base
  connects_to database: { writing: :secondary }
end

# それぞれのデータベースアダプターが自動的に使用される
Arel::InsertManager.new(Primary.arel_table).to_sql
Arel::InsertManager.new(Secondary.arel_table).to_sql

テストの追加

共通のテスト動作を定義したTreeManagerBehaviorモジュールが追加され、各Managerクラスのテストで再利用されています。このモジュールは、カスタムengineを持つテーブルでto_sqlが正しく動作することを検証します。

module TreeManagerBehavior
  included do
    describe "to_sql" do
      it "uses given table's engine if available" do
        table = Table.new(:users, klass: MyEngine)
        manager = build_manager(table)

        assert_includes manager.to_sql, "@users@"
      end
    end
  end
end

まとめ

この変更により、ArelのTreeManagerクラス群がよりスマートになり、開発者はengineを明示的に渡す必要がなくなりました。特にマルチデータベース環境での開発体験が大幅に向上し、コードの可読性と保守性が改善されています。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成、カスタムMarkdown構文、対象読者への適合性のすべての項目でガイドラインを完全に満たしています。特に、コードブロック前後の空行が徹底されており、可読性が非常に高いです。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

記事で引用されているコードは、変更の意図を正確に反映しており、技術的な説明も論理的で正確です。Arelの内部動作に関する説明(JoinSourceの扱いなど)も、提示されたコードと整合性が取れています。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

PRのタイトル「Enable Arel::TreeManagers to use Table's engine」の内容を、記事全体で忠実に解説しています。背景、実装内容、使用例のすべてがPRの目的と一致しており、ハルシネーションは検出されませんでした。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除