Узнайте, как разработать систему тегов и предоставить возможность упорядочивать, фильтровать, группировать или организовывать записи на основе некоторых произвольных свойств.
Функция, которая часто требуется в веб-приложениях, - это возможность упорядочивать, фильтровать, группировать или организовывать записи на основе некоторых произвольных свойств. Во многих ситуациях его просто называют «тегом», но назначение и поведение одинаковы, независимо от того, называется ли он категориями, метаданными, группами или дескрипторами.
Взгляните на витрину популярной игровой платформы для ПК 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
в базе данных.
Это несколько способов создания тегов в вашей системе. Помните, что тегом может быть что угодно, а не просто слово или имя! Это может быть цвет, приоритет или любое дискретное значение, которое вы можете использовать для категоризации или группировки записей.