単一テーブル継承

概要:単一テーブル継承(STI)を使用して新しいテーブルを設計しないでください。STIをパターンとして使用している既存のテーブルについては、新しい型を追加することは避け、別々のテーブルに分割することを検討してください。

STIは、1つのテーブルに異なるタイプのレコードを格納するデータベース設計パターンです。これらのレコードは、共有カラムのサブセットと、そのレコードがどのオブジェクトで表現されるべきかをアプリケーションに指示する別のカラムを持ちます。これは、例えば2つの異なるタイプのSSHキーを同じテーブルに格納するために使用できます。ActiveRecordはこれを利用し、STIの利用をより便利にするいくつかの機能を提供しています。

STIテーブルの新規作成は許可されなくなりました:

  • テーブルを小さく保つように努力すべきなのに、行数の多いテーブルを作ることになります。
  • インデックスを追加する必要があり、軽量ロックの使用量が増加します。
  • すべてのデータを値でフィルタリングする必要があるため、オーバーヘッドが発生します。
  • class_name を使ってオブジェクトの正しいクラスをロードしますが、クラス名を保存するのはコストがかかり不要です。

STIを使用する代わりに、以下の選択肢を検討してください:

  • タイプごとに異なるテーブルを使用。
  • *_type カラムを追加するのは避けましょう。これは、将来新しい型が追加され、将来リファクタリングが難しくなることを示すコード臭です。
  • _type カラムの実質的なSTIであるテーブルをすでに持っている場合は、検討してみてください:
    • 既存のデータを複数のテーブルに分割します。
    • 既存のテーブルを維持したまま、新しいテーブルとして新しい型を追加できるようにリファクタリングします(たとえば、基本クラスのロジックを関心事に移動します)。

上記のデメリットと代替案をすべて考慮した結果、STIが目下の問題に対する唯一の解決策である場合、代わりにenum型を使用し、EnumInheritance 関数を使用することで、少なくともレコードにクラス名を保存するイシューを回避することができます:

class Animal < ActiveRecord::Base
  include EnumInheritance

  enum species: {
    dog: 1,
    cat: 2
  }

  def self.inheritance_column_to_class_map = {
    dog: 'Dog',
    cat: 'Cat'
  }

  def self.inheritance_column = 'species'
end

class Dog < Animal; end
class Cat < Animal; end

テーブルがすでに*_type を持っている場合、必要に応じて異なるタイプの新しいクラスを追加することができます。

マイグレーションでは

モデルがマイグレーションで使用される場合は常に、単一テーブル継承を無効にする必要があります。Railsは(マイグレーションでも)関連付けをロードするため、STIを無効にしないと予期しないコードや関連付けをロードしてしまい、アップグレード時に意図しない副作用や障害を引き起こす可能性があります。

class SomeMigration < Gitlab::Database::Migration[2.1]
  class Services < MigrationRecord
    self.table_name = 'services'
    self.inheritance_column = :_type_disabled
  end

  def up
  ...

STIを無効にする以外にモデルに何も追加する必要がない場合、またはEachBatch 、クラスを定義する代わりにヘルパーdefine_batchable_model 。これによりマイグレーションはマイグレーション用の列を分離してロードし、ヘルパーはデフォルトでSTIを無効にします。

class EnqueueSomeBackgroundMigration < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    define_batchable_model('services').select(:id).in_batches do |relation|
      jobs = relation.pluck(:id).map do |id|
        ['ExtractServicesUrl', [id]]
      end

      BackgroundMigrationWorker.bulk_perform_async(jobs)
    end
  end
  ...