Статьи

Рубиновая экосистема для новых рубистов

newrubyist

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

Управление версиями

Допустим, у вас есть два проекта, основанные на двух разных версиях драгоценного камня. Проект, который использует более новую версию, не совместим со старым гемом. Чем ты занимаешься?

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

Вместо этого большинство Rubyists полагаются на какой-то менеджер версий . Менеджеры версий не только поддерживают гемы в порядке, но и разделяют реализации Ruby. Это позволяет легко проверить различия между, скажем, Ruby 2.0 и Ruby 2.1.

Популярные решения для управления версиями включают RVM , rbenv и Uru (Windows).

Вот как вы начали бы управлять Ruby с помощью RVM:

Установите RVM.

 $ \curl -sSL https://get.rvm.io | bash 

Исходный RVM-скрипт в bash-совместимой оболочке при его запуске.

 $ echo "source $HOME/.rvm/scripts/rvm" >> ~/.bash_profile 

Перезагрузите оболочку.

 $ source ~/.bash_profile 

Убедитесь, что скрипт RVM был получен. Должно быть напечатано «rvm — это функция».

 $ type rvm | head -n 1 

Установите Ruby 2.0.0.

 $ rvm install 2.0.0 

Переключиться на 2.0.0.

 $ rvm use 2.0.0 

Создайте новый набор гемов для 2.0.0 под названием «экспериментальный».

 $ rvm gemset create experimental 

Переключитесь на новый набор драгоценностей.

 $ rvm gemset use experimental 

Изготовление драгоценных камней

Упакованный код не всегда был в Ruby. В 2003 году rubyforge.org был запущен как место для разработчиков Ruby для обмена кодом. Несмотря на то, что это немного улучшило ситуацию, разработчики все еще были сами по себе, когда дело дошло до выяснения того, как запускать код друг друга. В ноябре 2003 года некоторые разработчики Ruby собрались вместе и решили решить проблему навсегда. В 2004 году rubygems.org запустил, а вместе с ним и инструмент gem .

RubyGems — менеджер пакетов для библиотек и программ Ruby. Вот несколько причин, по которым можно создать гем Ruby:

  1. Легко делиться кодом с другими разработчиками
  2. Избегайте дублирования кода между проектами

Установить драгоценный камень легко:

 $ gem install gem_name 

Макет для типичного проекта Gem выглядит следующим образом:

 - spec - gem_name_spec.rb - spec_helper.rb - bin - gem_name - lib - gem_name.rb - gem_name - source_file1.rb - source_file2.rb - source_file... - version.rb - Gemfile - gem_name.gemspec - README.md - LICENSE - Rakefile 

Нет необходимости запоминать структуру каталогов gem. Простой способ создать базовый шаблон gem — это инструмент bundler .

 $ bundle gem gem_name 

Примечание: Bundler, вероятно, поставляется предварительно установленным с вашей установкой Ruby, но если нет или если вам нужна последняя версия, вы можете установить гем:

 $ gem install bundler 

Если вам не нравится шаблон, который создает Bundler, есть другие доступные инструменты, включая jeweler и hoe .

Путь загрузки

Нам нужно изучить неочевидную проблему, с которой сталкиваются новые разработчики гемов: путь загрузки Ruby. Допустим, у вас есть несколько файлов в одном каталоге (вообще не создавая гем). Мы назовем их source1.rb и source2.rb .

 # source1.rb require 'source2' # source2.rb puts "hello world" 

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

 $ ruby source1.rb ...'require': cannot load such file -- source2 (LoadError)... 

Подождите, что происходит? Даже если они находятся в одном каталоге, source1.rb не видит source2.rb . Оказывается, что Ruby не включает автоматически каталог исходного файла, который он выполняет, в путь загрузки . Мы можем заставить этот пример работать, сказав, что это делается с помощью параметров командной строки -I (include directory) и . (какой каталог — это текущий каталог):

 $ ruby -I . source1.rb hello world 

В качестве альтернативы вы можете require прямой путь к файлу:

 # source1.rb require './source2' 

