Статьи

Грабли: автоматизировать все

Современный привет технологий фон шаблона в оранжевом

Пару лет назад я получил электронное письмо от друга, в котором говорилось, что он изучает Rails, и он решил, что я должен попробовать его. Хотя у меня был хороший опыт программирования, мои навыки веб-разработки были ужасными. Я немного знал HTML и CSS, но если бы вы спросили меня, что общего между POST и PUT, я бы сказал, что они оба начинаются с буквы P и заканчиваются буквой T.

Автор предположил, что освоение Rails требует овладения всеми аспектами веб-разработки, но идея изучения всего с нуля меня не взволновала. Несколько дней спустя я наткнулся на некоторый синтаксис Ruby в приложении Pastebin онлайн. У меня не было никакого опыта работы с Ruby, но его синтаксис и стиль были настолько элегантными и выразительными, что я легко мог понять, что автор пытается сделать. Я начал играть с книгами по Ruby и сразу влюбился в язык программирования. Это история моего романа с Руби.

Я знаю, что вам интересно: «Какое это имеет отношение к Rake и Rails? И почему я стилизовал слово «царапина»?

Честно говоря, я ненавижу начинать все с нуля. Я не знаю, только это я или всемирное явление. Когда вы делаете что-то в первый раз, это называется приключением. Если вы решите сделать это снова, все скажут, что это добавляет вам опыта. Но на третьем этапе получается скучно.

Когда вы войдете в мир Ruby on Rails как разработчик, фрилансер или любитель, вы сразу же почувствуете волнение и волнение от начала и запуска.

rails new webapp 

Но прежде чем вы начнете зубрить разработку потрясающих дизайнов и концепций вашего нового веб-приложения, есть ряд необходимых, хотя и кажущихся тривиальными, деталей, которые необходимо рассмотреть. Вещи как:

  • Очистка Gemfile
  • Инициализация git-репозитория
  • Настройка драгоценных камней для ваших инструментов, таких как Bootstrap
  • Настройка охраны + искра и т. Д.

Эти шаги, конечно, будут полезны для вас при разработке проекта, но они кажутся вам серьезным препятствием для начала работы. Разбираясь с этими мелочами, я теряю все силы и волнения и заставляю меня бежать за чашкой кофе.

Вот где приходит Rake. Как вы уже догадались по его названию, Rake — это приложение, созданное в духе Make, написанное на Ruby и разработанное Jim Weirich. Rake — это утилита управления задачами, которая может делать все, от автоматического удаления истории браузера до создания искусственного интеллекта. Я просто шучу; Грабли не могут этого сделать, но это мощно. Разработанный для сборки исполняемых программ из исходных файлов, он может сделать гораздо больше!

Первоначально Джим не был убежден, что Rake будет полезен, но сообщество Ruby с радостью доказало его неправоту, включив его в стандартную библиотеку Ruby.

Хорошо, позвольте мне с самого начала заявить, что я никогда не собирался писать этот код. Я не уверен, что это полезно, и я не уверен, что кому-то это даже будет интересно. Все, что я могу сказать, это то, что луковый грузовик, должно быть, проходил через долину Огайо. О чем я говорю? … Руби версия Make.

–Джим Вейрих

Начало работы с Rake

Rake — это специфичный для встраиваемого домена язык (EDSL), потому что за пределами Ruby он не существует. Термин EDSL предполагает, что Rake — это предметно-ориентированный язык, встроенный в другой язык (Ruby), который имеет большую область применения. Rake расширяет Ruby, поэтому вы можете использовать все функции и расширения, которые поставляются с Ruby. Вы можете воспользоваться Rake, используя его для автоматизации некоторых задач, которые постоянно бросают вам вызов. Из-за этого вы обнаружите, что новизна его использования не будет быстро стираться, и вы скоро станете намного более продуктивным.

Звучит интересно? Давайте начнем, не так ли?

Основы

Вот несколько рейковых заданий, которые вы, вероятно, видели раньше:

 $ rake db:migrate $ rake db:test:prepare 

Запуск rake из командной строки включает вызов rake указанием имени задачи. По мере того как количество задач увеличивается, их обслуживание и создание новых становится довольно утомительной задачей. Rake позволяет вам организовать ваши задачи с использованием пространств имен, позволяя создавать несколько задач с одинаковыми именами, назначенными для разных пространств имен, и избегать коллизий и неоднозначности имен.

