- День 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 в следующей статье, которая должна стать последней в этой серии.