найдите то, что вам подходит

1656636978 najdite to chto vam podhodit

Хейли Мнацеганян

Tqf7egsDTES1Wb9bGFx8BsA635NTnON4y9Ch
Фото Sanwal Deen на Unsplash

Если вы когда-либо создавали приложение с более чем одной моделью, вам приходилось думать о том, какой тип связей использовать между этими моделями.

Поскольку сложность приложения растет, может быть трудно решить, какие связи должны существовать между вашими моделями.

Часто возникает ситуация, когда нескольким моделям требуется доступ к функциям третьей модели. Для решения этого события Rails предлагает два метода однотабличное наследование и полиморфная ассоциация

DTM68ANtS7ExFvaAe8D9OFXE1nKiVDF8dCVQ

В однотабличном наследовании (STI), многие подклассы наследуют от одного суперкласса со всеми данными в одной таблице в базе данных. Суперкласс имеет столбец «тип», чтобы определить, к какому подклассу принадлежит объект.

В полиморфная ассоциация, одна модель «принадлежит» к нескольким другим моделям с помощью одной ассоциации. Каждая модель, включая полиморфную модель, имеет свою таблицу в базе данных.

Давайте рассмотрим каждый метод, чтобы увидеть, когда мы его используем.

Однотабличное наследование

Прекрасный способ узнать, когда STI подходит, это когда ваши модели общие данные/состояние. Совместное поведение необязательно.

Давайте представим, что мы создаем программу, содержащую список различных автомобилей, продаваемых в местном дилерском центре. Этот дилерский центр продает автомобили, мотоциклы и велосипеды.

(Я знаю, что дилерские центры не продают велосипеды, но терпите меня на минутку – вы увидите, к чему я веду с этим.)

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

Мы можем создать суперкласс Vehicle с атрибутами цвет, цена и покупка. Каждый из наших подклассов может наследовать от Vehicle и все могут получить те же атрибуты одним махом.

Наша миграция для создания таблицы транспортных средств может выглядеть следующим образом:

class CreateVehicles < ActiveRecord::Migration[5.1]  def change                               create_table :vehicles do |t|                                   t.string :type, null: false                               t.string :color                                   t.integer :price                                  t.boolean :purchased, default: false                                                          end                           end                       end

Важно, что мы создаем type колонка для сверхкласса. Это сообщает Rails, что мы используем STI и хотим получить все данные Vehicle и его подклассы должны находиться в одной таблице в базе данных.

Наши модельные классы будут выглядеть так:

class Vehicle < ApplicationRecordend
class Bicycle < Vehicleend
class Motorcycle < Vehicleend
class Car < Vehicleend

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

Кроме того, поскольку мы знаем, что подклассы совместно используют те же поля данных, мы можем совершать одинаковые вызовы объектов из разных классов:

mustang = Car.new(price: 50000, color: red)harley = Motorcycle.new(price: 30000, color: black)
mustang.price=> 50000
harley.price=> 30000
0g6EmTW86Pjlpxg6PSODx4lr4JREv3lchFn3
Гм, где я могу найти этот дилер? (источник)

Добавление функциональности

Теперь допустим, что дилер решил собрать дополнительную информацию о транспортных средствах.

Для Bicycles, она хочет знать, есть ли каждый велосипед дорожным, горным или гибридным. И для Cars и Motorcyclesона хочет следить за лошадиными силами.

Поэтому мы создаем миграцию для добавления bicycle_type и horsepower к Vehicles стол.

Вдруг у наших моделей больше нет идеального совместного использования полей данных. Любой Bicycle объект не будет иметь a horsepower атрибут, и любой Car или Motorcycle не будет a bicycle_type (надеюсь — я дойду до этого через мгновение).

Однако каждый велосипед в нашей таблице будет иметь a horsepower поле, и каждый автомобиль и мотоцикл будет иметь a bicycle_type поле.

Вот где все может стать липким. В этой ситуации может возникнуть несколько проблем:

  1. Наша таблица будет иметь много нулевых значений (nil в случае Ruby), поскольку объекты будут иметь поля, которые к ним не применяются. Или nulls может вызвать проблемы, поскольку мы добавляем проверки к нашим моделям.
  2. С увеличением таблицы мы можем столкнуться с затратами на производительность при запросах, если мы не добавим фильтры. Поиск определенного bicycle_type посмотрят каждый предмет в таблице— да не только Bicyclesно Cars и Motorcycles тоже.
  3. Ничто не мешает пользователю добавить «неподходящие» данные в неправильную модель. К примеру, пользователь с некоторыми знаниями может создать a Bicycle с horsepower из 100. Нам нужны проверки и хороший дизайн программы, чтобы предотвратить создание недействительного объекта.

