Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Полиморфные сквозные ассоциации в Ruby on Rails

Anna | 20.06.2014 | нет комментариев

В статье идет речь о способе создания полиморфизма для связей many-to-many в Ruby on Rails.

Задача

Возможен, что нужно разработать систему управления грузовым транспортом. В нашем распоряжении имеются несколько видов этого транспорта: поезда, вертолеты, грузовики и баржи. И вестимо, что всякое средство осуществляет перевозку только в сурово определенные населенные пункты. Скажем, часть грузовиков катается по центральной части России, часть по южной, вертолеты работают в Сибири и на Камчатке, поезда вообще ограничены железнодорожным полотном и так дальше.
Всякий вид транспорта в разрабатываемой системе будет представлен своим классом: TrainCopterTruck,Ship соответственно.
Населенные пункты (города, поселки, научные станции, здесь нас волнует не размер, а географические координаты), куда осуществляется перевозка, представлены классом Location.
Стоит условие: к всякой единице транспорта может быть привязано сколько желательно Location. В свою очередь к всякому населенному пункту может быть привязано сколько желательно единиц транспорта различных видов.

Задача решалась бы дюже легко в 2-х случаях, если бы:
— всякий населенный пункт был связан только с одним видом транспорта, то дозволено было применять обыкновенные полиморфные ассоциации;
— существовал только один вид транспорта, то дозволено было бы применять ассоциации многое-ко-многому.
Но в данном примере нужно применять 3-й метод, тот, что включает в себя вероятности обоих способов.

Неоптимальное решение

Первое, что приходит в голову, сделать четыре служебные транзитивные таблицы, которые будут объединять всякий вид транспорта с населенными пунктами.

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

Посмотреть код всецело

И класс Location, тот, что ссылается на все 4 вида транспорта

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

Уффф… Кажется здесь получилось 9 таблиц, 9 моделей и куча однородного кода. Не кажется ли, что слишком много для реализации одной связи? А если будет 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

Да, я понимаю, что moveable — не самое успешное наименование, но оно отменнее, чем transportable.

Дальше, создаем класс для хранения ассоциаций:

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 о том, что связь полиморфная.
И уменьшаем Location

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соответствие ассоциаций (trains, ships etc) значениям в колонке 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 тут каждого лишь разрешает сократить число кода и не писать :through => :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

В результате для реализации полиморфной транзитивной связи нам понадобилась только одна добавочная таблица и одна добавочная модель.
Посмотреть код всецело

P.S.:
Две строчки в видах транспорта:

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

являются всеобщими для всех четырех классов, следственно их дозволено убрать в всеобщий подключаемый модуль

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB