Railsの非標準STI

ストーリーを始める前に、 STIとは何かを思い出してください。



STI(単一テーブル継承)は、オブジェクト指向の継承をリレーショナルデータベーステーブルに転送できる設計パターンです。 データベーステーブルには、階層内のクラスの名前を識別するフィールドが含まれている必要があります。 多くの場合、RoRを含めて、フィールドはタイプと呼ばれます。



このパターンを使用すると、同じフィールドセットを含むが、動作が異なるオブジェクトを作成できます。 たとえば、名前、ユーザー名、パスワードを含むユーザーテーブルがありますが、管理者ユーザーの2つのクラス、Visitorが使用されました。 各クラスには、継承されたメソッドと個別のメソッドセットの両方が含まれています。 どのクラスを作成するかを決定し、 type fieldを使用して、フィールド名を再定義できます。



したがって、標準的なケースを考慮すると、クラス名はデータとともに1つのテーブルに格納されます。



しかし、別の状況が発生する可能性があります...



既存のデータベースの上に必要なタスクがありますが、特定のWebエディターはすでに多くのことに結びついています。 また、既存の回路がORMの要件を完全に満たす可能性はわずかです。 その結果、モデルを構成するときに全体をプルアップする必要があります。



正規化のためのかなり一般的な方法は、ルックアップテーブルの使用です。



たとえば、連絡先タイプのディレクトリに関連付けられた連絡先テーブルです。 この場合、入力されたデータをモデルレベルで確認したり、値をフォーマットするためのメソッドを追加したりすることができます。



この問題を解決するには2つの方法があります。

  1. STIを使用し、ここで直接要求します。
  2. ロジックをcaseで定義する1つのシッククラスを使用します。


私は2番目の選択肢も考えていません かさばりすぎて柔軟性がありません。 したがって、私たちは最初にこだわる。



したがって、 STIを使用するには、クラスを指す追加フィールドが必要です。 スキームをやり直すことは可能ですが、冗長性が増すため、正しい状態を維持する必要があります。 上記の例の場合、 タイプフィールドを追加するときに、フィールドの値を外部キーと同期する必要があります。 したがって、利用可能なデータを使用することは論理的です。 なぜなら クラス名が作成される前に決定される場合、 ActiveRecord自体の作業に介入する必要があります。



ドキュメントとソースを掘り下げて、このメカニズム全体を明らかにしました。 ActiveRecord :: Inheritanceモジュールにあるインスタンス化メソッドが責任を負います:

# File activerecord/lib/active_record/inheritance.rb, line 61 def instantiate(record) sti_class = find_sti_class(record[inheritance_column]) record_id = sti_class.primary_key && record[sti_class.primary_key] if ActiveRecord::IdentityMap.enabled? && record_id instance = use_identity_map(sti_class, record_id, record) else instance = sti_class.allocate.init_with('attributes' => record) end instance end
      
      





この方法は非常に簡単です。

  1. 作成するクラスが定義されます。
  2. IdentityMapサポートが有効になっている場合、それを使用します。それ以外の場合は、データベースから受信したデータに基づいて新しいインスタンスを作成します。


どのクラスを作成するかをどのように決定するかを考えてみましょう。 これを行うには、ソースコード、つまりfind_sti_classメソッドをさらに調べます。このメソッドは、対応するinheritance_columnフィールドから取得した型の名前が転送されます。上記のように、デフォルトではtypeと同じです。



ご覧のとおり、特別な魔法はありません。 したがって、タスクを解決するには、フィールドの値の代わりに、リンクテーブルから受け取った別のメソッドが転送されるように、インスタンス化メソッドを再定義する必要がありました。



得られたソリューションは、Gem-aの形式でフレーム化されました。 アソシエーションと同じ原理で機能します。 ActiveRecordは、 belongs_toメソッドと同じ構文を持つ、追加のActs_as_atiメソッドで拡張されます。



 @association_inheritance = { id: 0, field_name: params[:field_name] || :name, block: block_given? ? Proc.new {|type| yield type } : Proc.new{ |type| type }, class_cache: {}, alias: {} } params.delete :field_name @association_inheritance[:association] = belongs_to(association_name, params) validates @association_inheritance[:association].foreign_key.to_sym, :presence => true before_validation :init_type
      
      





この方法では、通信に関する補助情報を使用してハッシュが形成され、関係自体とバリデーターも追加されます。 さらに、インスタンスはいくつかのヘルパーメソッドで拡張され、実際にオーバーロードが実行されます。



オーバーロードされたメソッドは基本的に変更されず、作成された関係に基づいたクラス名の受信のみが追加されます。



 params = self.association_inheritance class_type = if record.is_a? String (params[:alias][record.to_s.downcase.to_sym] || record).to_s.classify else association = params[:association] type_id = record[association.foreign_key.to_s] params[:class_cache][type_id] ||= begin inheritance_record = association.klass.find(type_id) value = inheritance_record.send(params[:field_name].to_sym) value = (params[:alias][value.to_s.downcase.to_sym] || value) value.to_s.classify rescue ::ActiveRecord::RecordNotFound '' end end sti_class = find_sti_class(params[:block].call(class_type))
      
      





これが主な変更の終わりです。 結果のクラスを使用して、リンクテーブルを介してSTIを実装することが判明しました。 このアプローチはパフォーマンスにマイナスがあります(データキャッシュによっていくつかの場所で決定されました)が、同時にポリモーフィズムを完全に使用することを可能にします。



使用例:

 class PostType < ActiveRecord::Base end class Post < ActiveRecord::Base attr_accessible :name acts_as_ati :type, :class_name => PostType, :foreign_key => :post_type_id, :field_name => :name do |type| "#{type}Post" end end class ForumPost < Post attr_accessible :name ati_type :forum end class BlogPost < Post attr_accessible :name ati_type :blog end
      
      





このソリューションは、内部リソースの作業で使用され、これまでのところ肯定的な側面でのみ表示され、より読みやすくサポートしやすいコードを作成できるようになりました。



Gemはまだrubygemsでホストされていませんが、Gemfileを介して接続できます。

gem 'ext_sti', :git => 'git://github.com/fuCtor/ext_sti.git'





ローカルコピーとして

gem 'ext_sti', :path => %path_to_ext_sti%






All Articles