Ключевые слова:ruby, rails, web, (найти похожие документы)
From: Немытченко И.В. <inem@bk.ru.>
Newsgroups: email
Date: Mon, 11 Jun 2007 14:31:37 +0000 (UTC)
Subject: Руководство по Rake (Ruby make) для Rails-разработчиков - перевод
Оригинал расположен здесь
Не забудте туда заглянуть, тем есть забавные картинки и фотографии авторов блога.
Автор статьи - Gregg Pollack.
Перевод также опубликован здесь: http://rubyroid.org/blog/rake-tutorial/
Как Rails-разработчик, вы наверняка использовали "rake"
при тестировании или запускали "rake db:migrate" для выполнения
миграций. Но понимаете ли вы, что происходит внутри этих Rake
tasks? Вы в курсе, что можно написать свои задания или даже
создать библиотеку полезных Rake-файлов?
Вот несколько примеров того, для чего я использую Rake tasks:
* Создание списка пользователей для почтовой рассылки
* Ночные вычисления и формирование отчетов
* Сброс и перегенерация закэшированных страниц
* Резервное копирование базы данных и репозитория
* Выполнение каких угодно скриптов для манипуляции данными
* Разлив выпивки по стаканам
Почему make? Экскурс в историю.
Прежде чем понять, откуда взялся Rake, давайте сначала вспомним про его прадедушку Make.
Давайте мысленно перенесемся в то время, когда каждый кусок кода должен
был компилироваться, когда интерпретируемые языки и айФоны еще не
покорили Землю.
В то время вы получали программу в виде кучи исходного кода и
shell-скрипта. Этот скрипт содержал весь неодходимый код, необходимый
вашему компьютеру, чтобы собрать приложение. Вы запускали
"install_me.sh" (этот самый shell-скрипт), выполнялась строчка за
строчкой (обычно каждая строка - это компиляция одного исходного файла),
и в итоге вы получали исполняемый файл, который уже можно было
запустить.
Впринципе это подходило большинству людей, за исключением тех, кого угораздило
эту программу разрабатывать. Каждый раз даже после незначительного изменения в
коде, если вы хотели его проверить, вы должны были снова запускать этот скрипт
и перекомпилировать все заново. Очевидно, это могло занимать много времени для
больших програм.
В 1977 (в этом году я родился) Stuart Feldman из Bell Labs создал "Make", которые
решал проблему долгой перекомпиляции. Make тоже использовался для компиляции
программ, но с двумя существенными отличиями:
1. Make распознавал какие исходные файлы изменились с момента
последней компиляции. Используя эту информацию, при повторной
компиляции Make компилировал только изменившиеся исходные файлы.
Это дало огромный прирост в скорости перекомпиляции больших
програм.
2. Кроме этого Make содержал в себе отслеживание зависимостей, так
что вы могли сказать компилятору, что чтобы нормально
скомпилироваться, исходному файу А нужен исходный файл Б, а файлу
Б требуется файл В. Таким образом, если Make-у нужно
скомпилировать файл А и файл Б еще не скомпилирован, Б
компилировался в первую очередь.
Следует также объяснить, что Make - это просто исполняемый файл, такой
же как "dir" или "ls". Для того чтобы объяснить Make-у, как
скомпилировать программу,нужно создать "makefile", который содержал бы
все инструкции и зависимости для компиляции "исходников". У
makefile-ов есть свой хитровывернутый синтаксис, который нам здесь
знать не обязательно. Через некоторое время Make эволюционировал и им
стали пользоваться во всяких разных языках программирования.
Действительно, много Ruby-программистов пользовались им, пока ему на
смену не пришел Rake. , спросите вы. Да, Ruby - это интерпретируемый
язык, и нам не надо компилировать наш код, так почему программисты на
Ruby использовали Make? Ну, по двум большим причинам:
1. Создание заданий - В каждом большом приложении почти всегда
возникает необходимость в скриптах, которые можно было бы
запускать из командной строки. Вместо создания десяти отдельных
скриптов (или одного универсального), вы можете создать один
"Makefile", в котором все можно разделить на задания. После этого
их можно выполнять, просто набирая в командной строке "make
stupid", где stupid - это название вашего задания.
2. Отслеживание зависимостей между заданиями - Когда вы начинаете
писать библиотеку для повседневных задач, то сталкиваетесь с тем,
что некоторые задачи частично повторяют друг друга. Например, оба
задания "migrate" и "schema:dump" требуют соединения с базой
данных. Я мог бы создать задание "connect_to_database", и
установить, что "migrate" и "schema:dump" зависят от него. Таким
образом, в следующий раз, когда я запущу "migrate", перед ним
автоматически запустится "connect_to_database"
Откуда у нас появился Rake?
Несколько лет назад Jim Weirich работал над Java-проектом, в
котором он использовал Make. Во время написания Makefile он подумал,
что было бы удобно писать небольшие куски кода на Ruby прямо внутри
мэйкфайлов. Ну и короче он написал rake. Тут я малость опустил про
Джима Вейриха, кому интересно загляните в оригинал, там и фотка
есть (Jim Weirich в полосатой футболке :)
Ну и как rake уже в конце концов работает?
Допустим, мы хотим напиться, какие шаги нам нужно предпринять?
1. Купить водки (purchaseAlchohol)
2. Замешать коктейль (mixDrink)
3. Нажратсо (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
Purchased Vodka
$ 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 # Это задание купит нам водки
Ну и чтобы вызвать наши задания, очевидно нужно выполнить команду .
Как мне написать свои задания на 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 alrea
dy 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":
1. require File.expand_path(File.dirname(FILE) +
"/../../config/environment")
2. task(:send_expire_soon_emails => :environment) do
Запускаем задание на development-базе даных: На "боевой" базе данных:
Чтобы задание выполнялось по ночам, можно дабавить в 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