Например, вы можете создать две задачи с именем cleanup.

  1. main: cleanup — Эта задача очищает каталог вашего веб-приложения, удаляя файлы мертвой древесины и ненужные куски кода.
  2. temp: cleanup — стирает все файлы и папки, перечисленные в каталоге temp.
    Вот базовая структура граблей. image1

Это состоит из

  • Блок пространства имен
  • Краткое описание о задаче
  • Название задачи (обратите внимание, что я использовал символ, а не строку)
  • Блок do..end
  • Код, который должна выполнить задача

Каждый фрагмент кода между task :cleanup do..end блок task :cleanup do..end выполняется при вызове

 rake main:cleanup 

Если вы когда-нибудь решите добавить другую задачу в main пространство имен, она должна быть заключена во внешний блок do..end . Вы можете добавить любое количество задач в пространство имен.

Зависимость против вызова

Важным компонентом любого инструмента сборки является поиск зависимостей (или предварительных условий) и их устранение. Задача может иметь несколько предпосылок, которые должны быть выполнены до выполнения основной задачи.

Rakefile

 task :default => :third_task task :first_task do puts "First task" end task :second_task do puts ">>Second task" end task :third_task => [:first_task, :second_task] do puts ">>>>Hurray. This is the third task" puts " This task depends on the first_task and second_task and won't be executed unless both the dependencies are satisfied." print "The syntax for declaring dependency is " puts " :main_task => :dependency" puts "and if there are more than 1 dependency, place all the dependencies inside the [] separated by commas " puts ":main_task => [:dep1, :dep2]" end 

Если вам интересно, что там делает :default , это символ Ruby, который имеет особое значение для Rake. Запуск rake без каких-либо параметров из терминала будет по умолчанию выполнять third_task , потому что мы объявили его с символом :default .

 $ rake 

Наличие предварительных условий полезно, но разве не было бы здорово, если бы мы могли сделать это обычным, простым способом, таким как вызов задачи из другой задачи? Вы можете достичь этого, не покидая идиомы Рейка.

 namespace :main do task :test do if true puts "Calling test2 task." Rake::Task["main:test2"].invoke #invokes main:test2 else abort() end end task :test2 do puts ">Test2 task invoked" end end 

Как показано выше в качестве примера, вы даже можете поместить вызов задачи в условный оператор, и в этом случае он будет выполнен, только если условие выполнится.

Параметризованные задачи

Что если мы хотим, скажем, передать параметры в нашу задачу Rake? Звук невозможно? На самом деле, нет.

 task :tests, [:arg1, :arg2] do |t, args| puts "First argument: #{ args[:arg1] }" puts "Second argument: #{args[:arg2]}" end 

И вывод:

 $ rake tests[1,some_random_string] First argument: 1 Second argument: some_random_string 

Это зло, не правда ли? Параметры идут внутри [] через запятую. Требуется 2 переменные блока:

  • t является объектом, связанным с этой задачей
  • args это хеш, который хранит ваши аргументы

Создайте тестовый каталог для примера.

 mkdir testapp && cd testapp 

Мы создадим пустой файл данных и файл Rakefile внутри testapp и посмотрим, на что способен Rake.

 $ ls data.dat Rakefile 

Вот Рейкфайл

 namespace :setup do desc "A test task to check whether a directory exists" task :check do puts "Enter the name of the destination directory: " @dir = STDIN.gets.strip #Calling gets by itself would result in a call to "Kernel#gets" which is not what we want. if File.directory?("../#{@dir}") #Checks whether the user requested directory exists and if not creates a new one. puts "The directory exists" setup_copy #Calls setup_copy method else puts "Creating the requested directory..." mkdir "../#{@dir}" setup_copy end end desc "A test task to copy things around" task :copy => :check do #A task dependency puts "Copying files..." cp_r '.', "../#{@dir}" puts "Done.! :)" end end def setup_copy Rake::Task["setup:copy"].invoke #A task invocation end 

Для мгновенного удовлетворения попробуйте запустить

 rake setup:copy 

из каталога testapp . Рейк выполняет, как сказано.

  1. Когда setup:copy , Rake пытается выполнить setup:check зависимостей setup:check .
  2. setup:check фиксирует имя каталога назначения через переменную экземпляра. Использование локальной переменной в этом случае будет означать, что ее область действия будет ограничена этой конкретной задачей. Однако, сделав его доступным для последующих дочерних задач, мы сэкономили бы много кода и времени.
  3. Блок if-else проверяет, существует ли каталог, создает его в случае сбоя условия и setup_copy функцию setup_copy . Это идет, чтобы продемонстрировать жалобу Рэйка с Руби.
  4. Элемент управления возвращается к setup:copy . Метод cp_r копирует все файлы, находящиеся в текущем каталоге, и помещает их в папку, запрошенную пользователем. (‘R’ в cp_r заботится о рекурсивном копировании файлов, чтобы ничего не cp_r .)

