Статьи

Основы AntiPatterns: модели Rails

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

Если это довольно ново для вас, и вы видите себя как новичка во всех вещах Ruby / Rails, то это точно для вас. Я думаю, что будет лучше, если вы подумаете об этом, как о том, как быстро окунуться в гораздо более глубокую тему, мастерство которой не произойдет в одночасье. Тем не менее, я твердо верю, что начало такого раннего периода принесет огромную пользу новичкам и их наставникам.

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

Не стоит недооценивать последствия этих плохих решений — они будут преследовать вас, несмотря ни на что.

  • Жирные Модели
  • Отсутствует тестовый пакет
  • Вуайеристские модели
  • Закон Деметры
  • Спагетти SQL

Я уверен, что вы слышали песню «Толстые модели, тощие контроллеры» множество раз, когда вы впервые начали работать с Rails. Хорошо, теперь забудь об этом! Конечно, бизнес-логика должна решаться на уровне модели, но вы не должны чувствовать себя бессмысленно вбивать туда все, только чтобы не пересекать линии на территорию контроллера.

Вот новая цель, к которой вы должны стремиться: «Тощие модели, тощие контроллеры». Вы можете спросить: «Ну, как нам организовать код для достижения этой цели — в конце концов, это игра с нулевой суммой?» Хороший вопрос! Название игры — композиция, а Ruby хорошо оснащен, чтобы дать вам множество вариантов, чтобы избежать ожирения модели.

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

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

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

Мы можем сделать лучше! Если вы думаете, что сложность не имеет большого значения — в конце концов, вы особенный, умный и все такое — подумайте еще раз! Сложность — самый печально известный серийный убийца проектов, а не ваш дружелюбный район «Темный защитник».

«Модели Skinnier» достигают одной цели, которую продвинутые люди в кодовом бизнесе (вероятно, гораздо больше профессий, чем код и дизайн) ценят, и к чему мы все должны стремиться — к простоте! Или, по крайней мере, больше, что является справедливым компромиссом, если сложность трудно искоренить.

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

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

один человек группа

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

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

« `ruby class Spectre <ActiveRecord :: Base has_many: spectre_members has_many: spectre_agents has_many: враги_агенты has_many: операции

def turn_mi6_agent (враг_агент) ставит конец «агент МИ6 # {враг_агента.имя}, переданный Призраку»

def turn_cia_agent (врагов_агент) ставит конец «агент ЦРУ # {враг_агента.имя} передан призрак»

def turn_mossad_agent (врагов_агент) ставит конец «агент Моссада # {врага_агент.имя} передан призрак»

def kill_double_o_seven (spectre_agent) spectre_agent.kill_james_bond end

def dispose_of_cabinet_member (number) spectre_member = SpectreMember.find_by_id (number)

def print_assignment (operation) помещает «цель операции # {operation.name} в # {operation.objective}». end

частный

def врага_агент # конец кода

def spectre_agent # конец кода

операция def # конец кода

конец

« `

Призрак поворачивает различные виды вражеских агентов, делегаты убивают 007, жарит членов кабинета Spectre, когда они терпят неудачу, а также распечатывает оперативные задания. Очевидный случай микроуправления и однозначно нарушение «Принципа единой ответственности». Частные методы также быстро складываются.

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

« `ruby

Спектр класса <ActiveRecord :: Base has_many: spectre_members has_many: spectre_agents has_many: врагов_agents has_many: операции

def turn_enemy_agent Interrogator.new (врагов_агент). конец конца

частный

def враг_агент self.enemy_agents.last конец конец

Опросчик класса attr_reader: врага_агент

def initialize (врага_агента) @enemy_agent = врага_агента конец

def turn враг_агент.отчет конец конец

Класс EnemyAgent <ActiveRecord :: База принадлежит_ к: спектру принадлежит_ к: агентству

Def Turn ставит «После обширных промываний мозгов, пыток и накопления денег …» конец

class MI6Agent <EnemyAgent def turn super помещает конец «MI6 agent # {name}, переданный Spectre»

Класс CiaAgent <EnemyAgent def turn super помещает конец «агента CIA # {имя}, переданного Spectre», в конец

Класс MossadAgent <EnemyAgent def turn super помещает конец «агента Моссада # {имя} передано Призраку»

class NumberOne <ActiveRecord :: Base def dispose_of_cabinet_member (number) spectre_member = SpectreMember.find_by_id (number)

Операция класса <ActiveRecord :: Base has_many: spectre_agents own_to: specter

def print_assignment помещает «Операция # {имя} в цель # {цель}.» end end

Класс SpectreAgent <ActiveRecord :: База принадлежит_ к: операции принадлежит_ к: спектру

def kill_james_bond ставит «Мистер Бонд, я ожидаю, что ты умрешь!

Класс SpectreMember <ActiveRecord :: Base own_to: specter

def die ставит «Нееет, нееее, это не было меееееееее! ZCHUNK! »Конец конец

« `

