Advertisement
  1. Code
  2. Ruby on Rails
Code

Запросы в Rails, часть 3

by
Length:LongLanguages:

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

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

Если вы новичок в запросах Active Record и SQL, я рекомендую вам взглянуть на мои предыдущие две статьи, прежде чем продолжить. Так как можно без знания все этого чтение дальше может оказаться весьма трудным. С другой стороны, эта статья не будет такой же продолжительной, как и другие, если вы просто захотите взглянуть на эти несколько продвинутые варианты использования. Пойдем!

Темы

  • Скоупы и ассоциации
  • Слиммер джоины
  • Мерж
  • has_many
  • Кастомные джоины

Скоупы и ассоциации

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

Rails

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

Rails

Эти методы класса читаются одинаково, и вам не нужно беспокоиться на счет лямбды. Что бы ни было лучше для вас или вашей команды; Это зависит от того, какой API вы хотите использовать. Просто не смешивайте и не сравнивайте их с одним выбором! Обе версии позволяют легко связать эти методы внутри другого метода класса, например:

Rails

Rails

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

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

Rails

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

Немного слов о синтаксисе, поскольку мы отклонились от соглашений об именах и использовали нечто более выразительное, например double_o_agents. Мы должны упомянуть имя класса, чтобы не путать Rails, что в противном случае могло бы ожидать поиск класса DoubleOAgent. Разумеется, у вас могут быть как ассоциации Agent, так и обычные, а Rails не будет жаловаться по этому поводу.

Rails

Слиммер джоины

Когда вы запрашиваете базу данных для записей, и вам не нужны все данные, вы можете указать, что именно вы хотите получить. Зачем? Поскольку данные, возвращаемые в Active Record, в конечном итоге будут встроены в новые объекты Ruby. Давайте рассмотрим одну простую стратегию, чтобы избежать раздувания памяти в приложении Rails:

Rails

Rails

SQL

Таким образом, этот запрос возвращает список агентов с миссией из базы данных в Active Record, которая затем, в свою очередь, собирается создавать объекты Ruby из нее. Данные mission доступны, поскольку данные из этих строк объединяются в строки данных агента. Это означает, что объединенные данные доступны во время запроса, но не будут возвращены в Active Record. Таким образом, вы будете иметь эти данные например для выполнения вычислений.

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

Rails

Просто кратко о синтаксисе здесь: потому что мы не запрашиваем таблицу Agent через where, а через джоин в таблице :mission, нам нужно указать в нашем WHERE, что мы ищем конкретные missions.

SQL

Использование includes здесь также приведет к возврату миссий в Active Record для активной загрузки и захвату памяти объеками Ruby.

Слияние

merge пригодится, например, когда мы хотим совместить запрос с агентами и связанными с ними миссиями, которые имеют определенную область действия, определенную вами. Мы можем взять два объекта ActiveRecord::Relation и объединить их условия. Слияние полезно, если вы хотите использовать определенный скоуп при использовании ассоциации.

Другими словами, то, что мы можем сделать с merge, - это фильтр с именованным скоупом на объединенной модели. В одном из предыдущих примеров мы использовали методы класса для определения таких именных скоупов. 

Rails

Rails

SQL

Когда мы инкапсулируем, что dangerous миссия находится в модели Mission, мы таким образом можем спрятать ее на join посредством merge. Поэтому перемещение логики таких условий к соответствующей модели, которой она принадлежит, с одной стороны - хорошая техника для достижения более слабой связанности - мы не хотим, чтобы наши модели Active Record знали много деталей друг о друге, а с другой это дает вам хороший API в ваших объединениях. Пример ниже без слияния не будет работать без ошибок:

Rails

SQL

Когда мы теперь объединяем объект ActiveRecord::Relation для наших миссий на наших агентов, база данных не знает, о каких миссиях мы говорим. Нам нужно четко указать, какая связь нам нужна, и сначала присоединиться к данным миссии. Одна последняя вишенка на торте. Мы можем инкапсулировать это еще лучше, привлекая агентов:

Rails

Rails

SQL

И что мы имеем в итоге. Инкапсуляция, правильное ООП и отличная читаемость. Джек-пот!

has_many

Выше мы видели уже много раз видели ассоциацию belongs_to в действии. Давайте взглянем на это с другой точки зрения и включим секреты секретной службы в микс:

Rails

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

Rails

SQL

Вы что-то заметили? Небольшая деталь отличается. Внешние ключи перевернуты. Здесь мы запрашиваем список разделов, но используем внешние ключи следующим образом: "agents"."section_id" = "sections."id". Другими словами, мы ищем внешний ключ из таблицы, в которую мы делаем джоин.