Примечание: я не уверен, заметили ли вы, но нам не потребовались какие-либо стандартные библиотечные файлы или загрузка каких-либо расширений, чтобы сделать cp_r и mkdir доступными в нашем Rakefile. Это потому, что библиотека «fileutils» по умолчанию загружается с Rakefile. Я очень рекомендую просмотреть документы ruby ​​на fileutils . Вы определенно найдете это полезным на каком-то этапе обучения.

Разве вызов задачи и зависимость не приведут к конфликту интересов? Я думал, вы могли бы спросить что-то в этом роде

Нет. Они могут выжить вместе, не избивая друг друга. Мы будем придерживаться использования задач в качестве предварительных условий, таким образом мы сможем сделать код очевидным и ощутимым. Но вы можете сделать это и наоборот.

Представьте, что у вас есть 4 задачи, изображенные в фрагменте ниже:

 namespace :setup do task :init do puts ">init: Task to initiate a process" puts ">Imagine that all other tasks depend on this task" end task :cleanup => :init do #This task depends on :init puts ">>cleanup:Tasks related to cleaning up Gemfile, deleting public/index.html etc." end task :git=> :init do #This one too depends on :init puts ">>>git: Tasks concerned with setting up GIT repository" end task :all => [ :init, :cleanup, :git ] do #Depends on init, cleanup and git tasks. puts ">>>>all: Done" end end 

Попробуйте запустить

 rake setup:all 

и посмотрим, как это получится.

Последняя упомянутая команда выполняет все задачи подряд, потому что мы выполнили эту задачу с 3 предпосылками, а именно :init :cleanup и :git . Это классно. Но что если мы захотим настроить наше git-репо без очистки нашего Gemfile?

Это тоже достижимо. Идите вперед и введите это в свой терминал.

 rake setup:git 

Поскольку setup:init является обязательным условием для задачи, связанной с созданием git-репо, он будет выполнен и woohoo.

 Initializing.. Tasks concerned with setting up GIT repository. 

Грабли и рельсы: идеальный дуэт

Мы почти закончили с походами по основам Рейка. Осталось обсудить лишь несколько крошечных деталей о дизайне скрипта, который мы собираемся создать.

Обычно разработчики, как правило, имеют каталог, посвященный Rails, внутри которого они создают новые проекты и поддерживают существующие. Что-то вроде скриншота, изображенного ниже.

image2

Это хорошо. Мы создадим папку с именем Rake в каталоге ~ / Rails, которая, на мой взгляд, является желательным местом для размещения нашего Rakefile и всех связанных файлов.

 $ cd ~/Rails $ mkdir Rake 

Создайте новое приложение Rails, скажем, testapp в ~ / Rails. (С помощью rails new команда)

 $ ls testapp Rake 

Наш каталог Rake и каталог приложений Rails находятся на одном уровне. Итак, начнем.

Файл Rake и все связанные с ним файлы будут находиться в каталоге Rake . На другом конце у нас есть каталог testapp , который является каталогом назначения, которым мы планируем манипулировать. Задача setup:init запустит процесс. Он скопирует все файлы, находящиеся в каталоге Rake, в папку lib / tasks папки назначения. Каждое приложение Rails, созданное с помощью скрипта generate , поставляется с папкой lib / tasks . Соглашение Rails заключается в размещении всех задач Rake в этом каталоге, и эти задачи будут доступны по умолчанию из корня каталога приложений Rails.

image3

Вот набросок улучшенного задания init.

 namespace :setup do desc "Initiate setup. This task serves as a dependency for other tasks." task :init do print "Name of the destination directory: " name = STDIN.gets.strip if pwd().split('/').last == "Rake" puts "Copying the files to #{name}/lib/tasks." # Copying the rakefile over to lib/tasks of the destination directory. # This is a Rails generated directory dedicated for rake tasks. cp_r '.', "../#{name}/lib/tasks", verbose: false print "Do you want to continue with the setup?(y/n): " option = STDIN.gets.strip case option when /[^Yy]/ abort_message #A call to the abort_message method. end # change directory to the root of the Rails application directory cd "../#{name}" end end def abort_message abort("Exiting. You can each task individually. See rake -T for more info") #A handy method to exit early from a rake task. You can read more about it here. end end 

