Pull to refresh

Настройка Guard для автоматизации Ruby on Rails разработки

Reading time 7 min
Views 17K
Всем привет! По моему мнению, каждый программист должен стремиться к автоматизации и оптимизации всего, что движется и еще нет. В этой статье будет рассказано о том, как автоматизировать рабочий процесс Ruby on Rails разработчика с помощью Ruby гема под названием Guard. Эта статья в первую очередь полезна Ruby разработчикам, но может пригодиться и другим.

image

Что такое 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

image

Настройка для 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


image
Rubocop — гем для Ruby, позволяющий проверить .rb файл на корректность синтаксиса. В данном примере он настроен вместе с Guard, благодаря чему при каждом изменении .rb файла Rubocop проверяет его и выводит результат в консоль и в log/rubocop.log файл.

У Rubocop огромное количество настроек, благодаря чему его можно адаптировать под любые требования к синтаксису. Можно даже сделать так, чтобы он автоматически корректировал код. Для настройки гема используется файл .rubocop.yml, например, rubocop обычно ругается на строки больше 90 символов, но благодаря файлу настроек можно сделать так, чтобы он указывал только на строки больше 140.

Чтобы увидеть все настройки, достаточно прогнать команду

$ rubocop --auto-gen-config

которая создаст файл со всеми отключенными настройками. Можно таким образом по одной включать и получить итоговый нужный .rubocop.yml файл.

Результаты


Что в итоге настроено? В данном проекте достаточно запустить отдельными процессами zeus и guard. После чего происходит следующее:
  1. Автоматически поддерживается запущенный через zeus Rails сервер, который перезапускается при каждом изменении основных файлов настроек проекта
  2. При каждом изменении Gemfile устанавливаются все гемы
  3. При изменении любого файла с тестом прогоняется этот тест
  4. При изменении любого файла контроллеров/моделей/либов/вьюх запускается связанный с ним тест, если такой имеется
  5. Каждый измененный ruby файл проверяется на грамотность с помощью rubocop
  6. При изменении любого javascript/coffeescript файла запускаются все jasmine тесты
  7. При изменении любого файла миграции или seeds прогоняются все необходимые миграции

image

Таким образом, достаточно большое количество процессов удалось автоматизировать. Я бы хотел сделать так, чтобы у каждого проекта достаточно было бы лишь запустить guard и полностью сфокусироваться на творческом процессе.

Пример работы с Guard


Теперь опишу текущий процесс работы с Guard. Ниже идут рекомендации, которые я дал остальным разработчикам, с которыми я сейчас работаю.
  1. Откройте терминал и перейдите в папку проекта
  2. Запустите zeus для ускоренной работы тестов/сервера
    $ zeus start
    
  3. Запустите Guard
    $ bundle exec guard
    

    Теперь Guard автоматически запустит и будет поддерживать включенным Rails server, включенный через Zeus.
  4. Запустите все тесты, нажав в терминале Enter. После исправления всех тестов можно работать!

На что стоит обращать внимание: при изменении файлов тестов тесты будут прогоняться автоматически. То есть я рекомендую одновременно с окном IDE держать открытым окно терминала (в Rubymine, например, это можно сделать прямо в под окне), где тут же можно будет увидеть, обвалились ли тесты с внесенными изменениями.

image

Спасибо!


Спасибо за чтение! Не утверждаю, что я специалист в Guard, поэтому буду рад любым замечаниям и предложениям.
Only registered users can participate in poll. Log in, please.
Автоматизируете?
16.56% Каждый день, каждый процесс! 25
9.93% Несколько раз в неделю, периодически 15
43.71% Только когда приспичит, в редких случаях 66
8.61% Бросил! 13
21.19% И не начинал, доктор! 32
151 users voted. 60 users abstained.
Tags:
Hubs:
+26
Comments 13
Comments Comments 13

Articles