Итак, как мы видим, у STI есть некоторые недостатки. Это отлично подходит для программ, где ваши модели имеют общие поля данных и вряд ли изменятся.

ПЛЮСЫ ИППП:

  • Прост в исполнении
  • DRY – сохраняет реплицированный код с использованием наследования и общих атрибутов
  • Позволяет подклассам иметь собственное поведение при необходимости

МИНУСЫ ИППП:

  • Плохо масштабируется: по мере роста данных таблица может стать большой, и ее возможно трудно поддерживать/запрашивать
  • Требует осторожности при добавлении новых моделей или полей, которые отличаются от общих полей.
  • (условный) Позволяет создавать недействительные объекты, если проверки не на месте
  • (условный) Может быть сложно проверить или спросить, если в таблице существует много нулевых значений

Полиморфные ассоциации

С полиморфными ассоциациями модель может belong_to несколько моделей с одной ассоциацией.

YgakgeaPyCa0SWLFcxPmUERwET59Js3Jr9tI
Настало время морфина. (источник)

Это полезно, когда несколько моделей не имеют связи или обмениваются данными друг с другом, но имеют связь с полиморфным классом.

Как пример, давайте подумаем о социальной сети, как Facebook. В Facebook как отдельные личности, так и группы могут делиться публикациями.

Лица и группы не связаны между собой (за исключением того, что они являются типом пользователей), поэтому они имеют разные данные. Возможно, группа имеет такие поля, как member_count и group_type не относящиеся к отдельному лицу, и наоборот).

Без полиморфных ассоциаций мы имели бы что-то вроде этого:

class Post  belongs_to :person  belongs to :groupend
class Person  has_many :postsend
class Group  has_many :postsend

Обычно, чтобы узнать, кто является владельцем определенного профиля, мы смотрим на столбец, который есть foreign_key. А foreign_key Это идентификатор, используемый для поиска связанного объекта в таблице связанной модели.

Однако наша таблица Posts будет иметь два конкурирующих внешних ключа: group_id и person_id. Это было бы проблематично.

Когда мы пытаемся найти владельца публикации, мы должны проверить оба столбца, чтобы найти правильный Foreign_key, а не полагаться на один. Что произойдет, если мы столкнемся с ситуацией, когда оба столбца имеют значение?

Полиморфная ассоциация разрешает эту проблему путем уплотнения этой функциональности в одну ассоциацию. Мы можем представить наши классы так:

class Post  belongs_to :postable, polymorphic: trueend
class Person  has_many :posts, as :postableend
class Group  has_many :posts, as :postableend

Конвенция Rails для именования полиморфной ассоциации использует «-able» с названием класса (:postable для Post класс). Это дает понять в ваших отношениях, какой класс является полиморфным. Но вы можете использовать любое название для вашей полиморфной ассоциации.

Чтобы сообщить нашей базе данных, что мы используем полиморфную ассоциацию, мы используем специальные столбцы type и id для полиморфного класса.

The postable_type столбец записывает, к какой модели принадлежит пост, тогда как postable_id столбец отслеживает идентификатор объекта-собственника:

haley = Person.first=> returns Person object with name: "Haley"
article = haley.posts.firstarticle.postable_type=> "Person"
article.postable_id=> 1 # The object that owns this has an id of 1 (in this case a      Person)
new_post = haley.posts.new()# Automatically fills in postable_type and postable_id using haley object

Полиморфная ассоциация — это просто комбинация двух или более ассоциаций. Поэтому вы можете действовать так же, как и при использовании двух моделей, имеющих ассоциацию belongs_to.

Примечание: полиморфные ассоциации работают как с has_one, так и с has_many ассоциациями.

haley.posts# returns ActiveRecord array of posts
haley.posts.first.content=> "The content from my first post was a string..."

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

Чтобы сделать это быстро, необходимо добавить столбец внешнего ключа и столбец типа в полиморфный класс. Вы можете найти владельца публикации с помощью postable:

new_post.postable=> returns Person object
new_post.postable.name=> "Haley"

Кроме того, Rails реализует определенную безопасность в полиморфных отношениях. Только классы, являющиеся частью связи, могут быть включены как a postable_type:

