Налаштування Guard для автоматизації Ruby on Rails розробки

Всім привіт! На мою думку, кожен програміст повинен прагнути до автоматизації та оптимізації всього, що рухається і ще немає. У цій статті буде розказано про те, як автоматизувати робочий процес 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 increasing the 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 running and if 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'
# Guard With
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'
# Guard With
gem 'guard-rubocop'
end

group :development do
# Automagically launches tests for files changed
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 model changed
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 change js
# https://github.com/guard/guard-jasmine
guard :jasmine, all_on_start: false, server_mount: '/specs' do
watch(%r{^app/(.+)\.(js\.coffee|js|coffee)}) { "spec/javascript" }
watch(%r{^spec/javascript/(.+)\.(js\.coffee|js|coffee)}) { "spec/javascript" }
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, тому буду радий будь-яким зауважень і пропозицій.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.