Руководство по Rake (перевод)
Оригинал расположен здесь. Не забудте туда заглянуть, тем есть забавные картинки и фотографии авторов блога.
Автор статьи - Gregg Pollack.
Перевод получился скорее вольно-художественный, нежели дословный.
Вставки от переводчика, то бишь от меня по тексту выделены курсивом.
Собственно дальше пошел уже сам перевод:
Как Rails-разработчик, вы наверняка использовали “rake” при тестировании или запускали “rake db:migrate” для выполнения миграций. Но понимаете ли вы, что происходит внутри этих Rake tasks? Вы в курсе, что можно написать свои задания или даже создать библиотеку полезных Rake-файлов?
Вот несколько примеров того, для чего я использую Rake tasks:
- Создание списка пользователей для почтовой рассылки
- Ночные вычисления и формирование отчетов
- Сброс и перегенерация закэшированных страниц
- Резервное копирование базы данных и репозитория
- Выполнение каких угодно скриптов для манипуляции данными
- Разлив выпивки по стаканам
Содержание
- Почему make? Экскурс в историю.
- Откуда у нас появился Rake?
- Как Rake работает?
- Как использовать зависимости в Rake?
- Как документируются Rake-задания?
- Пространства имен в Rake
- Как написать свои задания на Ruby?
- Как написать написать свои Rake-задания для моего Rails-приложения?
- Могу я получить доступ к своим Rails-моделям из Rake-задания?
- Где еще можно почитать про Rake?
Почему make? Экскурс в историю.
Прежде чем понять, откуда взялся Rake, давайте сначала вспомним про его прадедушку Make.
Давайте мысленно перенесемся в то время, когда каждый кусок кода должен был компилироваться, когда интерпретируемые языки и айФоны еще не покорили Землю.
В то время вы получали программу в виде кучи исходного кода и shell-скрипта. Этот скрипт содержал весь неодходимый код, необходимый вашему компьютеру, чтобы собрать приложение. Вы запускали “install_me.sh” (этот самый shell-скрипт), выполнялась строчка за строчкой (обычно каждая строка – это компиляция одного исходного файла), и в итоге вы получали исполняемый файл, который уже можно было запустить.
Впринципе это подходило большинству людей, за исключением тех, кого угораздило эту программу разрабатывать. Каждый раз даже после незначительного изменения в коде, если вы хотели его проверить, вы должны были снова запускать этот скрипт и перекомпилировать все заново. Очевидно, это могло занимать много времени для больших программ.
В 1977 (в этом году я родился) Stuart Feldman из Bell Labs создал “Make”, которые решал проблему долгой перекомпиляции. Make тоже использовался для компиляции програм, но с двумя существенными отличиями:
- Make распознавал какие исходные файлы изменились с момента последней компиляции. Используя эту информацию, при повторной компиляции Make компилировал только изменившиеся исходные файлы. Это дало огромный прирост в скорости перекомпиляции больших програм.
- Кроме этого Make содержал в себе отслеживание зависимостей, так что вы могли сказать компилятору, что чтобы нормально скомпилироваться, исходному файу А нужен исходный файл Б, а файлу Б требуется файл В. Таким образом, если Make-у нужно скомпилировать файл А и файл Б еще не скомпилирован, Б компилировался в первую очередь.
Следует также объяснить, что Make – это просто исполняемый файл, такой же как “dir” или “ls”. Для того чтобы объяснить Make-у, как скомпилировать программу,нужно создать “makefile”, который содержал бы все инструкции и зависимости для компиляции “исходников”. У makefile-ов есть свой хитровывернутый синтаксис, который нам здесь знать не обязательно.
Через некоторое время Make эволюционировал и им стали пользоваться во всяких разных языках программирования. Действительно, много Ruby-программистов пользовались им, пока ему на смену не пришел Rake.
“Эээ, но ведь в Ruby ничего не надо компилировать, зачем бы это могло понадобиться Ruby-программистам?”, спросите вы.
Да, Ruby – это интерпретируемый язык, и нам не надо компилировать наш код, так почему программисты на Ruby использовали Make?
Ну, по двум большим причинам:
- Создание заданий – В каждом большом приложении почти всегда возникает необходимость в скриптах, которые можно было бы запускать из командной строки. Вместо создания десяти отдельных скриптов (или одного универсального), вы можете создать один “Makefile”, в котором все можно разделить на задания. После этого их можно выполнять, просто набирая в командной строке “make stupid”, где stupid – это название вашего задания.
- Отслеживание зависимостей между заданиями – Когда вы начинаете писать библиотеку для повседневных задач, то сталкиваетесь с тем, что некоторые задачи частично повторяют друг друга. Например, оба задания “migrate” и “schema:dump” требуют соединения с базой данных. Я мог бы создать задание “connect_to_database”, и установить, что “migrate” и “schema:dump” зависят от него. Таким образом, в следующий раз, когда я запущу “migrate”, перед ним автоматически запустится “connect_to_database”
Откуда у нас появился Rake?
Несколько лет назад Jim Weirich работал над Java-проектом, в котором он использовал Make. Во время написания Makefile он подумал, что было бы удобно писать небольшие куски кода на Ruby прямо внутри мэйкфайлов. Ну и короче он написал rake.
Тут я малость опустил про Джима Вейриха, кому интересно загляните в оригинал, там и фотка есть (Jim Weirich в полосатой футболке :)
Ну и как rake уже в конце концов работает?
Допустим, мы хотим напиться, какие шаги нам нужно предпринять?
- Купить водки (purchaseAlchohol)
- Замешать коктейль (mixDrink)
- Нажратсо (getSmashed)
Если бы я захотел использовать Rake для вызова таких задач, я бы создал Rake-файл с примерно таким содержимым:
task :purchaseAlchohol do
puts "Купил водки"
end
task :mixDrink do
puts "Замешал коктейль 'Мохнатый пупок'"
end
task :getSmashed do
puts "Ччувак, чота ты какой-то размытый..И-и-ик, накатим еще по стопарю?"
end
После этого я могу исполнять эти задания(находясь в той же директории), что-то типа такого:
$ rake purchaseAlchohol
Купил водки
$ rake mixDrink
Замешал коктейль 'Мохнатый пупок'
$ rake getSmashed
Ччувак, чота ты какой-то размытый.. И-и-ик, накатим еще по стопарю?
Круто, да? Тем не менее с точки зрения зависимостей, я могу выполнять эти задания в любом порядке. Но вообще говоря, если бы я пожелал “нажратсо” перед “замешать коктейль” или “купить водки”, то обычно это находится за пределами человеческих возможностей (это не про русских, да – прим. переводчика)
Итак, как мне использовать зависимости в Rake?
task :purchaseAlchohol do
puts "Купил водки"
end
task :mixDrink => :purchaseAlchohol do
puts "Замешал коктейль 'Мохнатый пупок'"
end
task :getSmashed => :mixDrink do
puts "Ччувак, чота ты какой-то размытый.. И-и-ик, накатим еще по стопарю?"
end
Итак, теперь я говорю, что если я хочу “замешать коктейль”, то надо бы сначала “купить водки”, а если хочу “нажратсо”, то сначала надо “замешать коктейль”. Как вы уже наверное догадались, в итоге получаем что-то такое:
$ rake purchaseAlchohol
Купил водки
$ rake mixDrink
Купил водки
Замешал коктейль 'Мохнатый пупок'
$ rake getSmashed
Купил водки
Замешал коктейль 'Мохнатый пупок'
Ччувак, чота ты какой-то размытый.. И-и-ик, накатим еще по стопарю?
Как видно, теперь при попытке “Нажратсо”, вызываются зависимые задания “купить водки” и “замешать коктейль”.
Через какое-то время вам возможно захочется усугубить ваши пристрастия и расширить ваш Rakefile. И даже может быть захочется привлечь к этому своих друзей. Сделаем вид, что мы как будто бы в настоещем софтверном проекте, соответственно если мы хотим добавить людей в команду, то надо убедиться что все хорошо задокументировано. Итак, закономерный следующий пункт:
Как документируются Rake-задания?
Собственно в Rake с этим все очень просто. Вызываем desc и вперед:
desc "Это задание купит нам водки"
task :purchaseAlchohol do
puts "Купил водки"
end
desc "Это задание замешает нам отличный коктейль"
task :mixDrink => :purchaseAlchohol do
puts "Замешал коктейль 'Мохнатый пупок'"
end
desc "Это задание позволит нам как следует нажраться"
task :getSmashed => :mixDrink do
puts "Ччувак, чота ты какой-то размытый.. И-и-ик, накатим еще по стопарю?"
end
Ну вот, теперь у наших заданий есть описания. Теперь я или мои друзья могут вызвать команду "rake -T" или "rake --tasks"
$rake --tasks
rake getSmashed # Это задание позволит нам как следует нажраться
rake mixDrink # Это задание замешает нам отличный коктейль
rake purchaseAlcohol # Это задание купит нам водки
Ну несложно, да?
Пространства имен в Rake
Ну раз уж мы окончательно пристрастились к алкоголю, мы теперь используем множество разных Rake-заданий, и нам не помешало бы их категоризировать. Для этого используем пространства имен. Для нашего примера это будет выглядеть так:
namespace :alcoholic do
desc "Это задание купит нам водки"
task :purchaseAlchohol do
puts "Купил водки"
end
desc "Это задание замешает нам отличный коктейль"
task :mixDrink => :purchaseAlchohol do
puts "Замешал коктейль 'Мохнатый пупок'"
end
desc "Это задание позволит нам как следует нажраться"
task :getSmashed => :mixDrink do
puts "Ччувак, чота ты какой-то размытый.. И-и-ик, накатим еще по стопарю?"
end
end
Пространства имен позволяют нам группировать задания по категориям и, О ДА, может быть более одного пространство имен в Rakefile-е. Если теперь запустить "rake --tasks", то вот что мы увидим:
$rake --tasks
rake alcoholic:getSmashed # Это задание позволит нам как следует нажраться
rake alcoholic:mixDrink # Это задание замешает нам отличный коктейль
rake alcoholic:purchaseAlcohol # Это задание купит нам водки
Ну и чтобы вызвать наши задания, очевидно нужно выполнить команду “rake alcoholic:getSmashed”.
Как мне написать свои задания на Ruby?
Ну собственно берем и пишем на Ruby. Без шуток. Недавно мне понадобился скрипт, который создавал несколько дерикторий, в общем законченное задание выглядит так:
desc "Create blank directories if they don't already exist"
task(:create_directories) do
# The folders I need to create
shared_folders = ["icons","images","groups"]
for folder in shared_folders
# Check to see if it exists
if File.exists?(folder)
puts "#{folder} exists"
else
puts "#{folder} doesn't exist so we're creating"
Dir.mkdir "#{folder}"
end
end
end
По умолчанию, rake имеет доступ ко всему, что есть в File Utils, но ничто не мешает вам подключить все что душе угодно, лишь бы оно было на Ruby.
Как мне написать написать свои Rake-задания для моего Rails-приложения?
В Rails уже имеется некоторое количество готовых rake заданий, которые можно просмотреть с помощью "rake --tasks". Если вы еще этого не сделали, тогда сделайте, я подожду…
Чтобы создать новые задания для вашего рельсового приложения, вам надо открыть директорию /lib/tasks (что вы уже должны были сделать). Если в этой директории вы создадите свой Rakefile “something.rake”, соответствующие задания будут автоматически подгружены. Они будут добавлены в список существующих заданий и вы можете запускать их обычным способом. Вот так будет выглядеть предыдущий пример в Rails-окружении:
namespace :utils do
desc "Create blank directories if they don't already exist"
task(:create_directories) do
# The folders I need to create
shared_folders = ["icons","images","groups"]
for folder in shared_folders
# Check to see if it exists
if File.exists?("#{RAILS_ROOT}/public/#{folder}")
puts "#{RAILS_ROOT}/public/#{folder} exists"
else
puts "#{RAILS_ROOT}/public/#{folder} doesn't exist so we're creating"
Dir.mkdir "#{RAILS_ROOT}/public/#{folder}"
end
end
end
end
Заметьте, что здесь мы можем использовать #{RAILS_ROOT}, чтобы получить полный путь. После добавления такого rake-файла, "rake --tasks" выдаст нам следующее:
...
rake tmp:pids:clear # Clears all files in tmp/pids
rake tmp:sessions:clear # Clears all files in tmp/sessions
rake tmp:sockets:clear # Clears all files in tmp/sockets
rake utils:create_directories # Create blank directories if they don't already exist
...
Замечательно, теперь можно рассмотреть пример, где это просто суперполезно..
Могу я получить доступ к своим Rails-моделям из Rake-задания?
Всенепременно! Собственно, для этого по большей части я и использую Rake: Пишу задания, которые необходимо вызывать периодически вручную или автоматически по крону. Как я уже сказал в начале статьи, я использую Rake для следующих задач:
- Создание списка пользователей для почтовой рассылки
- Ночные вычисления и формирование отчетов
- Сброс и перегенерация закэшированных страниц
- Резервное копирование базы данных и репозитория
- Выполнение каких угодно скриптов для манипуляции данными
Обалденно удобно, и легко. Вот rake задание, которое находит пользователей, у которых истекает срок подписки и рассылает им уведомления:
require File.expand_path(File.dirname(__FILE__) + "/../../config/environment")
namespace :utils do
desc "Finds soon to expire subscriptions and emails users"
task(:send_expire_soon_emails => :environment) do
# Find users to email
for user in User.members_soon_to_expire
puts "Emailing #{user.name}"
UserNotifier.deliver_expire_soon_notification(user)
end
end
end
Как видно, доступ к нашим моделям производится с помощью "require …" и "=> :environment":
require File.expand_path(File.dirname(FILE) + ”/../../config/environment”)task(:send_expire_soon_emails => :environment) do
Запускаем задание на development-базе даных: “rake utils:send_expire_soon_emails”
На “боевой” базе данных: “rake RAILS_ENV=production utils:send_expire_soon_emails”
Чтобы задание выполнялось по ночам, можно добавить в cron следующее:
0 0 * * * cd /var/www/apps/rails_app/ && /usr/local/bin/rake RAILS_ENV=production utils:send_expire_soon_emails
Вот!
Где еще можно почитать про Rake?
Теперь, когда вы уже знаете достаточно, чтобы начать писать свои суперполезные rake-задания, можно дать вам несколько ссылок по теме. Лучший способ улучшить свои программистские навыки – это чтение чужого кода, несколько следующих ссылок – это как раз наборы готовых rake tasks.
- Новые rake tasks в Edge Rails для создания и сброса БД
- Craig Ambrose написал Rake task для резервного копирования базы данных.
- Adam Greene собрал в кучу несколько Rake tasks, которые позволяют вам бэкапить ваши данные на Amazon S3
- Jay Fields рассказал про использование rake tasks для тестирования
- Err the blog написал про новый способ установки RAILS_ENV и про то, как можно загрузить Mysql-консоль из rake (не забудьте почитать комментарии).
- И наконец, Rake Bookshelf Books и руководство Мартина Фаулера Using the Rake Build Language. Обе ссылки дают довольно исчерпывающее описание Rake, но они уже немного устарели.
В догонку
Тут вот товарищ Джим прислал письмо с объяснением, как можно упростить мой скрипт для создания директорий:
# This is needed because the existing version of directory in Rake
is slightly broken, but Jim says it'll be fixed in the next version.
alias :original_directory :directory
def directory(dir)
original_directory dir
Rake::Task[dir]
end
# Do the directory creation
namespace :utils do
task :create_directories => [
directory('public/icons'),
directory('public/images'),
directory('public/groups'),
]
end