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

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

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

Давайте рассмотрим несколько способов добавления тегов.

Наивный подход

Наивный подход, который вы можете сделать, - это хранить теги в виде строки, разделенной запятыми, в одном столбце базы данных, как показано ниже:

ЗА: Это очень просто.

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

ПРОТИВ: Запросы - это боль.

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

ПРОТИВ: склонность к ошибкам форматирования

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

ПРОТИВ: Ограничено длиной поля

Столбцы базы данных имеют максимальную длину, и вы можете ее достичь.

Отдельные столбцы

Развитие первого подхода состоит в том, чтобы хранить каждый тег записи в отдельном столбце, удобно помеченном tag1, tag2, tag3 и так далее.

ПРО: Выглядит правильно.

Кажется, что наличие нескольких столбцов для нескольких значений соответствует разумным методам работы с базами данных… верно? (не в этом дело).

ПРОТИВ: На самом деле запросы могут быть сложнее.

На первый взгляд это может показаться хорошей идеей, но на самом деле запросить сложнее. Теперь вам нужно включить в запрос каждый столбец. Кроме того, ваш код должен будет проверять, существует ли тег во всех возможных столбцах тегов, поскольку нет гарантии, что тег будет в каком-либо конкретном столбце для любой записи. tag1 может иметь «маслянистый» в записи 1, но в записи 2 может быть «теплый» в tag1 и «маслянистый» в tag2.

ПРОТИВ: Сложно добавить больше тегов.

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

Отдельная модель

Третий подход - создать отдельную модель для тега. Например, если вы помечаете Potato, вы должны создать модель с именем PotatoTag, которая имеет ссылку на исходный Potato через potato_id внешний ключ.

ЗА: Запросы проще, чем наивный подход

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

ЗА: Нет практических ограничений

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

МИНУС: отдельная модель для всех типов тегов.

PotatoTag, GameTag, SnailTag - для каждой модели, которая у вас есть, потребуется собственная таблица, которая очень быстро выйдет из-под контроля.

Полиморфная модель

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

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

PRO: Преимущества отдельного модельного подхода

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

ПРО: Отметьте что угодно и используйте тот же самый точный код

Полиморфные модели позволяют придерживаться принципа «Не повторяйся» (DRY). Код для взаимодействия с тегами остается неизменным, независимо от того, какую запись вы помечаете.

Полиморфная модель с ограничениями

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

Вам нужно разрешить администраторам сохранять разрешенные теги, а затем использовать этот список разрешенных тегов, когда обычные пользователи захотят пометить запись. Модель, в которой информация тега находится в TagOption, а не в самом теге, может поддерживать эту ситуацию. Сам Tag будет просто ассоциативной сущностью между записью Taggable и TagOption, причем сам TagOption будет иметь фактическую информацию о теге.

ЗА: Ограничьте количество тегов предварительно утвержденным набором.

Этот подход позволяет ограничивать теги произвольным набором тегов.

PRO: Глобальное изменение тегов

Поскольку фактическая информация тега находится в TagOption, вы можете изменить значение тега, не меняя связанные с ним модели. Например, если у вас есть параметр тега под названием «оранжевый», и вы хотите изменить его на «оранжевый», вы можете изменить одну запись (TagOption) вместо каждой Tag в базе данных.

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