Я проработал с 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 принадлежит (belongs_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