87 подписчиков
Фильтрация данных: язык вместо кода
Хочу рассказать про интересное решение, которое позволяет унифицировать и значительно упростить фильтрацию данных в api или при обычных запросах с параметрами запроса.
Обычно подобная фильтрация выглядит так: site.com/items?size=5&city=london
Как правило, реализация подобных фильтров делается руками. Мы придумываем набор полей, по которым надо фильтровать и дальше в бекенде формируем sql запрос через билдер.
app.get('/employees', (req, res) => {
const { firstName, lastName, age } = req.query;
let results = [...employees];
if (firstName) {
results = results.filter(r => r.firstName === firstName);
}
if (lastName) {
results = results.filter(r => r.lastName === lastName);
}
if (age) {
results = results.filter(r => +r.age === +age);
}
res.json(results);
});
Самая мякотка в этой системе появляется тогда, когда у нас не просто совпадение по значению, а разнообразные предикаты в стиле “больше”, “меньше равно”, “содержит”, поиск по связным сущностям и так далее. В большинстве случаев такой код пишется прямо под конкретную ситуацию на бекенде. В интернете немало статей про это: graphql https://www.apollographql.com/blog/graphql/filtering/how-to-search-and-filter-results-with-graphql/ java https://medium.com/@cmmapada/advanced-search-and-filtering-using-spring-data-jpa-specification-and-criteria-api-b6e8f891f2bf
Но существует и более интересный подход, который позволяет автоматизировать задачу практически на 100%, убрать написание большей части предикатов и получить возможность реализовывать фильтры высокой сложности за полчаса работы. Познакомился я с этим подходом в библиотеке ransack, которая автоматически встраивается в Rails ORM. Работа этой библиотеки с точки зрения сервера сводится буквально к одной строчке: Model.ransack(params). А дальше начинается самое интересное. Концепция этой библиотеки состоит в том, что вместо императивного описания что делать на бекенде, мы декларативно описываем как фильтровать через правильное именование параметров.
Например, если мы хотим точное соответствие по полю name, то мы должны назвать параметр name_eq, если мы хотим искать по вхождению то name_cont. Тоже самое с числами. Если нам нужен возраст старше или равно, то мы пишем age_gt (greater than). Если мы хотим тоже самое но по связанным сущностям, то тогда описываем имя через связь user__name_eq (то есть ищем по имени пользователя, где пользователь это связь).
<input type="text" name="q[first_name_or_last_name_cont]">
<input type="text" name="q[email_cont]">
Хекслет весь насквозь увешан такими фильтрами. В большинстве случаев нам хватает дефолтных предикатов, но есть и пара кастомных. В любом случае даже кастомные предикаты работают по такой же схеме и мы можем применять их к разным сущностям если они это технически позволяют.
Дока по предикатам: https://activerecord-hackery.github.io/ransack/getting-started/using-predicates/
Демо Ransack: http://ransack-demo.herokuapp.com/users/advanced_search
Домашнее задание: Подобные библиотеки существуют и в других языках, но не все о них знают. Попробуйте найти библиотеку для своего стека и сбросьте в комментарии ссылку на гитхаб
2 минуты
18 сентября 2023