Rails

SQL

Раньше наши соединения через ассоциацию belongs_to выглядели так: внешние ключи были отзеркалены ("missions"."id" = "agents"."mission_id") и искали внешний ключ из таблицы, с которой мы начинаем.

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

Rails

SQL

Проверьте две части INNER JOIN. Вы все еще со мной? Мы «добираемся» через агентов к их миссиям из раздела агента. Мы получаем миссии, которые косвенно связаны с определенным разделом.

В результате мы включаем новые столбцы, но количество строк остается таким же. То, что отправляется обратно в Active Record - приводит к созданию новых объектов Ruby, также остается списком разделов. Поэтому, когда у нас есть несколько миссий с несколькими агентами, мы снова получим дублированные строки для нашего раздела. Давайте отфильтруем это еще немного:

Rails

SQL

Теперь мы получаем только возвращаемые части, которые участвуют в миссиях, в которых вовлечен враг Эрнст Ставро Блофельд. Поскольку некоторые супер-злодеи могут работать более чем в одном разделе - например, в разделах A и C, United Sates и Canada, соответственно.

Если у нас есть несколько агентов в данном разделе, которые работают над одной и той же миссией, чтобы остановить Blofeld или что-то еще, у нас снова будут повторяющиеся строки, возвращенные нам в Active Record. Давайте будем немного более разборчивыми:

Rails

SQL

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

Рельсы

Вы можете реорганизовать это еще больше и разделить обязанности по достижению свободной связи, но давайте двигаться дальше.

Пользовательские соединения

Большую часть времени вы можете положиться на Active Record, чтобы он писал нужный SQL для вас. Это означает, что вы остаетесь на земле Ruby, и вам не нужно слишком беспокоиться о деталях базы данных. Но иногда вам нужно прорыть дыру в землю SQL и делать свое дело. Например, если вам нужно использовать LEFT-соединение и выйти из обычного поведения Active Record при выполнении INNER-соединения по умолчанию. joins - небольшое окно для написания собственного пользовательского SQL, если это необходимо. Вы открываете его, подключаете свой собственный код запроса, закрываете «окно» и можете продолжать добавлять методы запросов Active Record.

Давайте продемонстрируем это с примером, который включает в себя гаджеты. Предположим, что типичный агент обычно имеет гаджеты через has_many, и мы хотим найти агентов, которые не снабжены какими-либо причудливыми гаджетами, чтобы помочь им в этой области. Обычное соединение не дало бы хороших результатов, так как мы действительно заинтересованы в nil или null в SQL-значениях этих шпионских игрушек.

Рельсы

Когда мы выполняем операцию join, мы получим только  агентов, которые уже оснащены гаджетами, потому что agent_id в этих гаджетах не равен нулю. Это ожидаемое поведение внутреннего соединения по умолчанию. Внутреннее соединение основывается на совпадении с обеих сторон и возвращает только строки данных, которые соответствуют этому условию. Необязательный гаджет с nil значением для агента, который не имеет гаджета, не соответствует этому критерию.

Рельсы

SQL

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

Rails

SQL

Неплохо, но, как вы можете видеть из вывода SQL, это не работает и продолжает настаивать на использовании INNER JOIN по умолчанию. Это сценарий, в котором нам нужно соединение OUTER, потому что одна сторона нашего так сказать «уравнения» отсутствует. Мы ищем результаты для гаджетов, которых не существует, точнее, для агентов без гаджетов.

До сих пор, когда мы передавали символ в Active Record в join, он ожидал ассоциации. С другой стороны, если передается строка, ожидается, что она будет фактическим фрагментом кода SQL - частью вашего запроса.

Rails

SQL

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

Rails

SQL

Внешнее соединение является более инклюзивной версией соединения, поскольку оно будет соответствовать всем записям из объединенных таблиц, даже если некоторые из этих отношений еще не существуют. Поскольку этот подход не так широк, как внутренние соединения, вы получите кучу nil. Конечно, это может быть информативным в некоторых случаях, но внутренние объединения, тем не менее, обычно являются тем, что мы ищем. Rails 5 позволяет использовать для таких случаев специализированный метод, называемый left_outer_joins. Наконецто!

Одна мелочь: держите эти заглядывающие дыры в землю SQL как можно меньше, если сможете. И все, включая вы сами в будущем, будут вам за это благодарны.

Заключение

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

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

Теперь выходите в сеть и найдите еще несколько материалов по SQL, чтобы хорошенько его освоить, раз и навсегда!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.