Помимо нескольких конструкций Ruby у вас не должно быть особых проблем с его расшифровкой. Хотя сохранение файлов Rake в lib / tasks не является обязательным, это может стать преимуществом на более позднем этапе фазы разработки.

Но есть подвох. Если вы планируете вызывать пользовательские задачи Rake из каталога testapp, как показано ниже, это не сработает.

 $ cd testapp rake setup:init rake aborted! Don't know how to build task 'setup:init' 

Хотя вы можете вызывать задачи из каталога Rake, не сталкиваясь ни с какими проблемами, выполнение пользовательских задач Rake из корневого каталога веб-приложения, как показано, приведет к ошибке. Rake будет игнорировать все файлы, в настоящее время помещенные в lib / tasks , включая Rakefile, потому что он будет загружать только файлы с расширением .rake. Чтобы это исправить, нам, возможно, придется настроить наш Rakefile. На данный момент мы позволим этому пройти и разберемся с этим позже.

Далее у нас есть задача очистки. Вы можете спроектировать эту задачу, чтобы выполнять все виды надоедливых заданий по очистке, прежде чем приступить к фактической разработке приложения. Как и в случае с большинством разработчиков, первое, что я делаю, это редактирую свой Gemfile, удаляя закомментированные строки кода и добавляя гемы, без которых мне трудно выжить.

 desc "Cleanup Gemfile and other stuff." task :cleanup => :init do print "Clean up the standard Gemfile for a new one?(y/n): " option = STDIN.gets.strip case option when /[^Yy]/ abort_message end puts "Setting a new Gemfile." cp 'lib/tasks/Gemfile', '.', verbose: false #Copying the gemfile from lib/tasks to the root of the project directory. puts "Running bundle install. This may take a while...\n\n" sh "bundle install" #sh bridges your task with the command-line, bestowing upon you the access to all the terminal commands. end 

Вот отредактированная версия Gemfile.

 source 'https://rubygems.org' ruby '2.0.0' gem 'rails', '4.0.0' group :development, :test do gem 'sqlite3', '1.3.7' end group :test do #test end gem 'sass-rails', '4.0.0' gem 'uglifier', '2.1.1' gem 'coffee-rails', '4.0.0' gem 'jquery-rails', '2.2.1' gem 'turbolinks', '1.1.1' gem 'jbuilder', '1.0.2' group :doc do gem 'sdoc', '0.3.20', require: false end group :production do #production end 

Мы поместим красивый Gemfile в наш каталог ~ / Rails / Rake, а Rake позаботится обо всем остальном. Команда rake setup:init скопирует Gemfile (вместе с Rakefile) в lib / tasks, а :cleanup переместит его в корневой каталог, заменив исходный Gemfile.

Вот моя реализация задач, связанных с инициализацией git, созданием репозитория git и помещением приложения в репозиторий github:

 desc "Git tasks" task :git => :init do print "Create a new GIT repository?(y/n): " option = STDIN.gets.strip case option when /[^Yy]/ abort_message end sh 'git init' puts "Adding a few items to .gitignore." cp 'lib/tasks/.gitignore', '.', verbose: false puts "Setting up git" sh 'git add .' sh 'git commit -m "Init" ' puts "Enter the link to your repository for this app." repo = STDIN.gets.strip sh "git remote add origin #{repo}" sh 'git push -u origin master' end 

Поскольку в вашем распоряжении есть все команды терминала через метод sh , вы можете с легкостью написать задачу для создания git-ветки:

 desc "Create a git branch" task :git_branch => :git do puts "Creating a git branch, just to be safe!" sh 'git checkout -b Pre-development' end desc "Run all setup tasks" task :all => [:init, :cleanup, :git, :git_branch] do puts "DOne" end 

Вот как выглядят наши базовые задачи из предыдущего, если добавить к ним немного мяса:

 namespace :setup do desc "Initiate setup. This task serves as a dependency for other tasks." task :init do print "Name of the destination directory: " name = STDIN.gets.strip #Calling gets by itself would result in a call to Kernel#gets which is not what we want. Switch to STDIN.gets instead. if pwd().split('/').last == "Rake" puts "Copying the files to #{name}/lib/tasks." # Copying the rakefile over to lib/tasks of the destination directory. This is a Rails generated directory dedicated for rake tasks. cp_r '.', "../#{name}/lib/tasks", verbose: false print "Do you want to continue with the setup?(y/n): " option = STDIN.gets.strip case option when /[^Yy]/ abort_message #A call to the abort_message method. end cd "../#{name}" #change directory to the root of the Rails app directory end end def abort_message abort("Exiting. You can each task individually. See rake -T for more info") #A handy method to exit early from a rake task. You can read more about it here. end desc "Cleanup Gemfile and other stuff." task :cleanup => :init do print "Clean up the standard Gemfile for a new one?(y/n): " option = STDIN.gets.strip case option when /[^Yy]/ abort_message end puts "Setting a new Gemfile." # Copying the gemfile from lib/tasks to the root of the project directory. cp 'lib/tasks/Gemfile', '.', verbose: false puts "Running bundle install. This may take a while...\n\n" #sh bridges your task with the command-line bestowing upon you, the access to all the terminal commands. sh "bundle install" end desc "Git tasks" task :git => :init do print "Create a new GIT repository?(y/n): " option = STDIN.gets.strip case option when /[^Yy]/ abort_message end sh 'git init' puts "Adding a few items to .gitignore." cp 'lib/tasks/.gitignore', '.', verbose: false puts "Setting up git" sh 'git add .' sh 'git commit -m "Init" ' puts "Enter the link to your repository for this app." repo = STDIN.gets.strip sh "git remote add origin #{repo}" sh 'git push -u origin master' end desc "Task to create a new git branch" task :git_branch => :git do puts "Creating a git branch, just to be safe!" sh 'git checkout -b Pre-development' end desc "Task to run all tasks under the setup namespace" task :all => [:init, :cleanup, :git, :git_branch] do puts "Done" end end 

Это много кода. Тем не менее, довольно легко понять, что он делает, потому что Ruby не усложняет ситуацию, а скорее упрощает ее до такой степени, что ее понимают совершенно незнакомые люди.

Не забудьте высушить ваш код

Rakefile выглядит немного загроможденным для меня. Так как скоро это станет довольно плохо, мы должны немного переработать и очистить его. Идеальное решение — аккуратно отсортировать задачи в отдельные файлы и импортировать их в основной файл Rakefile, например:

 Dir.glob('*.rake').each { |r| import r } 

init.rake

 namespace :setup do desc "Initiate setup. This task serves as a dependency for other tasks." task :init do print "Name of the destination directory: " name = STDIN.gets.strip if pwd().split('/').last == "Rake" puts "Copying the files to #{name}/lib/tasks." cp_r '.', "../#{name}/lib/tasks", verbose: false print "Do you want to continue with the setup?(y/n): " option = STDIN.gets.strip case_code(option) cd "../#{name}" else puts "We are already on the destination directory" end end def abort_message abort("Exiting. You can each task individually. See rake -T for more info") end def case_code(option) case option when /[^Yy]/ abort_message end end end 

cleanup.rake

 namespace :setup do desc "Cleanup Gemfile and other stuff." task :cleanup => :init do print "Clean up the standard Gemfile for a new one?(y/n): " option = STDIN.gets.strip case_code(option) puts "Setting a new Gemfile." cp 'lib/tasks/Gemfile', '.', verbose: false puts "Running bundle install. This may take a while...\n\n" sh "bundle install" end end 

git.rake

 namespace :setup do desc "Git tasks" task :git => :init do print "Create a new GIT repository?(y/n): " option = STDIN.gets.strip case_code(option) sh 'git init' puts "Adding a few items to .gitignore." cp 'lib/tasks/.gitignore', '.', verbose: false puts "Setting up git" sh 'git add .' sh 'git commit -m "Init" ' puts "Enter the link to your repository for this app." repo = STDIN.gets.strip sh "git remote add origin #{repo}" sh 'git push -u origin master' end task :git_branch => :git do puts "Creating a git branch, just to be safe!" sh 'git checkout -b Pre-development' end end 

all.rake

 namespace :setup do task :all => [:init, :cleanup, :git, :git_branch] do puts "Done" end end 

Это приносит нам пользу двумя способами,

  1. Наши задачи Rake теперь организованы и легко доступны.
  2. Вы можете вызывать эти задачи на любом этапе веб-разработки из корневого каталога проекта. Если вы помните, я упоминал, что файлы, находящиеся в lib / tasks, будут загружаться только в том случае, если они имеют расширение .rake . Rake проигнорировал наши пользовательские задачи ранее, потому что мы сохранили их в Rakefile.

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