Posted by Piotr Sarnacki
Sun, 03 May 2009 18:10:00 GMT
Ponad 4 miesiące temu światem Ruby’iego zatrzęsła wiadomość o połączeniu się 2 popularnych frameworków – Ruby on Rails i Merba. Dzisiaj, tylko kilka dni dzieli nas od ujrzenia pierwszych aplikacji, które będą napisane w Rails 3. Carl Lerche (twórca między innymi routera do merba) oraz Yehuda Katz na tegorocznym RailsConf będą mówić o Rails3 i mountable apps.
Na stabilną wersję będzie trzeba jeszcze trochę poczekać, ale trzymam kciuki, żeby na RailsConf rzeczywiście udało im się pokazać działającą aplikację.
W tym poście chciałbym krótko opisać to co ma się znaleźć w Rails 3. Z niemałą niecierpliwością śledzę newsy i doniesienia o nowych railsach i nie mogę się już doczekać niektórych usprawnień.
ActionORM (wcześniej ActiveORM)
Zwolennicy Merba bardzo podkreślają w swoim frameworku niezależność od konkretnych technologii. Było to reklamowane głównie jako “ORM agnosticism” czyli możliwość podpięcia do Merba jednego z wielu “ORMów” (DataMapper, ActiveRecord, Sequel) w porównaniu do Railsowej monolityczności w postaci jednego słusznego ActiveRecorda.
Jaki właściwie jest problem w używaniu innego ORMa w naszej aplikacji? Na poziomie modeli i kontrolerów nie ma to z reguły większego znaczenia. Przecież Railsów nie obchodzi czy klasa w katalogu app/models dziedziczy po ActiveRecord::Base, czy dołącza moduł: include DataMapper::Resource
Kłopot zaczyna się kiedy chce się korzystać z wielu helperów, które jako argument przyjmują obiekt ActiveRecord. Najprostszy przykład:
<% form_for @article do |f| %>
<%= f.text_field :title %>
<% end %>
Jeżeli @article nie jest obiektem AR, to poprawność działania tej metody zależy tylko od tego czy interfejs danego ORMa tutaj używany jest taki sam. W tym akurat przypadku problemem może być sprawdzanie błędów w walidacji. Railsy domyślnie dodają wrapper do pól z błędami, więc jeżeli ORM ma inny interfejs do pobrania informacji o błędach, to metoda po prostu nie zadziała.
W tym momencie jest to w Railsach zrobione z użyciem alias_method_chain i jeżeli obiekt nie ma zaimplementowanej metody errors, to tag pozostaje niezmieniony, więc można samemu dołączyć swoją własną implementację lub dodać potrzebne metody do modelu. Jednak wszyscy chyba się zgodzą, że nie jest to najbardziej elegancki sposób.
Jeżeli chodzi o Merba, to sprawa wygląda tylko trochę lepiej niż w railsach. Jest co prawda wsparcie dla 3 najpopularniejszych ORMów, ale implementacja nie pozwala na łatwe dodanie kolejnych ORMów bez grzebania w kodzie. Jeżeli ORM, którego chcesz używać ma interfejs zgodny z DataMapperem, Sequelem lub ActiveRecordem, to wszystko jest ok, ale jeżeli jest to coś innego, to masz problem.
Odpowiedzią na to ma być ActionORM. Czym jest ActionORM? Jest to proste proxy dla obiektów. ActionORM udostępnia API takie jak ActiveRecord (dzięki temu w kodzie railsów nie będzie trzeba wiele zmieniać). W prostych słowach ActionORM ma sprawić, że obiekty innych ORMów będą wyglądały jak obiekty ActiveRecord (czyli klasyczne wykorzystanie duck typing)
Tutaj jest przykład proxy dla Datamappera: http://github.com/lancecarlson/rails/blob/0faae5b971c3dbf3b1c4ead19504580233bbc7fa/activeorm/lib/active_orm/proxies/datamapper_proxy.rb
Różnice są jak widać niewielkie, ale podejrzewam, że będzie można dodać więcej metod, które się różnią pomiędzy implementacjami (oczywiście w granicach rozsądku, sprowadzanie całego interfejsu do jednego przypadku było by pozbawione sensu, chodzi o proste operacje, które każdy ORM ma rozwiązane podobnie, ale różni się API).
Rack Middleware
Railsy od jakiegoś czasu wykorzystują w pełni dobrodziejstwa Rack. Co więcej, różne części frameworka są zaimplementowane jako rack middlewares. Kontroler, router, obsługa sesji i wiele innych rzeczy to teraz po prostu kolejne middlewares (przepraszam za te anglojęzyczne wstawki, ale naprawdę nie mam pojęcia jak to ładnie przetłumaczyć). Dzięki temu i dodaniu Rails metal aplikacje są coraz bardziej modularne.
Programiści railsów dążą w tej chwili do jeszcze szerszego wykorzystania racka. Wszystko to idzie w stronę jak najłatwiejszego połączenia komponentów różnego typu. Na przykład możliwość podłączenia do routera kontrolerów railsowych, aplikacji sintatry i aplikacji merba (jako jedną aplikację oczywiście).
Optymalizacja i refaktoryzacja
Największą i najlepszą według mnie zmianą w railsach jest refaktoryzacja kodu ActionPack. Na czym ona polega? W bardzo prostych słowach: stworzony jest AbstractController, z którego dziedziczyć będą inne kontrolery, a w samym kodzie jest zastosowana dużo większa enkapsulacja. Jakie to ma znaczenie dla użytkowników railsów? Będzie istniał jeden interfejs dla wszystkich kontrolerów, dzięki czemu na przykład ActionController i ActionMailer będą mogły współdzielić funkcjonalność (żeby nie iść daleko, dopiero niedawno do mailera dodana została obsługa layoutów). Bardzo łatwe będzie też dodanie Parts znanych z merba (PartsController również dziedziczy w Merbie z AbstractControllera).
W chwili obecnej podobną funkcjonalność do Merb Parts udostępniają Rails Cells, ale nie jest to rozwiązanie idealne (implementacja jest zagmatwana, w cellach widoczne są zmienne instancji z kontrolerów i innych celli, podejrzewam, że jest to wolniejsze niż parts).
Co jeszcze? Yehuda Katz wziął się między innymi za optymalizację callbacków, czy bloku respond_to. Polecam również inne artykuły na temat refaktoryzacji na jego blogu.
Na pewno szybszy będzie też router. W chwili obecnej największym problemem jest to, że router generuje bardzo dużo metod (wszystkie metody _url i _path), co w rezultacie przyczynia się do bardzo wolnego startu aplikacji. W czasach mongreli nie miało to dużego znaczenia, bo aplikacje były odpalane raz i tylko sporadycznie restartowane. Teraz kiedy na wielu serwerach zainstalowany jest mod_passenger, czas startu aplikacji jest dużo ważniejszy – instancje, które nie dostają requestów przez 5 minut (domyślne ustawienie) są wyłączane dzięki czemu nie zajmują zasobów.
Nowy router można przetestować już teraz. Po więcej odsyłam na the merbist
Mountable apps
Jest to jedna z tych rzeczy, na które czekam w Railsach 3 najbardziej. Dosłownie liczę godziny do rails conf i prezentacji na ich temat (mam nadzieję, że będą nagrywane, bo niestety nie miałem wolnych kilku tysięcy złotych, żeby się pojawić osobiście na konferencji ;-).
Jakieś namiastki mountable apps są już dostępne w postaci Rails Engines i Merb Slices. Jest to jakieś rozwiązanie, ale na pewno dalekie od idealnego. Żeby skorzystać z którejś z tych opcji trzeba napisać specyficzny rodzaj aplikacji, a każde z tych rozwiązań jest w niektórych miejscach niedopracowane. Celem mountable apps będzie możliwość łatwego połączenia 2 aplikacji bez żadnych zmian w kodzie.
API dla pluginów
Jedną z rzeczy, którą wypromował Merb jest publiczne i prywatne api. Dzięki temu pisanie pluginów nie wiąże się tam z używaniem alias_method_chain i ogólnie pojętym monkey patchingiem na każdym kroku. A jeżeli czegoś nie da się zrobić używając tylko publicznego api, to jest to bug. Jest to chyba jedna z trudniejszych rzeczy w całej refaktoryzacji. Nie wiadomo dokładnie czego będą potrzebować twórcy pluginów, niektóre metody trzeba będzie dopiero stworzyć.
Ogromnym plusem takiego podejścia jest to, że twórcy frameworka nie muszą się martwić o prywatne metody, ważne żeby publiczne API się nie zmieniało. Dzięki temu pluginy napisane zgodnie z wytycznymi, czyli nie używając prywatnego API nie będą się wysypywały na każdej kolejnej wersji (co bardzo często można zaobserwować w tym momencie), a jeżeli coś się zmieni w publicznym API przy okazji większych zmian, to twórcy pluginów będą mogli się o tym łatwo dowiedzieć bez śledzenia commitów na githubie.
Podsumowanie
Jestem zdania, że połączenie merba i railsów to jedna z najlepszych rzeczy, która mogła się przytrafić programistom webowym. Railsy będą szybsze, bardziej modularne i łatwiejsze w rozszerzaniu. Tylko czekać na wyniki refaktoryzacji :)
Postaram się w najbliższych tygodniach pisać o tym co dzieje się z Railsami3, jak idą prace i co jeszcze ciekawego będzie można zobaczyć.
Posted in Ruby on Rails | Tags merb, rails, rails3, refaktoryzacja, ruby, rubyonrails | 7 comments | no trackbacks
Posted by Piotr Sarnacki
Sat, 15 Nov 2008 18:12:00 GMT
Czasami przeglądając kod różnych aplikacji można zauważyć kawałki wyglądające mniej więcej tak:
attr_accessor :price
def total_price
p = items.inject(0) {|sum, item| sum + item.price }
# więcej kodu
p
end
W prawdziwej aplikacji można to zobaczyć na przykład w Spree (użycie tot zamiast total)
p oznacza tutaj zapewne price, ale autor kodu intuicyjnie stwierdził, że pisząc “price =”, zamiast stworzenia nowej zmiennej przypisze wartość na atrybut price.
Rzeczywiście jest to intuicyjne i całkiem bezpieczne, ale zupełnie niepotrzebne. Spokojnie i bez żadnego problemu można użyć price. Dlaczego?
W rubim można opuścić słowo self jeżeli chodzi o odczytanie wartości metody. Jeżeli więc bez zadeklarowania zmiennej price ktoś napisałby puts(price), to ruby zinterpretowałby to jako chęć wyświetlenia self.price. Inaczej jest z zapisywaniem. Jeżeli wywołana jest metoda price=, to ruby stworzy nową zmienną.
Na przykład taki kod:
class Product
attr_accessor :price
def initialize(price)
self.price = price
end
def do_something_with_price
price = 10
puts price
puts self.price
end
end
p = Product.new(20)
p.do_something_with_price
Wypisze:
10
20
Na koniec napiszę tylko, że mam mieszane uczucia co do korzystania z tych właściwości języka. Z jednej strony nie lubię jak w kodzie pałętają się skrótowe nazwy zmiennych, ale z drugiej strony taki zapis pokazuje, że chodzi nam o coś innego niż self.costam.
Ja z reguły staram się nie skracać nazw w takich wypadkach. Jakie jest wasze zdanie?
Posted in Programowanie | Tags ruby, self | no comments | no trackbacks
Posted by Piotr Sarnacki
Fri, 03 Oct 2008 10:37:00 GMT
W railsach od jakiegoś czasu można używać named_scope. Jest to bardzo fajny mechanizm umożliwiający łatwiejsze skonstruowanie zapytania używając wcześniej zdefiniowanych metod. Wcześniej był dostępny jako plugin has_finder.
Wygląda to mniej więcej tak:
named_scope :active, :conditions => { :active => true }
named_scope :paid, :conditions => { :paid => true }
named_scope :recent, lambda { { :conditions => ['created_at >= ?', 1.week.ago] } }
Po zdefiniowaniu takich sope’ów (macie pomysł jak to spolszczyć?) można ich używać w ten sposób (zakładam, że scope’y zostały zadeklarowane w modelu Product):
Product.active.paid.recent
Jak można łatwo zauważyć, scope’y można łączyć w łańcuchy. Dzięki takiej konstrukcji otrzymujemy prosty i treściwy kod, który łatwo zrozumieć. Teraz coś lekko trudniejszego:
named_scope :in_category, lambda { |category| { :conditions => { :category_id => category } } }
# i użycie
Product.active.recent.in_category(category)
O co chodzi? Jako 2 argument można przekazać lambdę, która zostanie wywołana przy użyciu scope’a i powinna zwrócić Hash.
Ale to nie są jedyne zastosowania scope’ów. Można do nich przekazać też inne parametry, które może przyjąć metoda find.
Na przykład order, offset i limit:
named_scope :order, lambda { |*args| { :order => args.first || "created_at DESC" } }
named_scope :offset lambda { |offset| { :offset => offset } }
named_scope :limit, lambda { |*args| { :limit => args.first || 10 } }
# i użycie:
Product.active.order("id DESC").offset(10).limit
W pierwszym i trzecim scope’ie zastosowałem trick, który pozwala na użycie zdefiniowanej wartości domyślnej w razie gdy żadna nie zostanie podana. `*args` pakuje argumenty do tablicy. Jeżeli args.first zwróci nil, to znaczy, że tablica jest pusta, czyli nie zostały podane żadne argumenty i trzeba zaaplikować wartość domyślną.
Coś jeszcze? Fajnie by było, żeby można było sortować nie tylko po wartościach z jednej tablicy… a do tego wypadałoby użyć joins/include. Nic prostszego.
named_scope :joins, lambda { |joins| { :joins => joins } }
# a teraz można tak:
Product.active.joins(:user).order('users.email')
How cool is that?
No i na koniec coś co ostatecznie przekonało mnie, że named_scope jest genialnym wynalazkiem. Jeżeli ktoś robił kiedyś formularze, w których można wybrać kilka opcji i na ich podstawie trzeba zbudować zapytanie, zapewne wie, że jest to nieco upierdliwe. Używając `named_scopes` można to zrobić bardzo łatwo.
Ryan Bates napisał plugin scope_builder, dzięki któremu można to zrobić jeszcze łatwiej. Przypuśćmy, że mamy formę, w której można zaznaczyć kilka pól i po ich wysłaniu należy na ich podstawie pobrać odpowiednie rekordy z bazy.
# przygotowujemy listę parametrów, które użytkownik może ustawić
parameters = [:active, :paid, :recent, :title]
# Tworzymy scope
@products = Product.scope_builder
# teraz można sprawdzić, które pola zostały zazanczone
parameters.each do |param|
@products.send(param) if params[param]
end
# na koniec można na przykład dodać paginację
@products = @products.paginate(:per_page => 10, :page => params[:id])
Dla mnie genialne. :) Można dzięki temu łatwiej tworzyć zaawansowane wyszukiwanie, sortowanie i inne tego typu rzeczy.
Oczywiście najlepiej jest zamknąć taki kawałek kodu jako metodę modelu, zgodnie ze skinny controller, fat model, ale to pozostawię jako ćwiczenie ;-)
Posted in Ruby on Rails, Programowanie | Tags named_scope, rails, ruby | no comments | no trackbacks
Posted by Piotr Sarnacki
Tue, 17 Jun 2008 18:50:00 GMT
Dzisiaj opowiem wam bajeczkę o tym jak mod_passenger zeswatał ze sobą Apache’a i Railsy. :)
Jeżeli ktoś sądzi, że z tego związku nic nie będzie, to rzucam kilka zalet:
- nie trzeba bawić się w konfigurację proxy i martwić o zajęte porty
- nie trzeba monitorować mongreli (ja używałem do tego goda)
- ogólna prostota użycia
- dzięki ruby enterprise można zaoszczędzić 33% pamięci
- upload buffering, czyli nie musimy się martwić o upload dużych plików – pliki są przesyłane do aplikacji railsowej dopiero, gdy zostaną w całości uploadowane na serwer
- fair load balancing – zapytania są wysyłane do procesów, które mają najmniej klientów w kolejce. żeby uzyskać coś takiego używając nginxa trzeba było instalować dodatkowy plugin
- liczba instancji aplikacji nie jest sztywno określone. używając nginxa trzeba było przewidzieć jaka liczba serwerów railsów będzie potrzebna dla danej aplikacji, jeżeli na dany serwis wchodziło mało osób niewykorzystane serwery zjadały pamięć. mod_passenger uruchamia kolejne instancje w miarę potrzeb.
Dodatkowo napisałem apache upload progress module , dzięki czemu z mod_passengerem można sklecić pasek postępu wysyłania plików na serwer (forma dokładnie taka sama jak z Nginx Upload Progress, więc jeżeli ktoś go używał, to nawet nic nie będzie musiał zmieniać w skryptach).
Mod Passenger
Aktualnie najnowszą wersją mod_passengera jest 1.9.1 (RC2), instrukcja instalacji jest na blogu phusion.
Po odpaleniu instalatora użytkownik jest prowadzony za rączkę, więc nikt nie powinien mieć probleów – lubię takie podejście, nie muszę się zastanawiać nad tym co i jak mam zrobić i kopiować i wklejać kolejnych komend.
Mod passenger sprawuje się na serwerze bardzo fajnie, do tej pory nie miałem żadnych problemów. Żeby zrestartować aplikację rails wystarczy utworzyć plik tmp/restart.txt w katalogu aplikacji.
Po instalacji można jeszcze dodać w configu apacha linijkę:
PassengerMaxPoolSize X
Zamiast X wstawiamy maksymalną liczbę instancji aplikacji odpalonych na raz. W dokumentacji twierdzą, że dla VPSa z 256 megabajtami pamięci ram dobrą wartością będzie tutaj 2, a dla serwera dedykowanego z 2GB ramu 30. Większa liczba aplikacji to więcej zajętego ramu, ale też więcej requestów do obsłużenia w danej chwili.
Ruby Enterprise Edition
O ile o mod passengerze jest dużo informacji, to o ruby enterprise edition jeszcze niewiele. Według niektórych nazwa jest nietrafiona i niefajna, niektórym nie podoba się, że phusion chce się na REE wypromować.
Mi to w zasadzie wszystko jedno jak się chłopaki z Phusion promują, kto na tym zarobi i jaka jest nazwa, o ile ta wersja będzie dobrze działała i rzeczywiście zmniejszała zużycie pamięci.
Żeby zainstalować ruby enterprise edition należy ściągnąć paczkę (najnowszą wersję paczki można znaleźć
tutaj):
wget http://rubyforge.org/frs/download.php/38777/ruby-enterprise-1.8.6-20080623.tar.gz
Następnie ją rozpakować:
tar -zxvf ruby-enterprise-1.8.6-20080623.tar.gz
i uruchomić installer:
./ruby-enterprise-1.8.6-20080623/installer
Tyle mówi opis na stronie
REE, ale to jeszcze nie wszystko. Na początek można stworzyć dowiązanie symboliczne dla
REE:
ln -sf /opt/ruby-enterprise-1.8.6-20080623/bin/ruby /usr/bin/ruby-enterprise
Użytkownicy gentoo muszą również skopiować plik auto_gem, który automatycznie jest w tej dystrybucji dodawany do
RUBYOPT:
cp /usr/lib/ruby/site_ruby/auto_gem.rb /opt/ruby-enterprise-1.8.6-20080623/lib/ruby/site_ruby
Po zainstalowaniu rubiego instaluje się też kilka gemów, ale to tylko podstawowe – resztę trzeba zainstalować samemu. Oczywiście można odpalić gem list i instalować kolejne gemy w danych wersjach, ale od czego mamy rubiego. Dosłownie w 2 minuty napisałem prosty skrypcik, który wyciąga nazwy i wersje gemów po czym instaluje je dla Ruby Enterprise Edition:
`gem list`.split("\n").each do |line|
if line =~ /([0-9a-zA-Z_\-]*) \((.*)\)/
gem, versions = $1, $2.split(", ")
versions.each do |version|
puts "Installing #{gem}, version: #{version}"
puts `ruby-enterprise /opt/ruby-enterprise-1.8.6-20080623/bin/gem install #{gem} -y --version '#{version}' 2>&1`
end
end
end
Teraz wystarczy dodać w configu apacha linijkę:
RailsRuby /opt/ruby-enterprise-1.8.6-20080623/bin/ruby
I możemy cieszyć się naszym własnym wypicowanym apachem z mod_passengerem w korporacyjnej wersji ;-)
Posted in Ruby on Rails | Tags apache, enterprise, instalacja, mod_passenger, ruby | no comments | no trackbacks
Posted by Piotr Sarnacki
Sat, 05 Apr 2008 05:31:00 GMT
Jakiś czas temu pojawiły się wzmianki o hotruby (tylko wtedy nie miałem czasu o tym napisać). Krótko mówiąc jest to “implementacja rubiego w javascripcie”. Możemy używać rubiego w przeglądarce i we flashu.
Kod rubiego jest wysyłany do skompilowania przez skrypt cgi, skrypt cgi kompiluje go do bytecodu, po czym javascript wykonuje ów bytecode.
Na stronie HotRuby jest wzmianka o tym, że składnia języka jest w większości zaimplementowana (na pewno nie ma wyjątków), ale jak na razie większość funkcji i bibliotek nie. Na razie nie wiem do czego mogłoby mi się takie połączenie przydać, ale patrząc na dema łatwo można zauważyć, że całkiem fajne rzeczy da się stworzyć z pomocą hotrubiego. Oby tak dalej.
Posted in Inne | Tags hotruby, javascript, ruby | no comments | 1 trackback
Posted by Piotr Sarnacki
Thu, 31 Jan 2008 09:09:00 GMT
Jakiś czas temu pisałem ,że mogą szykować się zmiany w deploymencie railsów. Dzisiaj przeczytałem, że Ezra Zygmuntowicz zatrudnił szóstego programistę, którego zadaniem będzie praca nad mod_rubinius dla serwerów Nginx i Apache. Więcej w podcaście z udziałem Ezry
Mały cytat:
More interesting things from the podcast:
- In like one night Evan Phoenix implemented a multiple Rubinius VM running in single OS process in native threads and passing each other messages like it happens in Erlang). This can lead to a great solution to shared hostings and Ruby deployment problem David Hansson wrote about recently.
- Rubinius may support native code compilation along side with bytecode that Rubinius VM can run. Sounds interesting. Ryan Davis is working on Rubinius at Engine Yard and his Ruby2C experiments may be useful.
Posted in Ruby on Rails | Tags deployment, ezra, mod_rubinius, on, rails, rubinius, ruby, zygmuntowicz | no comments | no trackbacks
Posted by Piotr Sarnacki
Tue, 08 Jan 2008 10:06:00 GMT
Ruby posiada bardzo fajną właściwość nazwaną otwartymi klasami. Znaczy to tyle, że jeżeli nikt nie zamroził danej klasy/metody, można w dowolnym miejscu w kodzie nadpisać ją klasę, metodą, dodać nowe metody. Można tak nadpisać nawet klasy wbudowane w język!
Na przykład taki kod (och jak ja lubię ten przykład):
class Fixnum
alias old_plus +
def +(other)
(self.old_plus other) % 7
end
end
4+4 #=> 1
4+4=1 ? nie tego się spodziewaliśmy. A właściwie nie tego się spodziewali ludzie, którzy nie rozumieją jeszcze powyższego przykładu (celowo dodałem słowo jeszcze, będę na tyle cywilizowanym i miłym człowiekiem, że spróbuję wytłumaczyć o co chodzi).
Liczby całkowite są klasy Fixnum. Powyższy kod modyfikuje tą klasę. Najpierw tworzony jest alias dla metody +, żeby można było jej później używać, a następnie owa metoda zostaje nadpisana w taki sposób, żeby zwracała wynik dodawania modulo7
Do napisania tego artykułu natchnął mnie Daniel Owsiański pisząc o zjawisku roboczo nazwanym version lock-in. Daniel ma oczywiście dużo racji i moja paranoja, o której pisałem u niego w komentarzach jest objawem przewrażliwienia mojej mózgoczaszki w pewnych kwestiach. Są jednak wypadki, w których naprawdę warto zachować zgodność z nowymi wersjami. Pomaga tutaj powyższa właściwość języka Ruby. Pisał o tym kiedyś autor bloga Err the Blog w kontekście rozszerzania możliwości pluginów.
Często w rozmowach o Ruby on Rails na różnych listach dyskusyjnych można usłyszeć, że gdy chcemy coś zmienić w danej metodzie, najlepiej przekopiować kod metody, nadpisać ją, zmienić to co trzeba i voilla. Ale nie tędy droga panie i panowie :)
Wyglądałoby to mniej więcej tak. Chcemy na przykład nadpisać metodę find. Wchodzimy na
Rails API, znajdujemy ActiveRecord::Base#find, wklejamy kod w modelu i zmieniamy:
#w modelu:
def self.find(*args)
#jakiś zmodyfikowany kod finda
end
Jakie minusy ma takie podejście? Gdy mamy zainstalowaną dużą liczbę pluginów nigdy nie wiadomo czy któryś z nich nie nadpisuje już metody find i wtedy nadpisując ją stracimy funkcjonalność dodaną przez plugin. Smuteczek. Poza tym gdy zmieni się kod metody find w samym frameworku również u nas będziemy musieli go zmienić.
Jak to zrobić Ruby Way™? Przypuśćmy, że chcemy się popastwić nad wspomnianą metodą find:
#w modelu:
# metoda find jest metodą klasy
class << self
alias :old_find :find
def find(*args)
args[1] ||= {}
args[1][:conditions] ||= {}
args[1][:conditions] = [args[1][:conditions]] if args[1][:conditions].is_a?(String)
case args[1][:conditions]
when Hash:
args[1][:conditions].merge!(:deleted => false)
when Array:
if args[1][:conditions][0].strip.blank?
args[1][:conditions][0] = "deleted = ?"
else
args[1][:conditions][0] = ["(#{args[1][:conditions][0]})", "deleted = ?"].join(' AND ')
end
args[1][:conditions] << false
end
old_find(*args)
end
end
Powyższy kod dodaje do conditions warunek “deleted = false”, po czym wywołuje metodę find z tak zmodyfikowanymi argumentami. Czasami trzeba jednak namieszać coś w kodzie metody. Można wtedy dodać dodatkowy argument. Następnie dajemy ifa – jeżeli argument zwraca true wykonujemy zmodyfikowany kod, a jeżeli nie, wykonujemy kod oryginalnej metody.
Dzięki takiemu podejściu możemy w miarę łatwo upgradować pluginy i Railsy bez większego stresu :)
Posted in Ruby on Rails | Tags hacking, klasy, otwarte, ruby, rubyonrails, tips | no comments | no trackbacks
Posted by Piotr Sarnacki
Sun, 06 Jan 2008 09:23:00 GMT
Jak donosi Ruby Inside, wypuszczony został ostatnio serwer szybszy od mongrela.
Chodzi o Thin, czyli połączenie EventMachine, Racka (interfejs do komunikacji z serwerem) i Mongrela (a dokładniej jego bibliotek do parsowania). Instalacja i użycie jest równie łatwe co użycie samego mongrela. `gem install thin`, a później w katalogu aplikacji `thin start`. Jedna z moich aplikacji wyciągała na mongrelu 110req/s, a na thinie około 150req/s (ab -c 10 -n 100). Różnica całkiem spora. Warto się przesiąść
Sprawdziłem też ostatnio sphinxa. O sphinxie pisał już Jarosław Zabiełło i bardzo mnie powyższym artykułem zainteresował. Do tej pory używałem ferreta, ale odpalanie nie do końca stabilnych i niezbyt szybkich serwerów dla każdej aplikacji było trochę uciążliwe. Ezra Zygmuntowicz, twórca frameworka Merb wypowiadał się, że miał dość duże kłopoty ze stabilnością ferreta, które skończyły się po przejściu na sphinxa.
Instalacja opisana jest w dokumentacji, więc nie będę powtarzał :) Mogę dodać jedynie, że czasami używając bazy postgresql trzeba ręcznie dodać language poleceniem `createlang plpgsql nazwa_bazy` (chodzi o nowy język proceduralny dla postgresa). Serwer odpalony przez sphinxa jest bardzo wydajny i zajmuje kilkadziesiąt razy mniejsze ilości pamięci operacyjnej. Jedynym minusem jest to, że nie ma niektórych opcji, które posiada ferret – jeżeli ktoś ich potrzebuje proponuję sprawdzenie Solr.
Zapomniałem wspomnieć, że ultrasphinx działa z bazami postgresql >= 8.2
Posted in Ruby on Rails | Tags ferret, mongrel, ruby, rubyonrails, sphinx, thin | no comments | 1 trackback
Posted by Piotr Sarnacki
Sat, 05 Jan 2008 09:40:00 GMT
Wpadłem jakiś czas temu na bardzo fajną aplikację wspomagającą deployment Ruby on Rails. god.rb monitoruje procesy i w razie potrzeby (out of memory, zbyt dużo pożartego cpu, pad serwera) restartuje je. Config, jak i sam god oczywiście, jest napisany w Ruby’im, więc można sobie nieco ułatwić pracę (wspominałem kiedyś, że nigdy nie miałem dość samozaparcia, żeby się nauczyć pisać skrypty powłoki pod linuxem?).
Aby go zainstalować należy jako root (pod ubuntu sudo) wykonać polecenie:
gem install god
Na stronie goda są podane różne configi, ale nie zakładają one scenariusza: “sporo aplikacji, taki sam config, jak to zrobić automatycznie?”. Pomyślałem sobie, że napiszę prosty config, który będzie pobierał listę aplikacji Rails z pliku i dodawał mongrele do monitorowania. Gdy na serwerze siedzi więcej aplikacji głupio byłoby kopiować config dla każdej z nich i ręcznie ustawiać numery portów i katalogi.
Pełny config z komentarzami wyjaśniającymi o co kaman (
wersja tekstowa):
# run with: god -c /path/to/config.god
require 'yaml'
# otwieramy plik ze spisem aplikacji i robimy z nich tablicę
rails_apps = File.open("/etc/god/applications", "r").readlines.collect { |app| app.strip }
for rails_app in rails_apps
# załadujmy ustawienia danej aplikacji
# korzystać będziemy z konfiguracji dla clusterów
config = YAML::load(File.open(File.join(rails_app, "config/mongrel_cluster.yml")))
config["servers"] ||= 1 # domyślnie startujemy jeden serwer
# teraz stawiamy mongrela dla każdego portu
((config["port"].to_i)..(config["port"].to_i+config["servers"].to_i-1)).each do |port|
God.watch do |w|
pid_file = File.join(rails_app, config["pid_file"].gsub(/\.pid$/, ".#{port}.pid"))
w.name = "#{rails_app[/[^\/]*$/]}-mongrel-#{port}"
# dodajemy mongrele danej aplikacji do grupy o nazwie takiej samej jak nazwa katalogu
w.group = "#{rails_app[/[^\/]*$/]}"
w.interval = 30.seconds # default
w.start = "mongrel_rails start -c #{rails_app} -p #{port} \
-P #{pid_file} -e #{config['environment']} -d"
w.stop = "mongrel_rails stop -P #{pid_file}"
w.restart = "mongrel_rails restart -P #{pid_file}"
w.start_grace = 10.seconds
w.restart_grace = 10.seconds
w.pid_file = File.join(pid_file)
w.behavior(:clean_pid_file)
w.start_if do |start|
start.condition(:process_running) do |c|
c.interval = 5.seconds
c.running = false
end
end
w.restart_if do |restart|
restart.condition(:memory_usage) do |c|
c.above = 150.megabytes
c.times = [3, 5] # 3 out of 5 intervals
end
restart.condition(:cpu_usage) do |c|
c.above = 50.percent
c.times = 5
end
end
# lifecycle
w.lifecycle do |on|
on.condition(:flapping) do |c|
c.to_state = [:start, :restart]
c.times = 5
c.within = 5.minute
c.transition = :unmonitored
c.retry_in = 10.minutes
c.retry_times = 5
c.retry_within = 2.hours
end
end
end
end
end
puts "kuniec"
Aplikacje w pliku, który jest odczytywany na początku to ścieżki wpisane w kolejnych liniach. gdy wystartujemy goda poleceniem `god -c /sciezka/do/configu` uruchomią się mongrele dla wszystkich aplikacji. Ważne: powyższy config korzysta z konfiguracji stworzonych dla clusterów. Aby ją stworzyć należy wykonać w katalogu aplikacji:
mongrel_rails cluster::configure -e production -p 8000 -N 2
Powyższa komenda wygeneruje plik `mongrel_cluster.yml` w katalogu config aplikacji Ruby on Rails. Zostanie ustawione środowisko production, port 8000 i 2 serwery (na portach 8000 i 8001).
Żeby szczęście było pełne i niczym niezmącone można jeszcze pokusić się o napisanie skryptu startowego init.d. Oczywiście w ruby’im. A jakie to proste pokazywał kiedyś Jarek Zebiełło.
W razie wątpliwości pytajcie o szczegóły w komentarzach.
Posted in Ruby on Rails | Tags deployment, god, monitoring, ruby, rubyonrails | no comments | no trackbacks
Posted by Piotr Sarnacki
Sat, 01 Sep 2007 12:40:00 GMT
Zdarza mi się ostatnimi czasy przeczytać komentarz, że książki programistyczne są już niepotrzebne, bo i tak wszystko jest w necie. A nawet, cytuję: “w książkach to jest naćkane, jak chcesz konkretów to nie kupuj książek”.
Całkiem głupie to ;-)
Dla mnie książki programistyczne są niezastąpione. I najchętniej kupiłbym sobie połowę pozycji dostępnych na rynku. Po czym prawdopodobnie 1/3 bym nie przeczytał, ale to już inna kwestia. Ogólnie rzecz biorąc większości rzeczy zawartych w książkach można pewnie nauczyć się samemu, z lekką pomocą wujka Googla. Ale o ile przyjemniej przeczytać jak to robią profesjonaliści (o ile nikt nie da się nabrać niektórym profesjonalistom inaczej)
Po takim wstępie nie mogę się nie pochwalić, że jakiś czas temu kupiłem wspólnie z ludźmi od nowego projektu (na razie nie zapeszam. za jakiś czas pewnie coś napiszę) parę bardzo fajnych książek.
- Don’t make me think – genialna książka o usability. Bardzo krótka i treściwa. Autor starał się napisać książkę, którą będzie się dało przeczytać w parę godzin. I muszę przyznać, że bardzo dobrze mu to wyszło. Idealna dla ludzi, którzy nie są specami w tej kwestii, a chcą poprawić użyteczność swoich stron.
- Agile development with Rails – Pozycja obowiązkowa dla uczących się RoR. Co prawda teraz niewiele mi się przyda, ale czułbym się źle jakby jej nie kupił. Korzystałem dość sporo z ebooka z amule’a. No i przyda się bliźniemu, który właśnie zaczyna się uczyć RoR.
- Ruby for Rails – jeszcze nie przeczytałem, ale po przekartkowaniu zapowiada się bardzo ciekawie. Napiszę coś więcej jak wreszcie się za nią wezmę ;-)
- The Ruby Way – bardzo dobra książka. Jestem właśnie w trakcie czytania. Podaje bardzo dużo rozwiązań często spotykanych problemów zgddnie z “ruby way”. Ponad 600 stron esencji programowania ;-)
- JavaScript: The Definitive Guide – niektórzy narzekają, że książka bardziej zbliżona jest do dokumentacji niż do książki “do nauki”. Dla mnie to plus. Javascripta znam całkiem dobrze. Pewnie dzięki temu, że w poprzednim stuleciu, kiedy o bibliotekach takich jak jQuery nikt nie słyszał, bawiłem się w pisanie różnego rodzaju latających warstw, wysuwanych menu i innych arkanoidów (nawet fajnie się grało ;-). W tym momencie potrzebuję właśnie takiej dokumentacji, do której mogę spojrzeć podczas pisania i zobaczyć jakie metody udostępnia dana klasa, albo jak powinien się zachowywać jakiś kod. Jeżeli ktoś chce pisać coś więcej niż efekty z użyciem jQuery (czy innego script.aculo.us), to naprawdę zachęcam do kupna.
Podejrzewam, że gdyby początkujący programiści więcej inwestowali w wiedzę książkową to byłoby dużo mniej partactwa. Szczególnie, że książki związane z Rubim i RoR oprócz nauki programowania próbują od początku wpajać dobre praktyki programistyczne. I koniecznie trzeba przeczytać Pragmatycznego programistę :)
Posted in Programowanie | Tags javascript, książki, programowanie, rails, ruby, rubyonrails | no comments