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: