Всем привет! По моему мнению, каждый программист должен стремиться к автоматизации и оптимизации всего, что движется и еще нет. В этой статье будет рассказано о том, как автоматизировать рабочий процесс Ruby on Rails разработчика с помощью Ruby гема под названием Guard. Эта статья в первую очередь полезна Ruby разработчикам, но может пригодиться и другим.
Что такое Guard?
Guard – это инструмент, позволяющий автоматически выполнять какие-либо команды при изменении какого-либо файла. Например, при изменении файла настроек сервера Guard может автоматически перезапускать сервер. Или можно настроить автоматическую компиляцию LESS в CSS при сохранении файла. Всё зависит от того, как Guard будет настроен разработчиком.
У Guard есть специальный файл настроек Guardfile, где указывается, какие команды нужно запускать при изменении каких файлов. Все настройки можно указать самому, а можно использовать написанные сообществом Guard Plugins, в которых самые часто используемые настройки написаны заранее.
Установка и первый запуск
Лучший способ интегрировать Guard в проект – это добавить его в Gemfile.
group :development do
gem 'guard'
end
И затем установить его командой
$ bundle
После чего необходимо создать Guardfile командой
$ bundle exec guard init
Запустить Guard лучше всего используя Bundler командой
$ bundle exec guard
Настройка для Ruby on Rails приложения
После установки рассмотрим использование Guard для стандартного RoR проекта. Предположим, что RoR приложение уже создано. Пусть Guard будет автоматически устанавливать все необходимые гемы при изменении Gemfile.
1. Добавление в проект
Для этого в Gemfile добавим в группу для разработки гем guard-bundler
group :development do
# And updates gems when needed
gem 'guard-bundler', require: false
end
Установим гем
$ bundle install
А затем инициализируем плагин командой
$ guard init bundler
Обратите внимание на Guardfile, расположенный в корне проекта. Теперь там есть строчки
guard :bundler do
watch('Gemfile')
end
В них написано, что Guard будет следить за файлом Gemfile и будет выполнять команду, заранее записанную в геме guard-bundler. В данном случае, это
$ bundle install
2. Проверка
Проверим! Включим Guard в терминале командой
$ bundle exec guard
Добавим в Gemfile какой-нибудь гем. Например, guard-rspec, который будет автоматом прогонять тесты для Rspec.
gem 'guard-rspec', require: false
Откроем терминал с процессом guard и увидим, что он там автоматически запустил bundler, в результате работы которого guard-rspec был автоматически установлен. Как видно, подобная настройка Guard позволяет разработчику автоматизировать одну из часто выполняемых задач.
3. Настройка
Инициализируем плагин для Rspec после его установки
$ guard init rspec
Теперь в Guardfile появились новые строчки. Рассмотрим их:
# Note: The cmd option is now required due to the increasing number of ways
# rspec may be run, below are examples of the most common uses.
# * bundler: 'bundle exec rspec'
# * bundler binstubs: 'bin/rspec'
# * spring: 'bin/rsspec' (This will use spring if running and you have
# installed the spring binstubs per the docs)
# * zeus: 'zeus rspec' (requires the server to be started separetly)
# * 'just' rspec: 'rspec'
guard :rspec, cmd: 'bundle exec rspec' do
watch(%r{^spec/.+_spec.rb$})
watch(%r{^lib/(.+).rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }
# Rails example
watch(%r{^app/(.+).rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.*)(.erb|.haml|.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
watch(%r{^app/controllers/(.+)_(controller).rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
watch(%r{^spec/support/(.+).rb$}) { "spec" }
watch('config/routes.rb') { "spec/routing" }
watch('app/controllers/application_controller.rb') { "spec/controllers" }
watch('spec/rails_helper.rb') { "spec" }
# Capybara features specs
watch(%r{^app/views/(.+)/.*.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
# Turnip features and steps
watch(%r{^spec/acceptance/(.+).feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
end
Эти строчки настраивают автоматический запуск тестов Rspec. Рассмотрим некоторые из них. Например, строка
ruby watch('spec/spec_helper.rb') { "spec” }
говорит о том, что Guard будет следить за файлом spec/spec_helper.rb (путь относительно корня проекта — Guardfile файла) и при любом его изменении он будет запускать тестирование всей папки spec. Начало блока
ruby guard :rspec, cmd: 'bundle exec rspec’ do
говорит о том, что для любого правила все Rspec команды будут запускаться с параметрами bundle exec rspec. То есть, в рассмотренном случае при изменении ruby spec/spec_helper.rb будет запускаться команда
$ bundle exec rspec spec
Строка
rubywatch(%r{^app/(.+).rb$}) { |m| "spec/#{m[1]}_spec.rb" }
говорит о том, что при изменении любого .rb файла будет запускаться тестирование теста, связанного с этим файлом. То есть при изменении app/models/user.rb автоматически запустится команда
$ bundle exec spec spec/models/user_spec.rb
Для создания и редактирования подобных действий используются регулярные выражения. Рекомендую использовать в Ruby консоли команду match для отладки, например
"app/views/units/index.html.slim".match(%r{^app/views/(.+)/(.*).(.*).(erb|haml|slim)$})
Больше примеров!
Для Guard написано большое количество плагинов на все случаи жизни. Каждому разработчику стоит самостоятельно найти нужные для него и настроить их под себя. Я вкратце опишу те, которые используются у меня в данный момент. Я сам до сих пор не нашел идеальных решений, поэтому буду рад любым замечаниям и предложениям!
В Gemfile
group :development, :test do
# Integrates jasmine js testing
gem 'jasmine-rails'
# With guard
gem 'guard-jasmine', git: "git://github.com/guard/guard-jasmine.git", branch: "jasmine-2"
# Checks ruby code grammar
gem 'rubocop', require: false
# With rspec
gem 'rubocop-rspec'
# With guard
gem 'guard-rubocop’
end
group :development do
# Automagically launches tests for changed files
gem 'guard'
gem 'guard-rspec', require: false
# And updates gems when needed
gem 'guard-bundler', require: false
# And auto starts rails server
gem 'guard-rails'
# And auto runs migrations
gem 'guard-migrate'
end
В Guardfile
# More info at https://github.com/guard/guard#readme
# https://github.com/guard/guard-bundler
guard :bundler do
watch('Gemfile')
end
# https://github.com/guard/guard-rspec
guard :rspec, cmd: 'zeus rspec' do
watch(%r{^spec/.+_spec.rb$})
watch(%r{^lib/(.+).rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
# Run the model specs related to the changed model
watch(%r{^app/(.+).rb$}) { |m| "spec/#{m[1]}_spec.rb" }
# Controller changes
watch(%r{^app/controllers/(.+)_(controller).rb$}) { |m| ["spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
watch('config/routes.rb') { "spec/controllers" }
watch('app/controllers/application_controller.rb') { "spec/controllers" }
watch(%r{^spec/support/(.+).rb$}) { "spec" }
watch('spec/rails_helper.rb') { "spec" }
watch('spec/spec_helper.rb') { "spec" }
# Capybara features specs
watch(%r{^app/views/(.+)/.*.(erb|haml|slim)$}) { |m| "spec/acceptance/#{m[1]}" }
watch(%r{^app/views/(.+)/(.*).(.*).(erb|haml|slim)$}) { |m| "spec/acceptance/#{m[1]}" }
watch(%r{^app/views/(.+)/_.*.(erb|haml|slim)$}) { |m| "spec/acceptance/#{m[1].partition('/').first}/#{m[1].partition('/').last}_spec.rb" }
end
# Checks any changed ruby file for code grammar
# https://github.com/yujinakayama/guard-rubocop
guard :rubocop, all_on_start: false, cli: ['--out', 'log/rubocop.log'] do
watch(%r{^(.+).rb$}) { |m| "#{m[1]}.rb" }
end
# Restarts server on config changes
# https://github.com/ranmocy/guard-rails
guard :rails, zeus: true, daemon: true do
watch('Gemfile.lock')
watch(%r{^(config|lib)/.*})
end
# Restarts all jasmine tests on any js change
# https://github.com/guard/guard-jasmine
guard :jasmine, all_on_start: false, server_mount: '/specs' do
watch(%r{^app/(.+).(js.coffee|js|coffee)}) { "spec/javascripts" }
watch(%r{^spec/javascripts/(.+).(js.coffee|js|coffee)}) { "spec/javascripts" }
end
# Runs migrations on migrate files changes
# https://github.com/glanotte/guard-migrate
guard :migrate do
watch(%r{^db/migrate/(d+).+.rb})
watch('db/seeds.rb')
end
Немного о rubocop
Rubocop — гем для Ruby, позволяющий проверить .rb файл на корректность синтаксиса. В данном примере он настроен вместе с Guard, благодаря чему при каждом изменении .rb файла Rubocop проверяет его и выводит результат в консоль и в log/rubocop.log файл.
У Rubocop огромное количество настроек, благодаря чему его можно адаптировать под любые требования к синтаксису. Можно даже сделать так, чтобы он автоматически корректировал код. Для настройки гема используется файл .rubocop.yml, например, rubocop обычно ругается на строки больше 90 символов, но благодаря файлу настроек можно сделать так, чтобы он указывал только на строки больше 140.
Чтобы увидеть все настройки, достаточно прогнать команду
$ rubocop --auto-gen-config
которая создаст файл со всеми отключенными настройками. Можно таким образом по одной включать и получить итоговый нужный .rubocop.yml файл.
Результаты
Что в итоге настроено? В данном проекте достаточно запустить отдельными процессами zeus и guard. После чего происходит следующее:
- Автоматически поддерживается запущенный через zeus Rails сервер, который перезапускается при каждом изменении основных файлов настроек проекта
- При каждом изменении Gemfile устанавливаются все гемы
- При изменении любого файла с тестом прогоняется этот тест
- При изменении любого файла контроллеров/моделей/либов/вьюх запускается связанный с ним тест, если такой имеется
- Каждый измененный ruby файл проверяется на грамотность с помощью rubocop
- При изменении любого javascript/coffeescript файла запускаются все jasmine тесты
- При изменении любого файла миграции или seeds прогоняются все необходимые миграции
Таким образом, достаточно большое количество процессов удалось автоматизировать. Я бы хотел сделать так, чтобы у каждого проекта достаточно было бы лишь запустить guard и полностью сфокусироваться на творческом процессе.
Пример работы с Guard
Теперь опишу текущий процесс работы с Guard. Ниже идут рекомендации, которые я дал остальным разработчикам, с которыми я сейчас работаю.
- Откройте терминал и перейдите в папку проекта
- Запустите zeus для ускоренной работы тестов/сервера
$ zeus start
- Запустите Guard
$ bundle exec guard
Теперь Guard автоматически запустит и будет поддерживать включенным Rails server, включенный через Zeus.
- Запустите все тесты, нажав в терминале Enter. После исправления всех тестов можно работать!
На что стоит обращать внимание: при изменении файлов тестов тесты будут прогоняться автоматически. То есть я рекомендую одновременно с окном IDE держать открытым окно терминала (в Rubymine, например, это можно сделать прямо в под окне), где тут же можно будет увидеть, обвалились ли тесты с внесенными изменениями.
Спасибо!
Спасибо за чтение! Не утверждаю, что я специалист в Guard, поэтому буду рад любым замечаниям и предложениям.
Автор: cbrwizard