242: Thor
Other formats:
Каждый разработчик на рельсах иногда сталкивается с Rake. Rake первоначально была написана в качестве альтернативы make, но в Rails в основном используется для выполнения небольших административных скриптов. Rake имеет некоторые ограничения для написания скриптов, например, чтобы передать аргументы в Rake, обычно мы вынуждены использовать переменные среды. Другая проблема заключается в том, что Rake задачи нельзя легко сделать глобальными, они всегда локальные и привязаны к текущему проекту. Такие инструменты, как Sake решают эту проблему, но тогда вам нужно использовать дополнительный инструмент.
В этом эпизоде будет рассмотрен Thor. Thor – это альтернатива Rake, которая не имеет ограничений упомянутых выше. Он подключается по зависимостям в Rails, то есть если у нас установлены рельсы – значит, у вас есть Thor. Поэтому если вам нужно написать генератор для рельсов – используйте Thor, а как его использовать я сейчас покажу.
Thor запускается из командной строки, и если мы запустим thor help, мы получим список команд, которые он поддерживает.
$ thor help Задачи: thor help [TASK] # Описание задач или конкретной задачи thor install NAME # Установить необязательного имени Thor файла в системные задачи thor installed # Список установленных модулей и задач Thor thor list [SEARCH] # Список установленных задач Thor thor uninstall NAME # Удаление именованного модуля Thor thor update NAME # Обновление файла Thor из своего первоначального места thor version # Версия Thor
У нас нет не одного собственного скрипта Thor, давайте его создадим.
Скрипт для копирования файлов конфигурации
Если уж мы собрались делать скрипт для Thor, давайте сделаем что-нибудь полезное.
В приложениях Rails считается нормальным не ставить некоторые файлы конфигурации
в систему контроля версий, так как они могут содержать важные данные, такие как пароли.
Мы создадим пример конфигурационного файла в подкаталоге examples,
затем создадим скрипт Thor , который будет копировать эти файлы в папку config.
Thor скрипт можно положить в папку lib/tasks, как скрипт Rake.
Итак, создадим новый файл с именем setup.thor.
Скрипт Thor – это класс, наследуемый от Thor, и название класса станет именем для его команды.
Каждый метод, описанный в desc, становится командой.
Методу desc нужно передать 2 параметра: имя команды и описание.
Начнем с написания простой команды config, которая выведет строку текста.
/lib/tasks/setup.thor
class Setup < Thor
desc "config", "copy configuration files"
def config
puts "running config"
end
end
Мы можем проверить команду, запустив thor setup:config.
Она вызывает метод config и мы видим нашу строчку в терминале.
$ thor setup:config running config
Для того чтобы посмотреть список доступных команд запустим thor list.
$ thor list setup ----- thor setup:config # copy configuration files
В выводе команды мы видим описание, которое мы только что написали.
Копирование файлов
Давайте сделаем так, чтобы команда config делала что-то полезное.
Код перебирает каждый файл из папки /config/examples и копирует их в папку /config,
пропуская уже существующие файлы.
/lib/tasks/setup.thor
class Setup < Thor
desc "config", "copy configuration files"
def config
Dir["config/examples/*"].each do |source|
destination = "config/#{File.basename(source)}"
if File.exist?(destination)
puts "Skipping #{destination} because it already exists"
else
puts "Generating #{destination}"
FileUtils.cp(source, destination)
end
end
end
end
Если мы снова запустим команду config, она скопирует файлы.
$ thor setup:config Generating config/database.yml Generating config/private.yml
Когда мы запустим команду снова, она уже не будет копировать существующие файлы в папке config
$ thor setup:config Skipping config/database.yml because it already exists Skipping config/private.yml because it already exists
Было бы полезным, если бы с опцией --force существующие файлы были перезаписаны.
Мы можем сделать это вызвав method_options перед определением метода, для того чтобы задать опцию.
/lib/tasks/setup.thor
class Setup < Thor
desc "config", "copy configuration files"
method_options :force => :boolean
def config
Dir["config/examples/*"].each do |source|
destination = "config/#{File.basename(source)}"
FileUtils.rm(destination) if options[:force]
if File.exist?(destination)
puts "Skipping #{destination} because it already exists"
else
puts "Generating #{destination}"
FileUtils.cp(source, destination)
end
end
end
end
Мы можем добавить несколько опций и присвоить им разные типы: строковый, числовой и так далее.
Чтобы получить значение данной опции мы вызываем options и в приведенном выше коде
мы используем options[:force], чтобы получить опцию :force и удалить файлы,
если параметр force был задан.
Если мы запустим файл с опцией --force все существующие файлы перезапишутся.
$ thor setup:config --force Generating config/database.yml Generating config/private.yml
Добавление параметров
Любые дополнительные параметры, которые мы передаем в thor, становятся методами.
Предположим мы хотим скопировать только файл private.yml.
$ thor setup:config private.yml
Этот параметр будет передан методы config.
Мы не хотим, чтобы всегда нужно было указывать имя файла, поэтому присвоим параметру значение по-умолчанию
"*", чтобы копировались все файлы.
Хорошо бы еще держать документацию в порядке, поэтому давайте изменим описание, чтобы в нем упоминался
параметр NAME.
/lib/tasks/setup.thor
class Setup < Thor
desc "config [NAME]", "copy configuration files"
method_options :force => :boolean
def config(name = "*")
Dir["config/examples/#{name}"].each do |source|
destination = "config/#{File.basename(source)}"
FileUtils.rm(destination) if options[:force]
if File.exist?(destination)
puts "Skipping #{destination} because it already exists"
else
puts "Generating #{destination}"
FileUtils.cp(source, destination)
end
end
end
end
Теперь запустим команду с параметром и посмотрим, как она работает.
$ thor setup:config private.yml
Skipping config/private.yml because it already exists
Работает. А теперь запустим с опцией --force
$ thor setup:config private.yml --force Generating config/private.yml
Установка глобальных команд
Наш скрипт стал полезным, и мы хотим его использовать в других Rails проектах.
Это делается легко: все что нужно - это запустить thor install <path_to_file>
и наш скрипт будет в системных командах Thor.
$ thor install lib/tasks/setup.thor
Your Thorfile contains:
class Setup < Thor
desc "config [NAME]", "copy configuration files"
method_options :force => :boolean
def config(name = "*")
Dir["config/examples/#{name}"].each do |source|
destination = "config/#{File.basename(source)}"
FileUtils.rm(destination) if options[:force]
if File.exist?(destination)
puts "Skipping #{destination} because it already exists"
else
puts "Generating #{destination}"
FileUtils.cp(source, destination)
end
end
end
end
Do you wish to continue [y/N]? y
Please specify a name for lib/tasks/setup.thor in the system repository [setup.thor]:
Storing thor file in your system repository
Однажды установив, мы можем запускать thor list в любой папке и увидим список команд.
$ cd ~ $ thor list setup ----- thor setup:config [NAME] # copy configuration files
Наша команда теперь доступна глобально и мы можем ее использовать в любых Rails приложениях.
Доступ к Rails приложению из Thor
Есть еще несколько вещей в Thor, которые мы покажем, и для их демонстрации мы создадим
новую команду в нашем классе Setup.
Эта команда будет создавать несколько записей в нашей базе данных, поэтому мы назовем ее populate (заселение).
Наше приложение - блог с моделью Article и populate создаст нам 10 записей.
/lib/tasks/setup.thor
class Setup < Thor
desc "config [NAME]", "copy configuration files"
method_options :force => :boolean
def config(name = "*")
# Тело метода Config пропущенно
end
desc "populate", "generate records"
def populate
10.times do |num|
puts "Generating article #{num}"
Article.create!(:name => "Article #{num}")
end
end
end
Если мы попробуем запустить команду сейчас, получим ошибку, что класс Article не найден, потому что команда
не может загрузить модели приложения.
Приложение Rails по-умолчанию не доступно для команд Thor, поэтому нам нужно загрузить приложение до того,
как мы попробуем создать Article.
К счастью это делается не сложно, все что нужно - это привязать файл config/environment.
/lib/tasks/setup.thor
class Setup < Thor
desc "config [NAME]", "copy configuration files"
method_options :force => :boolean
def config(name = "*")
# Тело метода Config пропущенно
end
desc "populate", "generate records"
def populate
require ‘./config/environment’
10.times do |num|
puts "Generating article #{num}"
Article.create!(:name => "Article #{num}")
end
end
end
Если мы запустим команду еще раз, она создаст 10 статей, после того как среда загрузится.
$ thor setup:populate Generating article 0 Generating article 1 Generating article 2 Generating article 3 Generating article 4 Generating article 5 Generating article 6 Generating article 7 Generating article 8 Generating article 9
Было бы хорошо, если бы мы могли сами задать количество статей, загружаемых в базу.
Мы можем сделать это, используя method_options, так же как мы это делали для метода config.
/lib/tasks/setup.thor
class Setup < Thor
desc "config [NAME]", "copy configuration files"
method_options :force => :boolean
def config(name = "*")
# Тело метода Config пропущенно
end
desc "populate", "generate records"
method_options :count => 10
def populate
require './config/environment'
options[:count].times do |num|
puts "Generating article #{num}"
Article.create!(:name => "Article #{num}")
end
end
end
На этот раз для указания типа мы задали значение по-умолчанию, и Thor сам определит тип по этому значению.
Теперь, если мы укажем количество 5, пять статей и будет создано.
$ thor setup:populate --count 5 Generating article 0 Generating article 1 Generating article 2 Generating article 3 Generating article 4
Для этого эпизода все. Если вы хотите получить больше информации о Thor, читайте документацию, это отличное место чтобы начать, особенно если вы хотите знать больше про передачу опций.
Когда использовать Thor, а когда Rake? Если вы делаете простое Rails приложение, тогда лучше использовать Rake, так как он является более популярным и многие его знают. А если вам нужно для своего Rails приложения сделать какие-то задачи связанные с администрированием, тогда можно рассмотреть и Thor.


