Дефолтная сортировка что это

от admin

20 хитростей при работе с Laravel Eloquent

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

01: Увеличение и уменьшение

Вы можете сделать так:

02: Методы XorY

Eloquent имеет довольно много функций, которые объединяют оба метода, типа «сделайте X, если не получится, то сделайте Y».

Пример 1 — findOrFail():
Пример 2 — firstOrCreate():

03: Модельный метод boot()

В модели Eloquent есть волшебный метод boot(), в котором вы можете переопределить дефолтное поведение:

Один из самых популярных примеров, это установка значений для некоторых полей в момент создания объекта модели.
Например, генерируем поле UUID в этот момент.

04: Отношения с условиями и сортировкой

Это стандартный способ задания отношений:

А вы знали, что мы сразу можем добавить методы where и orderBy?

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

05: Свойства модели: временные метки, добавления и тому подобное

Есть несколько «параметров» модели Eloquent в форме свойств этого класса.
Самые популярные из них следующие:

Но подождите, это еще не все:

И есть еще больше, я просто перечислил самые интересные. Чтобы узнать больше, посмотрите код абстрактного класса Model

06: Найти несколько значений

Все знают метод find(), верно?

Я был весьма удивлен, как мало кто знает, что этот метод может принимать несколько идентификаторов в виде массива:

07: WhereX

Есть элегантный способ превратить это:

Да, вы можете изменить имя любого поля и добавить его в качестве суффикса к where, и это будет работать.

Кроме того, в Eloquent есть несколько предопределенных методов, связанных с датой и временем:

08: Сортировка по отношениям

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

Сначала сделайте отдельное отношение для последнего сообщения в теме:

А теперь, в нашем контроллере, мы займемся магией:

09: Eloquent::when() – без if-else

Многие из нас пишут условные запросы с помощью if-else, что-то вроде такого:

Но есть способ лучше — используйте when():

Возможно, он не выглядит более коротким и элегантным, но он круче в передаче параметров:

10: BelongsTo дефолтная модель

Допустим у вас есть Сообщение (Post), принадлежащее Автору (Author) и такой вот код в шаблоне Blade:

Но, что если автор удалён или не установлен по какой-то другой причине? Тогда вы получите ошибку, что-то вроде «property of non-object»
Конечно, вы можете предотвратить это:

Но вы можете сделать это на уровне Eloquent отношений:

В этом примере отношение author() вернет пустую модель App\Author, если за Сообщением не закреплен ни один Автор.

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

11: Сортировка по Преобразователю

Представим, что у нас есть:

Теперь попробуйте отсортировать по full_name. Не сработает:

Решение довольно простое. Нам нужно отсортировать результаты ПОСЛЕ их получения:

12: Дефолтная сортировка в глобальном Scope

Например, вы хотите чтобы User::all() всегда был отсортирован по полю name. Для этого вы можете назначить глобальный Scope. Вернемся к методу boot(), о котором вы уже говорили выше.

13: Методы сырых запросов

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

14. Replicate: копия строки

Без углубленных объяснений — вот лучший способ сделать копию записи базы данных:

15: Метод chunk() для больших таблиц

Не совсем об Eloquent, скорее о Collection, тем не менее, вот мощный способ обработки больших наборов данных: вы можете разбить их на части.

Вы можете сделать:

16: Дополнительные действия при создании модели

Мы все знаем эту команду Artisan

Но знаете ли вы, что есть три полезных флага для генерации файлов, связанных с моделью?

-m создаст файл миграции
-c создаст контроллер
-r указание, что контроллер должен содержать готовые ресурсы

17: Переопределение updated_at при сохранении

А вы знали, что метод ->save() может принимать параметры?
В результате мы можем заставить его «игнорировать» дефолтную функцию заполнения поля updated_at текущей отметкой времени. Смотрите:

Здесь мы переопределяем дефолтную updated_at на нашу собственную.

18: Каков результат update()?

Задумывались ли вы о том, что на самом деле возвращает этот код?

Я имею в виду, что обновление выполняется в базе данных, но в итоге чему будет равна переменная $result?

Ответ количество затронутых строк. Поэтому, если вам нужно проверить, сколько строк было затронуто, вам не нужно больше ничего вызывать — метод update() вернет вам это число.

19: Преобразование скобок в запросе Eloquent

Что делать, если у вас в SQL запросе смешаны AND и OR, например:

Как перевести это в Eloquent? Неправильный способ:

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

20: orWhere с несколькими параметрами

Вы можете передать массив параметров в orWhere().
«Обычный» способ:

Глубокая персонализация товарных витрин интернет-каталога

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

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

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

Именно с такой ситуацией я и столкнулся в 2018 году, имея в каталоге компании Адамас более 1200 моделей ювелирных колец. Что в свою очередь породило гипотезу: если возможно сделать персональную рекомендацию к товару в карточке товара, то почему нельзя персонализировать весь каталог?

