5 распространенных ошибок Rails-разработчиков

в 10:05, , рубрики: rails, ruby on rails, качество кода, ошибки, разработка

5 распространенных ошибок Rails разработчиков
Я проработал с Rails уже порядочное количество времени, и за это время я видел много Rails-приложений, а также не мало прочитал и написал плохого кода на Ruby. И вот вам 5 самых распространенных ошибок, которые я наблюдал практически в каждом приложении.

1. Миграции без ограничений

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

create_table "contacts" do |t|
  t.integer  "user_id"
  t.string   "name"
  t.string   "phone"
  t.string   "email"
end

Чего тут не хватает? Скорее всего Contact принадлежит (belong_to) пользователю (User) и у контакта должно быть как минимум имя — используйте ограничения базы данных для этого. Добавив ":null => false" мы всегда будем уверены в целостности модели, даже если в нашем коде валидации будут баги, потому что сама база данных не даст сохранить модель, нарушающую эти ограничения.

create_table "contacts" do |t|
  t.integer  "user_id", :null => false
  t.string   "name", :null => false
  t.string   "phone"
  t.string   "email"
end

Бонус-намек: используйте ":limit => N" для указания разумного размера ваших строковых полей. По умолчанию длина строки устанавливается в 255 символов, и наверное полю «phone» совсем ни к чему такой размер, логично?

2. Объектно-ориентированное программирование

Большинство Rails-разработчиков не пишут объектно-ориентированный Ruby-код. Они пишут MVC-ориентированный код, путем раскладывания моделей и контроллеров по нужным папкам. Некоторые добавляют вспомогательные модули с методами класса в папку «lib», но не более. Это продолжается 2-3 года, прежде чем разработчик осознает: «Rails — это просто Ruby. Я могу создавать простые объекты и компоновать их с фреймворком как угодно, а не как он это предписывает».

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

3. Конкатенация HTML в хелперах

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

str = "<li class='vehicle_list'> "
str += link_to("#{vehicle.title.upcase} Sale", show_all_styles_path(vehicle.id, vehicle.url_title))
str += " </li>"
str.html_safe

Жесть! Это уродливо и легко ведет к XSS уязвимостям. content_tag — ваш друг!

content_tag :li, :class => 'vehicle_list' do
  link_to("#{vehicle.title.upcase} Sale", show_all_styles_path(vehicle.id, vehicle.url_title))
end

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

4. Огромные запросы загружают все в память

Вам нужно что-то исправить в данных и вы просто проходите их все в цикле и исправляете, верно?

User.has_purchased(true).each do |customer|
  customer.grant_role(:customer)
end

Теперь представим, что у вас коммерческий сайт и миллион пользователей. Пускай каждый объект класса User занимает в памяти 500 байт. Приведенный ваше код отъест 500 Мб памяти. Вариант получше:

User.has_purchased(true).find_each do |customer|
  customer.grant_role(:customer)
end

find_each использует внутри себя метод find_in_batches, выбирая за раз 1000 записей и существенно снижая потребление памяти.

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

5. Обзор кода

Я предполагаю, что вы используете GitHub, а также я предполагаю, что вы не используете пул-реквесты. Если вы тратите день или два для реализации новой фичи, то делайте это в отдельной ветке и используйте пул-реквест. Ваша команда сможет проверить ваш код, подсказать что можно улучшить, а также указать вам на какие-то моменты, которые вы могли упустить из вида. Гарантирую, что это приведет к повышению качества вашего кода.

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


Бонус: полезный комментарий к оригинальной статьи

find_each и find_in_batches предпочтительны для больших выборок, однако нужно учитывать, что вы меняете память приложения на циклы процессора для базы данных.

Предположим, что вы делаете запрос, который вернет 1'000'000 записей.

SELECT * FROM users WHERE unindexed_field = true;

Если вы сделаете User.all.each, то база данных сделает один огромный запрос, просчитав весь результат за один раз. Если же вы сделаете User.where(...).find_each(:batch_size => 100), то базе данных придется сделать тот же самый запрос 1'000'000/100 = 10'000 раз. И если вы используете MySQL, то он будет пересчитывать результат каждый раз заново. Т.е. для записей 100-200 он просчитает первые 200 записей, для 200-300 — первые 300 записей, затем для 300-400 — первые 400 записей и так далее до 999900-1000000.

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

Общий «хак» для решения этой задачи такой:

ids = User.where(...).select(:id).all
ids.in_groups_of(100) do |id_group|
  # ...
end

Автор: Svyatov


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js