Так что вам нужно делать что-то подобное при разработке драгоценных камней? Нет. Для тестирования гемов в разработке исходные каталоги могут быть программно добавлены в глобальную переменную Ruby $LOAD_PATH .

 # source1.rb $LOAD_PATH.unshift(".") require "source2" 

Это используется для включения папки lib и всех ее подкаталогов. Обычно вы сталкиваетесь с File::expand_path преобразующим относительный путь к папке lib из файла в абсолютный путь.

 # spec/spec_helper.rb lib = File.expand_path('../../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) # now we can require lib/gem_name.rb which can require other code as needed require 'gem_name' 

$LOAD_PATH — это просто массив. $LOAD_PATH.unshift(lib) добавляет каталог в начало массива, чтобы он загружался раньше всего. Обратите внимание, что File.expand_path('../../lib', __FILE__) ссылается на каталог lib один каталог вверх, а не на два, как это выглядит . Это обычная поездка. __FILE__ в качестве второго аргумента указывает каталог для запуска, в противном случае используется текущий рабочий каталог (в котором был __FILE__ Ruby).

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

 $:.unshift(File.expand_path('../../lib', __FILE__) 

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

 - lib - gem_name.rb - gem_name - some_file.rb # lib/gem_name.rb require 'gem_name/some_file' 

Когда гем установлен, содержимое каталога lib помещается в каталог, который уже находится в пути загрузки Ruby. Следовательно, путь загрузки не должен изменяться где-либо внутри фактического кода драгоценного камня, а имя драгоценного камня должно быть уникальным.

gemspec

Файл gemspec gemspec — это место, где гем фактически определен. Gemspec — это единственный файл, который должен существовать для создания гема .

 # gem_name.gemspec lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'gem_name/version' Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'gem_name' s.version = GemName::VERSION s.authors = ['Your Name'] s.email = ['[email protected]'] s.homepage = 'https://github.com/your_name/gem_name' s.summary = 'Gem in a few words' s.description = 'Longer decription of gem' s.required_ruby_version = '>= 1.9.3' s.require_path = 'lib' s.files = Dir[LICENSE, README.md, 'lib/**/*'] s.executables = ['gem_name'] s.add_dependency('sqlite3') s.add_development_dependency("rspec", ["~> 2.0"]) s.add_development_dependency("simplecov") end 

Не все эти поля обязательны для заполнения. Например, не каждый гем имеет исполняемые файлы, и некоторые могут предпочесть вместо этого поместить свои зависимости в Gemfile . Обязательно ознакомьтесь со спецификацией rubygems.org .

Gemfile

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

Зависимость gem в Gemfile выглядит следующим образом:

 gem <gem_name>, <version constraint> 

Вот пример Gemfile:

 # Gemfile source 'https://rubygems.org' gem 'nokogiri', '~> 1.4.2' group :development do gem 'sqlite3' end group :test do gem 'rspec' gem 'cucumber' end group :production do gem 'pg' end gemspec 

Неочевидным фрагментом синтаксиса в Gemfiles является так называемый оператор сперми ( ~> ). Этот оператор будет увеличивать последнюю цифру, пока она не перевернется. Так что gem 'nokogiri', '~> 1.4.2' семантически эквивалентен gem 'nokogiri", '>=1.4.2', '<1.5.0' .

gemspec просто указывает Bundler установить зависимости, перечисленные в файле gemspec. Многие предпочитают помещать все свои зависимости в Gemfile, поэтому эта строка не обязательна.

Если вы хотите синхронизировать свою среду с зависимостями гема, просто перейдите в каталог, содержащий Gemfile, и запустите:

 $ bundle install 

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

 $ bundle install --without production 

Когда Bundler вычисляет зависимости, он создает файл с именем Gemfile.lock . Цель этого файла — помочь избежать незначительных различий между средами разработки. Общий вопрос заключается в том, должно ли это идти в хранилище. Если проект является драгоценным камнем, нет, но если это приложение, да — причина в том, что приложения, как правило, имеют свои собственные отдельные наборы драгоценностей, и важно, чтобы у всех, кто разрабатывает приложение, была одинаковая точная версия каждой зависимости.

Для получения дополнительной информации см. Ссылку на Gemfile Bundler и Обоснование Bundler .

Rakefile

rake — это эквивалент мира Ruby для GNU make . В отличие от Gemfiles и gemspecs, Rakefiles существуют в основном для удобства. Наличие простых команд, таких как rake test позволяет автоматизированным системам (или другим разработчикам) тестировать код без необходимости знать, какую систему тестирования использовать.

 # Rakefile lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "gem_name/version" task :test do system "rspec" end task :build do system "gem build gem_name.gemspec" end task :release => :build do system "gem push gem_name-#{GemName::VERSION}" end 

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

Строительные драгоценные камни

Как только драгоценный камень правильно организован и протестирован, его можно построить. Команда gem build создаст файл .gem, который можно установить локально или отправить в хранилище gem. Имя файла будет включать версию, заданную в gemspec.

 $ gem build gem_name.gemspec $ gem install gem_name-version.gem $ gem push gem_name-version.gem 

Теперь в проектах вы или любой, кто устанавливает гем, можете использовать его с:

 require 'gem_name' 

Примечание . Иногда вы увидите в коде require 'rubygems' . Это пережиток того времени, когда RubyGems был отдельным проектом. Начиная с Ruby 1.9, RubyGems является частью стандартной библиотеки.

Bundle Exec

Вы будете часто команды, написанные как bundle exec rake db:migrate вместо rake db:migrate . Это связано с тем, что bundle exec гарантирует, что команда будет выполнена с использованием версий гемов, указанных в Gemfile . RVM создает наборы гемов с помощью rubygems-bundler, чтобы избежать этой проблемы, и это также не должно быть проблемой, если вы используете Ruby 2.2, но об этом стоит знать.

Ruby Реализации

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

  • MRI / CRuby — эталонная реализация Ruby, написанная на C
  • JRuby — Ruby работает на виртуальной машине Java
  • Rubinius — Ruby, написанный на Ruby, работающий на LLVM
  • RubyMotion — коммерческая реализация для создания нативных приложений для Apple / Android

Есть много других .

Кажется, существует распространенное заблуждение, что потоки не работают в Ruby. Потоки ограничены в CRuby (MRI), но они дают возможность работать над двумя проблемами одновременно. Важно понимать разницу между параллелизмом и параллелизмом :

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

Эталонная реализация Ruby, MRI, имеет глобальную блокировку интерпретатора , поэтому она может использовать только один собственный поток за раз. Это означает, что потоки Ruby могут работать одновременно, но не параллельно. Если в MRI требуется выполнение кода на нескольких процессорах, необходимо использовать разветвление или какой-либо другой вариант координации процессов (по крайней мере, до тех пор, пока GIL не будет удален, если вообще когда-либо). Есть реализации Ruby, в которых отсутствует GIL, включая все другие, перечисленные выше. Кроме того, разветвление технически недоступно для JVM, поэтому в JRuby вам все равно придется использовать потоки.

Я написал статью более подробно о разветвлении в Ruby здесь .

Документация

Когда gem установлен, вы часто будете замечать уведомление об rdoc документации rdoc и ri . RDoc — это встроенный генератор документации для Ruby. ri — это man инструмент для чтения автономной документации в терминале.

 $ ri Array $ ri Array#length $ ri Math::sqrt 

При использовании менеджера версий может потребоваться сгенерировать основную документацию вручную. В противном случае ri просто напечатает «Ничего не известно о [чем-либо]». Например, с помощью rvm основная документация может быть сгенерирована с помощью:

 $ rvm docs generate 

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

 $ gem install gem_name --no-rdoc --no-ri 

подглядывать

Другой способ изучить документацию по Ruby — установить pry . Pry — это альтернатива консоли irb с различными улучшениями, в том числе возможностью видеть оригинальный исходный код C для методов.

 $ gem install pry pry-doc $ pry [1] pry(main)> show-method Array#map From: array.c (C Method): Owner: Array Visibility: public Number of lines: 13 static VALUE rb_ary_collect(VALUE ary) { long i; VALUE collect; RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); collect = rb_ary_new2(RARRAY_LEN(ary)); for (i = 0; i < RARRAY_LEN(ary); i++) { rb_ary_push(collect, rb_yield(RARRAY_AREF(ary, i))); } return collect; } 1] pry(main)> cd Array [2] pry(Array):1> show-doc map From: array.c (C Method): Owner: Array Visibility: public Signature: map() Number of lines: 12 Invokes the given block once for each element of self. Creates a new array containing the values returned by the block. See also Enumerable#collect. If no block is given, an Enumerator is returned instead. a = [ "a", "b", "c", "d" ] a.collect { |x| x + "!" } #=> ["a!", "b!", "c!", "d!"] a.map.with_index{ |x, i| x * i } #=> ["", "b", "cc", "ddd"] a #=> ["a", "b", "c", "d"] 

тестирование

Допустим, вы клонировали какой-то случайный репозиторий, запустили некоторые метрики кода и исправили некоторое дублирование. Вы не знакомы с проектом, кроме файлов, над которыми вы работали. Откуда ты знаешь, что ничего не сломал?

Тестирование

Сообщество Ruby очень серьезно относится к тестированию. Среды тестирования для Ruby включают Test :: Unit , RSpec , Minitest и Cucumber . Test::Unit был оригинальной стандартной структурой модульного тестирования для Ruby, но он был Minitest устаревшим в пользу Minitest .

Как правило, тесты находятся в папке / test или / spec на корневом уровне проекта. Обычно существует файл /test/test_helper.rb или /spec/spec_helper.rb, который запускается перед обработкой тестовых случаев или спецификаций. Это хорошее место для настройки глобальной среды тестирования.

Давайте посмотрим на пример тестирования с Minitest . Minitest может выполнять как модульное тестирование, так и тестирование со спецификацией, а также бенчмаркинг.

Примечание: хотя Ruby поставляется с Minitest , я получил ошибку при использовании Minitest::Test вместо Minitest::Unit::TestCase . Это было исправлено путем установки последней версии гема.

 $ gem install minitest --version 5.4.2 

Мы начнем с простого тестового файла, использующего формат модульного теста.

 require "minitest/autorun" class Foo def hello "goodbye" end end class TestFoo < Minitest::Test def setup @foo = Foo.new end def test_hello assert_equal "hello", @foo.hello end end 

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

 1) Failure: TestFoo#test_hello [test.rb:15]: Expected: "hello" Actual: "goodbye" 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips 

Minitest — это круто, но RSpec, пожалуй, самая популярная среда тестирования во вселенной Ruby. Это во многом благодаря быстро развивающемуся сообществу и различным интеграциям с другими библиотеками тестирования, такими как Capybara . В отличие от Minitest , RSpec включает команду rspec теста, rspec , которая будет проверять все спецификации в иерархии spec/ каталогов с именем *_spec.rb .

Сначала установите гем rspec .

 $ gem install rspec --version 3.1.0 

Как правило, каталог spec/ имеет такую ​​структуру:

 - spec - spec_helper.rb - gem_or_app_name - models - some_model_spec.rb - controllers - some_controller_spec.rb 

Для демонстрации просто создайте пример спецификации и пустой spec_helper.rb .

 # spec/example_spec.rb require "spec_helper" class Foo def hello "goodbye" end end describe Foo do before(:each) do @foo = Foo.new end it "says hello" do expect(@foo.hello).to eq "hello" end end 

Чтобы проверить спецификацию, просто запустите rspec в корневом каталоге проекта.

 $ rspec Failures: 1) Foo says hello Failure/Error: expect(@foo.hello).to eq "hello" expected: "hello" got: "goodbye" (compared using ==) # ./spec/example_spec.rb:13:in `block (2 levels) in <top (required)>' Finished in 0.00079 seconds (files took 0.06974 seconds to load) 1 example, 1 failure</top> 

Как насчет spec / spec_helper.rb ? Чтобы увидеть, для чего он используется, мы добавим покрытие кода. SimpleCov — это популярный инструмент покрытия кода, который будет генерировать покрытие HTML в каталоге cover coverage/ в корне проекта.

Сначала установите гем simplecov .

 $ gem install simplecov --version 0.9.1 

Теперь просто вызовите SimpleCov.start в spec / spec_helper.rb .

 # spec/spec_helper.rb require "simplecov" SimpleCov.start 

Охват будет рассчитываться при каждом rspec . В этом случае нечего скрывать, но инструмент будет активирован.

Имейте в виду, что rspec не выполняет spec/spec_helper.rb автоматически. Он должен быть обязательным в каждой спецификации, добавив require 'spec_helper' в начало файла спецификации.

Резюме

Управление версиями :
— Использование системы Ruby установка чревата опасностью — используйте вместо этого менеджер версий.
— Используйте разные гемсеты для каждого крупного проекта. Изоляция может помочь избежать конфликтов, а также проблем с зависимостями, которые указаны неправильно или неточно.

Изготовление драгоценных камней :
— При разработке гемов каталог библиотеки необходимо вручную добавить в $LOAD_PATH чтобы код работал так, как если бы гем был установлен. Это можно сделать из командной строки или программно.
File.expand_path('../../lib', __FILE__) ссылается на папку lib на один каталог вверх, а не на два каталога, как может показаться.
— Только один файл, gem_name.rb , должен быть в каталоге lib . Остальная часть исходного кода должна идти в lib/gem_name/ .
— При использовании Ruby 1.8.7, require 'rubygems' должно идти перед тем, как требовать каких-либо драгоценных камней.
— Зависимости Gem могут входить в Gemfile Bundler или в Gemspec RubyGems.
— В gemspecs, #executable_files= executetable_files #executable_files= ожидает имена файлов, а не пути. Предполагается, что папка с именем bin .
— Rake-файлы похожи на Make-файлы и определяют общие задачи, такие как тестирование, сборка и выпуск.
— Для получения массивов файлов для gemspec популярно использовать git ls-files , но в Ruby есть Dir который более переносим.
bundle exec [command] выполняет команду, используя версии гемов, указанных в Gemfile. Это не должно быть необходимо с RVM или Ruby> = 2.2.0.

Ruby Реализации :
— Эталонная реализация Ruby (CRuby / MRI) имеет глобальную блокировку интерпретатора, поэтому, хотя параллелизм возможен с потоками, параллелизм (с несколькими процессорами одновременно) невозможен.
— Если параллелизм необходим в МРТ, используйте разветвление с Kernel#fork
— Параллельное выполнение потоков может быть достигнуто с помощью JRuby, Rubinius и RubyMotion.

Документация
— Инструмент ri можно использовать для изучения основной документации в автономном режиме.
rdoc — это стандартный встроенный генератор документации Ruby.
— Возможно, ваш менеджер версий должен установить документацию для вашей реализации Ruby, например, с помощью команды rvm docs generate .
pry — это сложная консоль, которая может исследовать документацию и программы во время выполнения.

тестирование
— Тесты или спецификации идут в папку test / или spec / проекта.
— Настройка и конфигурация идут в test / test_helper.rb или spec / spec_helper.rb
MiniTest — это стандартная библиотека тестирования, но вы можете столкнуться с устаревшим кодом, который использует Test::Unit .
— Все спецификации RSpec можно проверить, запустив rspec в корневом каталоге проекта, содержащем spec/ .
— Все спецификации должны заканчиваться * _spec.rb, иначе они будут пропущены, если не будут запущены напрямую.
— RSpec не выполняет spec / spec_helper.rb автоматически. require 'spec_helper' должен идти в каждом файле спецификации.
— SimpleCov — это библиотека покрытия тестов, которую можно использовать, установив гем и добавив его в вспомогательный файл набора тестов.