Шаг 1: Подготовка данных

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

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

И мы начали собирать сырые данные о поведении пользователей, на тот момент ещё плохо понимая, что будем делать с ними в дальнейшем. Собирали буквально всё: открытия карточек товара, просмотр изображений товара, доскроливание до того или иного блока на сайте, взаимодействие с этим блоком, фиксировали время затраченное на все эти операции, и, конечно же, привязывали это всё к id пользователя.

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

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

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

Для каждого просмотренного пользователем товара составлялись 4 вектора соответствующие категории, цвету металла, вставкам, и гендеру, представленным в виде унитарного кода.

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

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

Шаг 2: Формирование признаков сортировки каталога, наиболее полно отвечающих ожиданиям каждого пользователя.

Формирование матрицы «оценок» всех товаров каталога для пользователей из каждого отдельно взятого кластера происходит следующим образом: каждый товар, с которым совершалось какое-либо действие (просмотр, добавление в «список желаний», добавление в корзину, онлайн-резервирование, заказ и т.п.), получает балл, соответствующий самому «дорогому» из всех действий, произведенных с ним данным пользователем. Баллы за повторные просмотры суммируются до тех пор, пока не достигнут порогового значения.

После этого составляется матрица размером (количество кластеров × количество товаров), где каждой строке соответствует вектор усредненных оценок пользователей соответствующего кластера. И с помощью предиктивных алгоритмов на основе свойств товара присваиваются оценки товарам, с которыми конкретный пользователь не взаимодействовал.

Графически это можно представить следующим образом:

Шаг 3: Внедрение решения

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

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

После получения трех значимых событий в рамках одной сессии (например, просмотра 3х товаров) система присваивает пользователю некий кластер. В случае, если это уже известный нам пользователь, запрашиваются 10 последних действий пользователя за последний месяц, и мы сразу можем дать пользователю релевантные рекомендации.

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

У системы есть 2 типовых сценария работы на которых она показывает наибольшую эффективность.

Сценарий 1: Сортировка каталога на основе пользовательского интереса

  • Пользователь переходит в категорию «Кольца».
  • Пользователь ищет кольца с сапфирами из белого золота.
  • Пользователь находит кольцо с сапфиром, добавляет его в корзину, переходит в категорию «Серьги» и ищет серьги к данному кольцу.

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

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

При этом, поскольку сортировка присваивается для всех товаров, не опираясь на существующую структуру каталога при переходе в другую категорию (например, «Серьги») мы также получим релевантную сортировку:

У данной системы есть один минус: поскольку сортировка товаров во всем каталоге стала динамической, пользователю не удастся реализовать сценарий вроде «я помню на 6-й странице категории «Подвески» было-что интересное, хочу ещё раз на это посмотреть». Но наши тесты показали, что такие сценарии довольно редки, и невозможность их реализовать не оказала негативного влияния на конверсию.

Сценарий 2: Кастомизация главной страницы, и заглавных изображений категорий.

Поскольку мы создали глобальный рейтинг товаров под интересы пользователя, мы можем легко выделить как глобальный топ-товаров для пользователя, так и топ-товары любой категории (и вообще в любой выборке товаров по любому признаку).

Это создало интересный эффект: система сама начала собирать коллекции товаров для пользователей (причем как имеющие признаки коллекций, так и объединять схожие визуально изделия в свои собственные «коллекции»)

Вот так например стал выглядеть каталог по-умолчанию для одного из кластеров:

ActiveRecord немного про грабли, Relations и индексы

Хочу рассказать Вам о наболевшем: о работе с AR в целом и с Relation в частности; предостеречь от стандартных садовых изделий, которые легко могут испортить жизнь и сделать код медленным и прожорливым. Повествование будет основываться на Rails 3.2 и ActiveRecord того же разлива. В Rails 4, конечно же, много чего нового и полезного, но на него ещё перейти нужно, да и фундамент в любом случае один и тот же.

Читать:
Почему не работает камера на леново ноутбук

Сей материал, по большей части, предназначен для начинающих, ибо автору очень больно смотреть на извлечение содержимого целых таблиц в память в виде ActiveRecord объектов и на прочие отстрелы конечностей при использовании AR . Разработчикам, познавшим дзен, топик вряд ли принесёт пользу, они могут лишь Помочь, дополнив его своими примерами и назиданиями.

Уж сколько раз твердили миру…

Если вы начали работать с Relation (да и с любым ActiveRecord объектом вообще), то нужно чётко представлять одну вещь: в какой момент мы «овеществляем» выборку, то есть в какой момент мы перестаём конструировать SQL-запрос. Иначе говоря: когда происходит выборка данных и мы переходим к из обработке в памяти. Почему это важно? Да потому что неловкое:

Может повесить сервер, забрать всю оперативку и сделать ещё много пакостей. А то же самое, но иными словами:

отработает быстро и без последствий. Таки образом find и find — это совсем не одно и то же! Почему? Да потому что в первом случае мы сказали Product.all и выстрелили себе в ногу, так как это означает извлечь всё содержимое таблицы products и для каждой строки построить AR-объект, создать из них массив и уж по нему пройтись find, который является методом класса Array (вообще говоря, find из Enumerable, но это уже детали). Во втором случае всё гораздо лучше: find — это метод AR и предназначен для поиска по pk . То есть мы генерируем запрос

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

Что такое хорошо и что такое плохо

Теперь, разобравшись почему работа с AR — это большая ответственность, разберёмся с тем, как же не выстрелить себе в ногу. Сие довольно просто: надо пользоваться методами, которые предоставляет нам AR. Вот они: where, select, pluck, includes, joins, scoped, unscoped, find_each и ещё несколько, о которых можно узнать в документации или в соседнем хабе. А вот чем лучше не пользоваться перечислить будет очень сложно и, в то же время, очень просто: нежелательно пользоваться всем остальным, так как почти все оставшееся многообразие методов превращает Relation в Array со всеми вытекающими последствиями.

Простые рецепты

Теперь, приведу несколько стандартных и не очень конструкций, которые облегчают жизнь, но о которых очень часто забывают. Но перед задам вопрос читателю: вспомните функцию has_many. Подумайте, какие её параметры вы знаете и какими активно пользуетесь? Перечислите их в уме, посчитайте… а теперь вопрос: знаете ли вы сколько их на самом деле?

Зачем я это спросил? Да чтобы очень приблизительно оценить Ваш уровень и сказать, что ежели большую часть опций Вы знаете, то и нижеизложенное вряд ли принесёт Вам новые знания. Оценка эта очень условная, поэтому, уважаемый Читатель, не гневайся сильно, ежели она показалась Тебе нелепой/несостоятельной/странной/etc (нужное подчеркнуть).

Рецепт номер раз

Итак, теперь пойдём по-порядку. Про update_attributes и update_attribute знают все (или не все?). Первый — массово обновляет поля с вызовом валидаций и колбэков. Ничего интереного. Второй — пропускает все валидации, запускает колбэки, но может обновить значение только одного выбранного поля(кому-то больше по душе save(validate: false)). А вот про update_column и update_all почему-то часто забывают. Эти метод пропускают и валидации, и колбэки и пишут прямо в базу без всяких предварительных ласк.

Рецепт номер два

В комментариях напомнили про замечательный метод touch. О нём тоже частенько забывают и пишут что-то вроде

Хотя, по хорошему, для таких целей проще сделать вот так:

К тому же для touch есть собственный колбэк after_touch, а так же опция :touch присутствует у метода belongs_to.

Как правильно итерировать

В хабе уже говорили про find_each, но я не могу не упомянуть его ещё раз, ибо конструкции

и им изоморфные, встречаются чуть более чем везде. Проблема в обычных итераторах, применённых на Relation только одна: они вытаскивают из БД сразу всё. И это ужасно. В противоположность им find_each, по умолчанию, таскает по 1000 штук за раз и это просто прекрасно!

UPD: как уже отмечали в комментариях, все методы, которые однозначно не проецируются на raw-sql делегируются в to_a из-за чего и происходит выборка всего запроса в память и работа с ним уже не на стороне БД, а на стороне Ruby.

Совет про default_scope

Оборачивайте содержимое default_scope в блок. Пример:

В чём разница? В том, что первый вариант выполняется прямо при запуске сервера и если поля nullified в БД не оказалось, то и сервер не взлетит. То жесамое относится и к миграциям — они не пройдут из-за отсутствия поля, которое, скорее всего, мы как раз хотим добавить. Во втором случае, в силу того, что Ruby ленив, блок выполнится только в момент обращения к модели и миграции выполнятся штатно.

Has_many through

Ещё один часто встречающийся пациент это

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

и затем выполнить

Получается и нагляднее и эффективнее.

Немного про JOIN

В продолжение темы джоинов вспомним про includes. Что в нём особенного? Да то, что это LEFT JOIN. Довольно часто вижу, что левый/правый джоин пишут явно

это конечно тоже работает, но чистый SQL в RoR всегда был не в почёте.

Так же, не отходя от кассы, надо напомнить про разницу значений в joins и where при совместном использовании. Допустим у нас есть таблица users, а разные сущности, например products имеют поле author_id и реляцию author, кояя имеет под собой таблицу users.

Следующий код для такого случая работать не будет

