Ruby on Railsの多態的な横断的関連付け

この記事では、Ruby on Railsで多対多リレーションシップのポリモーフィズムを作成する方法について説明します。



挑戦する



貨物輸送管理システムを開発する必要があると仮定します。 この輸送には、列車、ヘリコプター、トラック、バージなど、いくつかの種類があります。 そして、各手段は厳密に定義された集落にのみ輸送を行うことが知られています。 たとえば、一部のトラックはロシアの中央部に、一部は南部に乗っており、ヘリコプターはシベリアとカムチャッカで運行しています。列車は一般に鉄道などによって制限されています。

開発されたシステムの各タイプのトランスポートは、そのクラスで表されます:それぞれTrainCopterTruckShip

輸送が行われる集落(都市、町、科学観測所、ここではサイズではなく地理座標)は、 Locationクラスによって表されます。

条件があります:任意のロケーションユニットを任意のロケーションに関連付けることができます。 次に、さまざまなタイプのトランスポートユニットをいくつでも各地域に接続できます。







次の場合、問題は2つの場合に非常に簡単に解決されます。

-各ローカリティは1つの輸送モードのみに関連付けられていたため、通常のポリモーフィックな関連付けを使用できました。

-トランスポートのモードは1つしかなく、 多対多の関連付けを使用できました。

ただし、この例では、両方の方法の機能を含む3番目の方法を使用する必要があります。



準最適なソリューション



最初に頭に浮かぶのは、各輸送モードと決済を組み合わせる4つのサービス推移テーブルを作成することです。

class Train < ActiveRecord::Base has_many :train_locations, dependent: :destroy has_many :locations, through: :train_locations end class TrainLocation < ActiveRecord::Base belongs_to :train belongs_to :location end
      
      





完全なコードを表示



そして、交通機関の4つのモードすべてを参照するLocationクラス

 class Location < ActiveRecord::Base has_many :train_locations, dependent: :destroy has_many :ship_locations, dependent: :destroy has_many :copter_locations, dependent: :destroy has_many :truck_locations, dependent: :destroy has_many :trains, :through => :train_locations has_many :ships, :through => :ship_locations has_many :copters, :through => :copter_locations has_many :trucks, :through => :truck_locations end
      
      





Ufff ... 9つのテーブル、9つのモデル、および同種のコードの束が判明したようです。 1つの接続を実現するには多すぎるように思えますか? 輸送モードが10個ある場合、実装には21個のテーブルと21個のモデルが必要ですか?

単一の推移表でポリモーフィズムを使用してみませんか?

すぐに言ってやった!



予備決定



移行を作成します。

 class CreateMoveableLocations < ActiveRecord::Migration def change create_table :moveable_locations do |t| t.references :location t.references :moveable, polymorphic: true t.timestamps end end end
      
      





はい、移動可能なものは最良の名前ではないことを理解していますが、移動可能なものよりも優れています。



次に、関連付けを保存するためのクラスを作成します。

 class MoveableLocation < ActiveRecord::Base belongs_to :location belongs_to :moveable, polymorphic: true end
      
      





トランスポートのタイプのクラスを作成します。

 class Train < ActiveRecord::Base has_many :moveable_locations, as: :moveable, dependent: :destroy has_many :locations, through: :moveable_locations end
      
      





完全なコードを表示



ここでasパラメーターは必須であり、接続がポリモーフィックであることをTrainクラスに伝えます。

場所を短くする

 class Location < ActiveRecord::Base has_many :moveable_locations, dependent: :destroy has_many :trains, :through => :moveable_locations has_many :ships, :through => :moveable_locations has_many :copters, :through => :moveable_locations has_many :trucks, :through => :moveable_locations end
      
      





テストを実行します(結局、彼らはモデルのテストを作成しますよね?)そして...合格しません。



最適なソリューション



事実、 Locationクラスにmoveable_type列の値との関連付け(電車、船など)を説明する特別な魔法がまだ少しあります。

 class Location < ActiveRecord::Base has_many :moveable_locations, dependent: :destroy with_options :through => :moveable_locations, :source => :moveable do |location| has_many :trains, source_type: 'Train' has_many :ships, source_type: 'Ship' has_many :copters, source_type: 'Copter' has_many :trucks, source_type: 'Truck' end end
      
      





ここでwith_optionsブロックを使用すると、コードの量を減らすことができるだけで、各関連付けの宣言後に == :: moveable_locations ,: source =>: moveableを書くことはできません。

sourceおよびsource_typeは、 Locationをすべてのトランスポートモードに魔法のように関連付けるパラメーターです(source_typeはclass_nameパラメーターの置き換えであるというステートメントを満たしましたが、これは完全に真実ではなく、source_typeはポリモーフィックな関連付けにのみ使用されます)

これで、次の方法でエンティティを簡単に操作できます。

 train = Train.new train.locations << city1 train.locations << city2 train.locations << city3 copter = Copter.new copter.locations << city1
      
      





それにしても:

 big_city = Location.new big_city.trains << train1 big_city.trains << train2 big_city.copters << copter1 big_city.trucks << truck1 big_city.trucks << truck2
      
      





その結果、ポリモーフィックな推移的接続を実装するには、1つの追加テーブルと1つの追加モデルのみが必要でした。

完全なコードを表示



PS:

輸送モードの2行:

  has_many :moveable_locations, as: :moveable, dependent: :destroy has_many :locations, through: :moveable_locations
      
      





4つのクラスすべてに共通であるため、共通のプラグインで削除できます



All Articles