Создание Ruby on Rails с нуля, день 2

  • День 1: От ничего к чему-то
  • День 2: Больше вещей об отношениях, тестах и т. д.
  • День 3: Мои мысли о Ruby

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

Кроме того, будут созданы связанные с пользователем модульные тесты для отработки RSpec.

Итак, давайте приступим.

Создание отображения один-к-одному

Прежде всего, та же модель магазина создается с помощью scaffold, но разница в том, что в конце магазин должен быть назначен пользователю.

$ bin/rails generate scaffold имя магазина user:belongs_to

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

Она должна быть указана как has_one в app/models/user.rb, иначе она становится связкой один-ко-многим. Кроме того, Rails имеет врожденную проблему при работе с отношениями отображения, которая заключается в N + 1 запросах.

В данном примере N + 1 запросов не оказывает серьезного влияния, поскольку это отображение «один к одному», но если это отображение «один ко многим», то оно будет влиять на производительность. Решение также очень простое, и оно представлено в вышеупомянутом коммите.

Измените оригинальный Shop.all, созданный scaffold, на Shop.includes(:user). Тогда модель пользователя не будет искаться по одной записи за раз, а будет перечислена сразу.

Создание вложенной маршрутизации

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

Просто измените config/routes.rb, чтобы добавить ресурсы магазина под ресурсы пользователя, как показано в коммите на Github.

Настройка модульных тестов

В настоящее время самым популярным фреймворком тестирования на Rails является RSpec, поэтому я также сделал несколько упражнений с RSpec.

Установка очень проста, просто добавьте новую строку gem 'rspec-rails' в Gemfile, и рекомендуется добавить ее под группами development и test. Далее установите и настройте.

$ bundle install
$ bin/rails generate rspec:install

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

$ bin/rails generate rspec:scaffold user

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

$ rspec

Однако на системе M1 мы столкнемся с проблемами.

Rails bootstrap по умолчанию ставит selenium-webdriver, но selenium-webdriver скомпилирован для платформы x86, поэтому при запуске rspec возникает ошибка.

Мне не нужно было проводить сквозные тесты, поэтому я просто отключил неактуальные зависимости из Gemfile и rspec запустился правильно.

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

Начнем тестирование

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

Полный тест приведен по ссылке ниже.
https://github.com/wirelessr/Hello-RoR/commit/55fca06899d60256345c9d5ec5f14a2aa51a8b3f

Затем мы переходим к тестированию контроллера. К счастью, в rspec:scaffold уже настроен основной фреймворк, поэтому нам остается только заполнить детали.

Родной файл теста находится в spec/requests/users_spec.rb, и там есть несколько мест, где функция skip пропускает, и все, что нам нужно сделать, это следовать инструкциям в skip, чтобы заменить ее на легальную или нелегальную модель пользователя.

Коммит на Github прилагается.

Мои размышления о RSpec

На самом деле, синтаксис RSpec мне очень знаком благодаря моему опыту написания mocha поверх Node. Будь то describe или it или даже expect, все они похожи, поэтому я не столкнулся с какими-либо трудностями.

В процессе практики я обратился к документу о лучшей практике RSpec. Раньше я писал mocha очень просто, не обращаясь ни к каким правилам, так что этот документ также дает хороший совет. В будущем, будь то RSpec от Ruby или mocha от Node, они должны быть написаны более красивым образом.

Прямая ссылка на статью Better Specs.

Мои размышления о Ruby

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

Однако есть несколько специфических для Ruby поведений, изучение которых заняло у меня некоторое время.

Символ

В Ruby часто встречаются переменные, начинающиеся с двоеточия, например :abc, которая на самом деле ссылается на строку abc. Однако, в отличие от строки, эта строка неизменяема.

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

Эта концепция также встречается в Python, который размещает обычные слова и числа по фиксированным адресам памяти для ускорения обращений. В приведенном ниже примере Python адреса a и b одинаковы, и оба исходят из объекта 1, который был заранее определен.

>>> a = 1
>>> b = 1
>>> id(a)
5777693336
>>> id(b)
5777693336
Вход в полноэкранный режим Выход из полноэкранного режима

Круглые скобки можно опускать

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

  • При форматировании строки "#{abc} is good" трудно понять, относится ли abc к переменной или вызывается функция abc().
  • При вызове функции foo a=> 1, b => 2, c => 3 сколько аргументов имеет foo при такой записи? Ответ: мы не знаем, для этого нужно посмотреть сигнатуру foo.

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

Определение класса

Определение атрибута внутри объекта — это @, поэтому @abc эквивалентно питоновскому self.abc.

Но если мы определяем функцию с помощью self, это означает нечто совершенно иное, def self.bar(), что в терминах Python означает, что bar является classmethod.

Низкая производительность

В CPython производительность всего приложения ограничена реализацией GIL, и даже при многопоточном выполнении все операции все равно должны выполняться последовательно.

Это справедливо и для Ruby, который также имеет GIL и обычно использует pre-fork, чтобы полностью использовать ресурсы машины, а Gunicorn, используемый в Python, фактически является производным от Ruby’s Unicorn.

Послесловие

Хотя процесс практики Rails прошел гладко, я столкнулся с множеством трудностей при выполнении обзора кода, связанных как с особенностями Ruby, о которых я говорил в предыдущем разделе, так и с особенностями Rails.

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

Оцените статью
devanswers.ru
Добавить комментарий