Почему? Потому что в joins указывается имя реляции, которую джоиним, а в where накладывается условие на таблицу и надо говорить

Избежать такого можно явным указанием ‘AS author’ в джоине, но это снова будет чистый SQL.

Далее посмотрим на джоины с другого ракурса. Что бы мы не джоинили, в итоге мы получаем объекты класса, с которого всё начиналось:

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

Здесь мы получаем массив с нужным полем из БД. Плюсы: минимум запросов, не создаётся AR-объектов. Минусы: в Rails 3 pluck принимает только 1(один) параметр и вот такое

можно будет сделать только в Rails 4.

Build реляций

Теперь обратимся к рассмотрению работы с build-ом реляций. В общем случае всё довольно просто:

После вызова product.save у нас будет происходить сохранение всех ассоциаций вместе с валидациями, преферансом и куртизанками. Во всём этом радостном действе есть один нюанс: всё это хорошо, когда product не readonly и/или нет иных ограничений на сохранение. В таких случаях многие устраивают огород, аналогичный огороду с joins в примере выше. То есть создают document, привязывают его к product и build-ят строки для документа. Получается кривова-то и дефолтное поведение, которое, обычно, завязано на обработку ошибок product не работает. Поэтому в довесок всё это сразу же обставляют костылями, пробрасывающими ошибки и получается довольно мерзко. Что делать в таком случае? Надо вспомнить про autosave и понять как он работает. Не вдаваясь в детали скажу, что работает он на callback-ах. Поэтому способ сохранить реляции для вышеописанного продукта есть:

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

Несколько слов об индексах

На последок нужно сказать про индексы, ибо многие бились головой об твёрдые предметы из-за проблем на почве индексов. Сразу прошу прощения что мешаю в кучу ActiveRecord и возможности БД, но по личному убеждению: нельзя хорошо работать с AR, не осознавая что происходит в этот момент на стороне БД.

Проблема первая

Почему-то многие уверены что order на Relation не зависит от того, по какому столбцу мы сортируем. Разновидностью этого заблуждения является отсутствие понимания разницы между order Relation и order Array. Из-за этого можно встретить default_scope с ордером по VARCHAR полю и вопросы в духе: «А почему это у вас так медленно страница загружается? Там же всего пара записей извлекается из БД!». Проблема здесь в том, что дефолтная сортировка — это чертовски дорого, если у нас нет индекса на этом столбце. По умолчанию AR сортирует по pk . Это происходит когда мы делаем

Проблема вторая
  • polymorphic ассоциации
  • промежуточная таблица связей «много ко многим».

Вот несколько слов про разницу обычного и составного индекса. Далее в подробности вдаваться не буду, ибо тема для отдельного хаба. К тому же, до меня уже всё расписали.
Теперь про промежуточную таблицу связей. Всем известный HBTM . Здесь, в некоторых случаях, уместно повесить составной индекс на assemblies_parts (см. ссылку на HBTM ). Но надо помнить о том, что последовательность полей в составном индексе имеет знаение. Подробности тут.

Проблема третья

«Индексы нужны везде!». Встречается не так часто, но вызывает страшные тормоза всего и вся. Нужно помнить, что индекс — это не панацея и гарантированный х10-х100 к скорости, а инструмент, который нужно применять в правильных местах, а не махать им над головой и засовывать в каждую дырку. Вот тут можно почитать про типы индексов, а тут можно узнать зачем они вообще нужны.

Нормальная сортировка в настройках компонента

Задача — сделать разумные настройки для сортировки элементов инфоблока в компонентах с выборками. [spoiler]
Нормальные настройки сортировки обладают некоторыми особенностями. Во-первых, сортировать можно по нескольким полям в разных направлениях. Во-вторых, сортировать можно по свойствам элементов.

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

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

Алгоритм всего описанного выше простой: показываем пользователю пару параметров для сортировки "поле-направление". Если пользователь определяет эту пару, то подгружаем еще одну такую пару (без значения). Если он вторую пару указывает, то подгружаем еще и так до бесконечности, пока поля не кончатся. В итоге в параметрах компонента мы будем иметь несколько значений SORT_FIELD_Х, где Х — порядковый номер пары сортировки. Форма редактирования настроек компонента теперь будет выглядеть так:

Разобрать в компоненте набор таких параметров не составит труда. Вот пример моей разборки:

Что касается практического применения, то мне приходится достаточно часто устанавливать сортировку по 3-5 разным параметрам, например сортировка товаров в разделе: SORT, избранность, популярность, пользовательский рейтинг, ID.

Небольшое замечание. Имеет смысл предлагать сортировку только по свойствам типа текст (S), число (N), значение списка (L). По остальным типам свойств сортировка не имеет смысла, так как значением будут идентификаторами файлов, других элементов и еще не бог весть чего.

Похожие статьи