Статьи

Создание драгоценных камней с Bundler

Построение драгоценного камня раньше было сложной задачей, которая требовала либо точного знания формата самого камня, либо некоторых специальных инструментов для создания подходящего шаблона. В наши дни мы можем использовать превосходный Bundler, чтобы устранить эту сложность и свести к минимуму объем сгенерированного кода.


Тестовый драгоценный камень, который мы собираемся создать, — это фиктивный генератор контента, который вы можете использовать во время разработки. Вместо того, чтобы генерировать предложения «lorem ipsum», он использует Дракулу Брэма Стокера для генерирования произвольного количества предложений, взятых из книги. Наш рабочий процесс начнется с создания гема, тестирования и реализации минимального количества кода, необходимого для подготовки нашего гема, а затем его публикации на RubyGems .


Я собираюсь предположить, что у вас уже установлена ​​среда Ruby. Для этого урока мы будем использовать Ruby 1.9.3 в качестве базового уровня. Однако, если вы планируете разработать настоящий драгоценный камень, было бы неплохо также протестировать его на Ruby 1.8 и других интерпретаторах. Для этой цели инструмент, подобный Travis CI, является находкой; Благодаря надежному набору тестов Travis позволит вам испытать ваш драгоценный камень на самых разных платформах без каких-либо хлопот. Давайте начнем с создания скелета:

1
bundle gem bramipsum

Мне очень жаль, если вам не нравится имя, которое я выбрал, на самом деле одна из самых сложных задач при разработке драгоценного камня — найти правильное имя. Команда создаст каталог под названием bramipsum с несколькими файлами:

Gemfile очень минимален:

1
2
3
4
source ‘http://rubygems.org’
 
# Specify your gem’s dependencies in bramipsum.gemspec
gemspec

Обратите внимание, что в нем четко bramipsum.gemspec , что вы должны переместить свои зависимости bramipsum.gemspec в bramipsum.gemspec , чтобы в bramipsum.gemspec были все соответствующие данные для вашего bramipsum.gemspec , которые будут использоваться для заполнения метаданных в Rubygems.

Файл gemspec содержит много информации о нашем геме; мы видим, что он в значительной степени полагается на Git, чтобы назначить правильные значения всем переменным, которые включают листинг файла.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
# -*- encoding: utf-8 -*-
require File.expand_path(‘../lib/bramipsum/version’, __FILE__)
 
Gem::Specification.new do |gem|
  gem.authors = [«Claudio Ortolina»]
  gem.email = [«[email protected]»]
  gem.description = %q{TODO: Write a gem description}
  gem.summary = %q{TODO: Write a gem summary}
  gem.homepage = «»
 
  gem.executables = `git ls-files — bin/*`.split(«\n»).map{ |f|
  gem.files = `git ls-files`.split(«\n»)
  gem.test_files = `git ls-files — {test,spec,features}/*`.split(«\n»)
  gem.name = «bramipsum»
  gem.require_paths = [«lib»]
  gem.version = Bramipsum::VERSION
  gem.add_development_dependency ‘rake’
end

Далее мы можем запустить bundle для установки Rake. Поскольку он был добавлен как зависимость для разработки, он не будет установлен Bundler, когда кто-то использует наш гем.

Несколько интересных заметок о файле:

  • Он включает в себя вступительный комментарий Ruby 1.9, который определяет кодировку файла. Это важно, так как некоторые данные в файле (например, электронная почта или имя автора) могут быть не-символами ascii.
  • description и summary должны быть изменены, чтобы правильно отображаться на Rubygems.
  • Версия определяется внутри файла lib/bramipsum/version , который требуется вверху. Он определяет константу VERSION , вызываемую непосредственно перед концом файла.

Папка lib содержит общий файл bramipsum.rb для которого требуется модуль version . Даже если комментарий в файле предполагает, что вы добавляете код непосредственно в сам файл, мы будем использовать его просто для того, чтобы требовать отдельных классов, которые будут формировать наш маленький драгоценный камень.


Начнем с обновления данных в bramipsum.gemspec :

1
2
3
4
gem.description = %q{Random sentences from Bram Stoker’s Dracula}
gem.summary = %q{Generate one or more dummy sentences taken from Bram Stoker’s Dracula}

Очень простые вещи. Далее давайте добавим поддержку для правильного тестирования. Мы будем использовать Minitest , так как он включен по умолчанию в Ruby 1.9. Давайте добавим test каталог:

1
mkdir test

Далее нам нужен файл test_helper.rb и тест на наличие Bramipsum::VERSION .

1
2
3
touch test/test_helper.rb
mkdir -p test/lib/bramipsum
touch test/lib/bramipsum/version_test.rb

Давайте test_helper.rb файл test_helper.rb и добавим несколько строк:

1
2
3
require ‘minitest/autorun’
require ‘minitest/pride’
require File.expand_path(‘../../lib/bramipsum.rb’, __FILE__)

Это требует Minitest и Pride для цветного вывода; тогда требуется основной файл bramipsum.

Файл version_test.rb необходимо обновить с помощью следующего кода:

1
2
3
4
5
6
7
8
9
require_relative ‘../../test_helper’
 
describe Bramipsum do
 
  it «must be defined» do
    Bramipsum::VERSION.wont_be_nil
  end
 
end

Мы используем формат ожидания для наших тестов. Сам тест довольно понятен, и его легко запустить, набрав:

1
ruby test/lib/bramipsum/version_test.rb

Вы должны пройти сдачу теста!

Давайте теперь обновим Rakefile чтобы иметь более удобный способ запуска наших тестов. Сотри все и вставь следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
#!/usr/bin/env rake
require «bundler/gem_tasks»
 
require ‘rake/testtask’
 
Rake::TestTask.new do |t|
  t.libs << ‘lib/bramipsum’
  t.test_files = FileList[‘test/lib/bramipsum/*_test.rb’]
  t.verbose = true
end
 
task :default => :test

Это позволит нам запустить наши тесты, набрав rake из корневой папки gem.


Поскольку основное внимание в этом руководстве уделяется созданию драгоценного камня, мы ограничим количество добавляемой функциональности.


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

1
2
mkdir -p book
curl https://raw.github.com/cloud8421/bundler-gem-tutorial/master/book/dracula.txt -o book/dracula.txt

Давайте теперь создадим Base класс, куда добавим все методы, необходимые для извлечения данных из книги.

1
2
touch lib/bramipsum/base.rb
touch test/lib/bramipsum/base_test.rb

Тестовый файл будет иметь только несколько ожиданий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
require_relative ‘../../test_helper’
 
describe Bramipsum::Base do
 
  subject { Bramipsum::Base }
 
  describe «reading from file» do
 
    it «must have a source» do
      subject.must_respond_to(:source)
    end
 
    it «must have the dracula file as a source» do
      subject.source.must_be_instance_of(String)
    end
 
  end
 
  describe «splitting into lines» do
 
    it «must correctly split the file into lines» do
      subject.processed_source.must_be_instance_of(Array)
    end
 
    it «must correctly remove empty lines» do
      subject.processed_source.wont_include(nil)
    end
 
  end
 
end

Запуск rake теперь покажет исключение, так как файл base.rb по-прежнему пуст. Base просто прочитает содержимое файла и вернет массив строк (удалив пустые).

Реализация очень проста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
module Bramipsum
 
  class Base
 
    def self.source
      @source ||= self.read
    end
 
    def self.processed_source
      @processed_source ||= self.source.split(«\n»).uniq
    end
 
    private
 
    def self.read
      File.read(File.expand_path(‘book/dracula.txt’))
    end
 
  end
 
end

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

Затем нам нужно открыть lib/bramipsum.rb и добавить правильный оператор require:

1
require_relative «./bramipsum/base»

Если вы сохраните и запустите rake сейчас, вы должны увидеть, что все тесты пройдены.


Далее нам нужно добавить новый класс для генерации предложений. Мы назовем это Sentence .

1
2
touch lib/bramipsum/sentence.rb
touch test/lib/bramipsum/sentence_test.rb

Как и прежде, мы должны открыть lib/bramipsum.rb и потребовать новый созданный файл:

1
2
require_relative «./bramipsum/base»
require_relative «./bramipsum/sentence»

Этот класс будет наследоваться от Base , поэтому мы можем сохранить реализацию минимальной. Для теста понадобятся только три ожидания:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
require_relative ‘../../test_helper’
 
describe Bramipsum::Sentence do
 
  subject { Bramipsum::Sentence }
 
  it «must return a random sentence» do
    subject.sentence.must_be_instance_of(String)
  end
 
  it «must return 5 sentences by default» do
    subject.sentences.size.must_equal(5)
  end
 
  it «must return the specified amount of sentences» do
    subject.sentences(10).size.must_equal(10)
  end
 
end

Идея состоит в том, что мы можем вызвать Bramipsum::Sentence.sentence или Bramipsum::Sentence.sentences(10) чтобы сгенерировать то, что нам нужно.

Содержание sentence.rb также очень лаконично:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
module Bramipsum
 
  class Sentence < Base
 
    def self.sentence
      self.processed_source.sample
    end
 
    def self.sentences(n=5)
      self.processed_source.sample(n)
    end
 
  end
 
end

Поскольку мы работаем с Ruby 1.9, мы можем использовать метод sample для возврата случайного элемента из массива.

Еще раз, запущенные rake должны показать все пройденные тесты.


Если вы запустите gem build из командной строки, для вас будет собрана и упакована локальная копия gem. Если вам не нужно распространять его, или если вам нужно держать его в секрете, вы можете остановиться здесь. Но если это проект с открытым исходным кодом, я призываю вас сделать это.

Очевидный шаг — добавить наш новый драгоценный камень на RubyGems.org.

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

1
curl -u cloud8421 https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials

Теперь вы только в одном шаге, чтобы опубликовать драгоценный камень на Rubygems, однако не делайте этого, если вы действительно этого не хотите. Команда, которую вы запустите:

1
gem push bramipsum-0.0.1.gem

Поздравляем! Теперь вы знаете, как создать драгоценный камень с нуля, просто используя Bundler. Однако, есть и другие вещи, которые необходимо учитывать:

  • Совместимость: вы также можете поддерживать Ruby 1.8. Это потребует рефакторинга всех вызовов require_relative ; Кроме того, вам нужно будет использовать драгоценный камень Minitest как он не включен по умолчанию в Ruby 1.8
  • Непрерывная интеграция: вы можете добавить поддержку Travis CI, и ваш драгоценный камень будет протестирован в облаке со всеми основными выпусками Ruby. Это позволит легко быть уверенным в отсутствии проблем с изменениями поведения платформы.
  • Документация: это важно, хорошо иметь комментарии RDoc, которые могут помочь в создании автоматических документов, и хороший файл README с примерами и рекомендациями.

Спасибо за прочтение! Любые вопросы?