Я думаю, что наиболее важная часть, на которую вы должны обратить внимание, — это то, как мы использовали простой класс Ruby, такой как Interrogator для обработки обращения агентов из разных агентств. Реальные примеры могут представлять собой конвертер, который, скажем, преобразует HTML-документ в PDF и наоборот. Если вам не нужны полные функциональные возможности классов Active Record, зачем их использовать, если простой класс Ruby также может помочь? Чуть меньше веревки, чтобы повеситься.

Класс Spectre оставляет неприятную задачу превращения агентов в класс Interrogator и просто делегирует его. Теперь этот человек несет единоличную ответственность за пытки и промывание мозгов захваченных агентов.

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

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

Поскольку все эти агенты являются объектами Active Record, мы создали универсальный EnemyAgent , который имеет общее представление о том, что означает превращение агента, и инкапсулируем этот бит для всех агентов в одном месте, создав его подклассы. Мы используем это наследство, предоставляя super turn методы turn различных агентов, и поэтому мы получаем доступ к бизнесу по «промыванию мозгов» и пыткам без дублирования. Отдельные обязанности и отсутствие дублирования являются хорошей отправной точкой для продвижения вперед.

Другие классы Active Record принимают на себя различные обязанности, о которых не нужно заботиться Spectre. «Номер один» обычно сам занимается приготовлением на гриле неисправных членов кабинета Spectre, так почему бы не позволить выделенному объекту справиться с поражением электрическим током? С другой стороны, проваленные члены Призрака знают, как умереть, когда их курили в кресле от NumberOne . Теперь Operation печатает и свои назначения — не нужно тратить время на Spectre с такими арахисами.

И последнее, но не менее важное: агент в поле обычно пытается убить Джеймса Бонда, поэтому kill_james_bond теперь является методом для SpectreAgent . Голдфингер, конечно же, поступил бы по-другому — поиграл бы с этим лазерным штурманом, если он у вас есть, наверное.

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

  • Разделили ли мы проблемы? Абсолютно!
  • У нас есть легкие, тощие классы, которые лучше подходят для выполнения особых обязанностей? Достаточно уверен!
  • Рассказываем ли мы «историю», рисуем ли мы более ясную картину того, кто участвует и отвечает за определенные действия? Я надеюсь, что это так!
  • Легче ли переварить то, что делает каждый класс? Наверняка!
  • Мы сократили количество частных методов? Ага!
  • Представляет ли это лучшее качество объектно-ориентированного программирования? Поскольку мы использовали композицию и ссылались на наследование только там, где это было необходимо для настройки этих объектов, вы держите пари!
  • Это чувствует себя чище? Да!
  • Мы лучше подготовлены к тому, чтобы изменить наш код, не создавая путаницы? Конечно, вещь!
  • Стоило ли? Как вы думаете?

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

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

Это, наверное, самый очевидный AntiPattern. Исходя из ориентированной на тестирование стороны, прикосновение к зрелому приложению, которое не имеет тестового покрытия, может быть одним из самых болезненных событий. Если вы хотите больше всего ненавидеть мир и свою собственную профессию, просто потратьте шесть месяцев на такой проект, и вы узнаете, какая часть мизантропа потенциально в вас. Шучу, конечно, но я сомневаюсь, что это сделает вас счастливее и что вы захотите сделать это снова — никогда. Может быть, неделю тоже подойдет. Я почти уверен, что слово «пытка» будет появляться в вашем уме чаще, чем вы думаете.

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

Я переоцениваю? Я действительно так не думаю! Вы хотите иметь очень обширное тестовое покрытие, не только потому, что это отличный инструмент проектирования, предназначенный только для написания кода, который вам действительно нужен, но и потому, что вам нужно будет изменить свой код в будущем. Вы будете намного лучше подготовлены к работе с вашей базой кода и будете намного увереннее, если у вас есть тестовая система, которая помогает и направляет рефакторинг, обслуживание и расширения. Они обязательно произойдут в будущем, ноль сомнений в этом нет.

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

Это очень любопытные модели, которые хотят собрать слишком много информации о других объектах или моделях. Это резко контрастирует с одной из самых фундаментальных идей объектно-ориентированного программирования — инкапсуляция. Мы скорее хотим стремиться к самодостаточным классам и моделям, которые максимально сами управляют своими внутренними делами. С точки зрения концепций программирования, эти вуайеристские модели в основном нарушают «Принцип Наименьшего Знания», то есть «Закон Деметры» — как бы вы его ни произносили.

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

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

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

Это обычный способ объяснить это, и все сводится к использованию не более одной точки для вызовов вашего метода. Кстати, вполне нормально использовать больше точек или вызовов методов, когда вы имеете дело с одним объектом, который не пытается достичь большего. Что-то вроде @weapons.find_by_name('Poison dart').formula просто в порядке. Искатели иногда могут накапливать несколько точек. Инкапсуляция их в специальные методы, тем не менее, является хорошей идеей.

Давайте посмотрим на пару плохих примеров из классов выше:

« `ruby

@ operation.spectre_agents.first.kill_james_bond

@ spectre.operations.last.spectre_agents.first.name

@ spectre.enemy_agents.last.agency.name

« `

Чтобы разобраться в этом, вот еще несколько вымышленных:

« `ruby

@ quartermaster.gizmos.non_lethal.favorite

@ mi6.operation.agent.favorite_weapon

@ mission.agent.name

« `

Бананы, верно? Не выглядит хорошо, не так ли? Как видите, эти вызовы методов слишком сильно заглядывают в бизнес других объектов. Самым важным и очевидным негативным последствием является повсеместное изменение множества вызовов этих методов, если структура этих объектов должна измениться, что в конечном итоге и произойдет, потому что единственная константа в разработке программного обеспечения — это изменение. Кроме того, это выглядит действительно противно, совсем не просто на глазах. Когда вы не знаете, что это проблематичный подход, Rails в любом случае позволяет вам зайти очень далеко — не крича на вас. Много веревки, помнишь?

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

« `ruby

класс SpectreMember <ActiveRecord :: Base has_many: операции has_many: spectre_agents

конец

Операция класса <ActiveRecord :: Base own_to: spectre_member

конец

Класс SpectreAgent <ActiveRecord :: Base own_to: spectre_member

конец

@ spectre_member.spectre_agents.all @ spectre_member.operations.last.print_assignment @ spectre_member.spectre_agents.find_by_id (1) .name

@ operation.spectre_member.name @ operation.spectre_member.number @ operation.spectre_member.spectre_agents.first.name

@ spectre_agent.spectre_member.number

« `

« `ruby

класс SpectreMember <ActiveRecord :: Base has_many: операции has_many: spectre_agents

def list_of_agents spectre_agents.all конец

def print_operation_details operation = Operation.last operation.print_operation_details end end

Операция класса <ActiveRecord :: Base own_to: spectre_member

def spectre_member_name spectre_member.name конец

def spectre_member_number spectre_member.number end

def print_operation_details ставит «Целью этой операции является # {цель}. Цель — # {target} ”end end

Класс SpectreAgent <ActiveRecord :: Base own_to: spectre_member

def superior_in_charge помещает конец «Мой босс номер # {spectre_member.number}»

@ spectre_member.list_of_agents @ spectre_member.print_operation_details

@ operation.spectre_member_name @ operation.spectre_member_number

@ spectre_agent.superior_in_charge

« `

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

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

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

Мы можем сделать лучше, чем это. Там, где это возможно, давайте делегировать вызовы методов непосредственно их ответственным объектам и постараемся максимально сократить методы-оболочки. Rails знает, что нам нужно, и предоставляет нам удобный метод класса delegate чтобы сообщить друзьям нашего объекта, какие методы нам нужны.

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

« `ruby

Операция класса <ActiveRecord :: Base own_to: spectre_member

делегат: имя,: номер, до:: spectre_member, префикс: true

def spectre_member_name

# spectre_member.name # end

def spectre_member_number

# spectre_member.number # end

конец

@ operation.spectre_member_name @ operation.spectre_member_number

Класс SpectreAgent <ActiveRecord :: Base own_to: spectre_member

делегат: номер, к:: spectre_member, префикс: true

def superior_in_charge устанавливает «Мой босс с номером # {spectre_member_number}» »

конец

« `

Как видите, мы могли бы немного упростить использование делегирования метода. Мы полностью избавились от Operation#spectre_member_name и Operation#spectre_member_number , и SpectreAgent больше не нужно вызывать number для spectre_membernumber делегируется обратно его классу-источнику « SpectreMember .

В случае, если это сначала немного сбивает с толку, как это работает? Вы говорите делегату, которому :method_name он должен делегировать to: which :class_name (несколько имен методов тоже :method_name ). prefix: true часть не обязателен.

В нашем случае, он предшествовал имени получающего класса в виде змеи перед именем метода и позволял нам вызывать operation.spectre_member_name вместо потенциально неоднозначной operation.name Name — если мы не использовали опцию префикса. Это очень хорошо работает с belongs_to и has_one .

На стороне has_many вещей, однако, музыка остановится, и вы столкнетесь с проблемами. Эти ассоциации предоставляют вам прокси-сервер коллекции, который будет выдавать вам NameErrors или NoMethodErrors, когда вы делегируете методы этим «коллекциям».

Чтобы завершить эту главу о модели AntiPatterns в Rails, я хотел бы потратить немного времени на то, чего следует избегать при использовании SQL. Ассоциации Active Record предоставляют варианты, которые значительно облегчают вашу жизнь, когда вы знаете, что вам следует избегать. Методы поиска — это отдельная тема, и мы не будем рассматривать их во всей их полноте, но я хотел бы упомянуть несколько общих методов, которые помогут вам, даже если вы пишете очень простые из них.

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

« `ruby

Операция класса <ActiveRecord :: Base

has_many: агенты

конец

Агент класса <ActiveRecord :: Base

принадлежат: операция

конец

Класс OperationsController <ApplicationController

def index @operation = Operation.find (params [: id]) @agents = Agent.where (operation_id: @ operation.id, licence_to_kill: true) end end

« `

Выглядит безобидно, нет? Мы просто ищем группу агентов, которые имеют лицензию на убийство для нашей страницы ops. Подумай еще раз. Зачем OperationsController копаться во внутренних органах Agent ? Кроме того, действительно ли это лучшее, что мы можем сделать для инкапсуляции поиска Agent ?

Если вы думаете, что можете добавить метод класса, такой как Agent.find_licence_to_kill_agents который инкапсулирует логику поиска, вы определенно делаете шаг в правильном направлении — хотя этого недостаточно.

« `ruby

Агент класса <ActiveRecord :: Base

принадлежат: операция

def self.find_licence_to_kill_agents (операция) где (operation_id: operation.id, licence_to_kill: true) конец…

конец

Класс OperationsController <ApplicationController

def index @operation = Operation.find (params [: id]) @agents = Agent.find_licence_to_kill_agents (@operation) end end

« `

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

« `ruby

Операция класса <ActiveRecord :: Base

has_many: агенты

def find_licence_to_kill_agents self.agents.where (licence_to_kill: true) конец…

конец

Класс OperationsController <ApplicationController

def index @operation = Operation.find (params [: id]) @agents = @ operation.find_licence_to_kill_agents end end

« `

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

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

« `ruby

Агент класса <ActiveRecord :: Base own_to: операция

область действия: licenced_to_kill, -> {где (licence_to_kill: true)} конец

Агент класса <ActiveRecord :: Base own_to: операция

def self.licenced_to_kill где (licence_to_kill: true) конец конец

Класс OperationsController <ApplicationController

def index @operation = Operation.find (params [: id]) @agents = @ operation.agents.licenced_to_kill end end

« `

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

Этот подход позволяет вам выполнять запросы как отдельные вызовы SQL. Мне лично нравится использовать scope для ее явности. Области также очень удобны для цепочки внутри хорошо известных методов поиска — таким образом, они увеличивают возможность повторного использования кода и DRY-кода. Допустим, у нас есть что-то более сложное

« `ruby

Агент класса <ActiveRecord :: Base own_to: операция

scope: licenced_to_kill, -> {где (licence_to_kill: true)} scope: womanizer, -> {where (womanizer: true)} scope: bond, -> {где (имя: ‘Джеймс Бонд’)} scope: игрок, — > {где (игрок: правда)} конец

« `

Теперь мы можем использовать все эти области для создания более сложных запросов.

« `ruby

Класс OperationsController <ApplicationController

def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.licenced_to_kill end

def show @operation = Operation.find (params [: id]) @bond = @ operation.agents.womanizer.gambler.licenced_to_kill end

… конец

« `

Конечно, это работает, но я хотел бы предложить вам пойти еще дальше.

« `ruby

Агент класса <ActiveRecord :: Base own_to: операция

scope: licenced_to_kill, -> {где (licence_to_kill: true)} scope: womanizer, -> {where (womanizer: true)} scope: bond, -> {где (имя: ‘Джеймс Бонд’)} scope: игрок, — > {где (игрок: правда)}

def self.find_licenced_to_kill licenced_to_kill end

def self.find_licenced_to_kill_womanizer womanizer.licenced_to_kill end

def self.find_gambling_womanizer gambler.womanizer end

конец

Класс OperationsController <ApplicationController

def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.find_licenced_to_kill end

def show @operation = Operation.find (params [: id]) @bond = @ operation.agents.find_licenced_to_kill_womanizer #or @bond = @ operation.agents.bond end

конец

« `

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

Если вы беспокоитесь о нарушении закона Деметры, вам будет приятно услышать, что, поскольку мы не добавляем точки, обращаясь к соответствующей модели, а связываем их только с собственным объектом, мы не совершаем никаких преступлений Деметры.

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

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

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