new_post.update(postable_type: "FakeClass")=> NameError: uninitialized constant FakeClass

ВНИМАНИЕ

Полиморфные ассоциации имеют один огромный красный флаг: нарушена целостность данных.

muZSd9uTpviItuoWh7aR83AVSRZJahsE6AjG
Скотт Адамс из http://dilbert.com/

В обычном отношении belongs_to мы используем внешние ключи для ссылки в ассоциации.

Однако они обладают большей силой, чем просто формирование связи. Внешние ключи также предотвращают ошибки ссылок, требуя, чтобы объект, на который ссылается во внешней таблице, действительно существовал.

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

К сожалению, полиморфные классы не могут иметь внешние ключи по обсуждаемым нами причинам. Мы используем type и id столбцы вместо внешнего ключа. Это означает, что мы теряем защиту, которую предлагают внешние ключи.

Rails и ActiveRecord помогают нам на поверхности, но каждый, кто имеет прямой доступ к базе данных, может создавать или обновлять ссылающиеся на нулевые объекты объекты.

Например, проверьте эту команду SQL, в которой создается сообщение, даже если группа, с которой она связана, не существует.

Group.find(1000)=> ActiveRecord::RecordNotFound: Couldn't find Group with 'id'=1000
# SQLINSERT INTO POSTS (postable_type, postable_id) VALUES ('Group', 1000)=> # returns success even though the associated Group doesn't exist

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

Полиморфная ассоциация ПРЕИМУЩЕСТВА:

  • Легко масштабировать объем данных: информация распределяется между несколькими таблицами базы данных, чтобы минимизировать разгрузку таблицы
  • Легко масштабировать количество моделей: больше моделей можно легко связать с полиморфным классом
  • DRY: создает один класс, который может использоваться многими другими классами

Полиморфная ассоциация CONS

  • Больше таблиц может сделать запросы более сложными и более дорогими по мере увеличения данных. (Чтобы найти все сообщения, созданные за определенный период времени, необходимо сканировать все связанные таблицы)
  • Не может иметь внешний ключ. The id столбец может ссылаться на любую из связанных таблиц модели, что может замедлить запросы. Он должен работать в сочетании с type колонка.
  • Если ваши таблицы очень велики, для хранения строчных значений используется много места postable_type
  • Целостность ваших данных нарушена.

Как узнать, какой метод использовать

ИППП и полиморфные ассоциации имеют определенное пересечение, когда речь идет о случаях использования. Хотя это не единственное решение для «деревовидной» модели отношений, оба имеют некоторые очевидные преимущества.

Оба Vehicle и Postable Примеры можно было реализовать с помощью любого метода. Однако было несколько причин, которые дали понять, какой метод наилучший в каждой ситуации.

Вот четыре фактора, которые следует учитывать, когда решать, отвечает ли любой из этих методов вашим потребностям.

  1. Структура базы данных. STI использует только одну таблицу для всех классов связи, тогда как полиморфные ассоциации используют таблицу для каждого класса. Каждый метод имеет свои преимущества и недостатки по мере расширения программы.
  2. Общие данные или состояние. STI – отличный вариант, если ваши модели имеют много общих атрибутов. В противном случае полиморфная ассоциация, вероятно, является лучшим выбором.
  3. Будущие проблемы. Подумайте, как ваше приложение может изменяться и развиваться. Если вы рассматриваете STI, но думаете, что добавите модели или поля модели, отличные от общей структуры, вы можете просмотреть свой план. Если вы думаете, что ваша структура, скорее всего, останется такой же, STI останется в целом быть быстрее для запросов.
  4. Цельность данных. Если данные не будут содержаться (одна программа использует базу данных), полиморфная ассоциация, вероятно, будет плохим выбором, поскольку ваши данные будут скомпрометированы.

Заключительные мнения

Ни ИППП, ни полиморфные ассоциации не идеальны. Они оба имеют плюсы и минусы, которые часто делают один или другой более подходящим для ассоциаций со многими моделями.

Я написал эту статью, чтобы научить себя этим концепциям так же, как и кому-то другому. Если есть что-то неправильное или какие-то моменты, которые, по вашему мнению, следует упомянуть, пожалуйста, помогите мне и всем остальным, разделившись в комментариях!

Если вы что-нибудь узнали или нашли это полезным, нажмите на ? кнопку, чтобы выразить свою поддержку!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *