Warte uwagi: Bundler
Wraz z nadchodzącymi wielkimi krokami Rails 3 sporo mówi się o jednym konkretnym gemie. Ma ułatwić zarządzanie zależnościami w aplikacjach. Ma uprościć pracę przy tworzeniu i korzystaniu z gemów. Generalnie ma zbawić świat, uzależnić wszystkich developerów od siebie, a następnie odpalić cap deploy:skynet i po sprawie... OK, może bez tego ostatniego.
Warto jednak wiedzieć, że Bundler nie jest jedynie częścią Rails 3. Jest niezależnym, potężnym narzędziem, które można - i warto - używać przy tworzeniu wszelkich aplikacji w Rubym.
Dlaczego?
Wszystko zaczęło się w 428 r. p.n.e... nie wróć to znowu nie to. Budowa aplikacji, choćby najmniejszych, wymaga od programisty korzystania z wielu narzędzi. Dzięki dobrodziejstwom open-source mamy do dyspozycji tysiące bibliotek napisanych przez tysiące innych developerów. Każda z nich to żyjący własnym życiem twór, który w trakcie rozwoju i działania naszego projektu może przechodzić przez multum zmian. Jak zapewnić, że zmiana w zewnętrznym module nie wpłynie negatywnie na działanie naszej aplikacji? Jak zadbać o to, by każdy developer w zespole miał identyczne środowisko? Railsy radziły sobie do tej pory korzystając z wpisów config.gem w pliku environment.rb - możliwość określenia specyficznej wersji i źródła gema daje pewne zabezpieczenie, choć mocno ogranicza.
Ale świat nie kończy się na Rails, rozbudowane zależności mogą mieć wszystkie aplikacje w Rubym. Co więcej, same gemy zależą nierzadko od innych gemów i tu zaczynają się poważne schody. Developerzy wynajdowali patenty na zapewnienie zgodności, m.in. hardkodując je w źródle swoich bibliotek. Takie potworki zamiast sprawę rozwiązywać, tylko ją komplikowały.
Bundler ma na celu wszystkie te problemy rozwiązać.
Jak?
Weźmy na warsztat prosty skrypt z jedną zależnością:
require "rubygems"
require "nokogiri"
require "open-uri"
doc = Nokogiri::HTML(open('http://www.google.com/search?q=lukasz+adamczak'))
link = doc.css('h3.r a.l').first
puts link.content
Jeśli w systemie mamy nokogiri, program powinien wypisać:
lans musi być
Jeśli nie, przeczytamy całkiem słuszny komunikat:
no such file to load -- nokogiri
Idąc tradycyjną ścieżką, moglibyśmy teraz zainstalować gem install nokogiri i problem rozwiązany. Pojdźmy jednak The Bundler Way. Instalujemy bundlera (na dzień dzisiejszy aktualna wersja to 1.0.0.rc.2, więc trzeba użyć --pre)
gem install bundler --pre
Otrzymujemy bardzo potężne polecenie bundle, którego możemy użyć już teraz. bundle init utworzy w bieżącym katalogu prosty wzorcowy plik Gemfile, który będzie opisywał wszystkie zależności naszego projektu. Drugi krok to użycie Bundlera w kodzie. Zmieniamy układ require'ów na:
require "rubygems" require "bundler/setup" require "nokogiri" require "open-uri"
require "bundler/setup" gwarantuje, że projekt będzie wykorzystywał do zarządzania zależnościami właśnie bundlera. W tym momencie cały projekt stał się odizolowaną "bańką". Całe środowisko jest opisane przez Gemfile. Nie mają znaczenia gemy jakie masz w systemie. Tylko to, co wyraźnie zaznaczysz w Gemfile'u będzie należało do projektu. Aby się przekonać zainstaluj:
sudo gem install nokogiri
Mogłoby się wydawać, że teraz skrypt wykona się poprawnie. Błąd! Bundler sprawdzi plik Gemfile (domyślnie pusty) i uzna, że nie wie co to nokogiri:
no such file to load -- nokogiri (LoadError)
Dodaj więc do Gemfile linię:
gem "nokogiri"
Teraz skrypt wykonuje się poprawnie. Standardowa kolejność pracy z Bundlerem powinna jednak być odwrotna. Po dodaniu lub zmianie czegokolwiek w Gemfile, pamiętaj o odpaleniu polecenia bundle install. Zajmie się ono instalacją brakujących gemów, w razie potrzeby prosząc nawet o hasło do sudo. Zainstalowane w ten sposób gemy trafiają domyślnie w to samo miejsce gdzie instalowane przez gem install.
bundle ma też kilka innych ciekawych opcji. check do sprawdzenia czy posiadamy wszystko co wymagane.
$ bundle check Could not find gem 'nokogiri (= 1.4.3.1, runtime)' in any of the gem sources.
(a po instalacji)
$ bundle check The Gemfile's dependencies are satisfied
console uruchamia irb z wczytanym kompletnym środowiskiem:
$ bundle console ruby-1.8.7-p249 > Nokogiri => Nokogiri
bundle exec umożliwia uruchomienie wykonywalnego skryptu z gema - z tej konkretnej wersji, którą podaliśmy w Gemfile:
$ bundle exec nokogiri http://rubysfera.pl Your document is stored in @doc... ruby-1.8.7-p249 >
show i open pozwalają znaleźć i otworzyć katalog ze źródłem gema:
$ bundle show Gems included by the bundle: * bundler (1.0.0.rc.2) * nokogiri (1.4.1) $ bundle show nokogiri /Users/Czak/.rvm/gems/ruby-1.8.7-p249/gems/nokogiri-1.4.1 $ bundle open nokogiri # otwiera powyższy katalog w domyślnym edytorze
Aha!
Bundler podczas pracy tworzy automatycznie plik Gemfile.lock na bazie Gemfile oraz stanu Twojego aktualnego środowiska. Jeśli w Gemfile nie określisz wymaganej wersji nokogiri, a w systemie masz zainstalowaną 1.4.1, wtedy do Gemfile.lock trafi:
GEM
remote: http://rubygems.org/
specs:
nokogiri (1.4.1)
Należy pamiętać aby Gemfile.lock dodać do repozytorium wraz z źródłowym Gemfile. To zagwarantuje, że cały zespół będzie pracował na identycznym środowisku. Jeśli w którymś momencie wymusisz w Gemfile wersję:
gem "nokogiri", "1.4.3.1"
wtedy Gemfile.lock zostanie uaktualniony automatycznie przez bundlera i obie zmiany należy wcommitować. Po każdej zmianie w Gemfile (a właściwie jak często się da, np. po git pull) warto uruchamiać bundle install aby zsynchronizować oba pliki i - w razie potrzeby - uaktualnić lokalne środowisko.
Na koniec
Takim krótkim tekstem ledwo musnąłem powierzchnię Bundlera. Może jednak przekonałem kogoś, że jest to narzędzie warte uwagi i jednocześnie łatwe w użyciu. Warto już teraz wyrobić sobie nawyk wywoływania bundle init zaraz po git init, niezależnie od rodzaju budowanej aplikacji. Bundler może w niedługim czasie wprowadzić trochę ładu do Rubinowego światka, na czym skorzysta każdy developer.
Aby dowiedzieć się więcej:

