Named scope

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 ,  | Tags , ,  | 6 comments | no trackbacks

Po dłuższej przerwie

Posted by Piotr Sarnacki Tue, 30 Sep 2008 14:38:00 GMT

Przez jakiś czas nie pisałem nic na blogu. Długo zbierałem się do napisania czegokolwiek, od dawien dawna miałem przetłumaczyć wpisy na moim angielskojęzycznym blogusiu. Tak bardzo mi się nie chciało, że jak tylko siadałem do kompa z zamiarem przetłumaczenia czy napisania czegoś podobnego coś mnie odrzucało.

Dlatego, żeby więcej się nie męczyć podsumuję ten temat i podam linki – większość ludzi, którzy tutaj trafią i tak na pewno zna angielski.

Napisałem apache upload progress module do apacha. Moduł, dzięki któremu można pobrać dane o wysyłanych plikach. Format odpowiedzi oparłem o moduły tego typu dla nginxa i lighttpd dlatego jeżeli wcześniej ktoś używał któregoś z nich, przesiadka będzie całkowicie bezbolesna. Sam używałem wcześniej nginx upload progress (głównie dlatego napisałem moduł do apacha) i po przerzuceniu aplikacji na apacha z nowym modułem nie trzea było zmieniać ani jednej linijki. Na angielskim blogu zamieściłem opis instalacji i konfiguracji modułu.

Żeby można było w miarę łatwo używać modułu napisałem także pluginy do “prototype’a”http://github.com/drogus/prototype-upload-progress/tree/master i jquery, które obsługują pasek postępu. Przykłady są w repozytoriach, umieściłem je także na serwerze dla obczajenia w akcji. Popełniłem także tekst o tym jak hackowałem plugin, żeby działał w safari – safari w tym momencie nie było wcale lepsze od IE… nawet powiedziałbym, że gorsze. Swoją drogą plugin nie działa w najnowszej operze (działał w 9.5 zdaje się), więc jeżeli ktoś ma chwile czasu i mógłby sprawdzić dlaczego i co można zrobić, żeby działał, to może wnieść swój wkład w rozwój (dodam, że nie jest to ta sama kwestia co dla safari, bo to już sprawdziłem) – nie sądzę żebym miał teraz czas sam na tym siedzieć.

Dodatkowo napisałem jeszcze tekst o tym jak można fajnie uatrakcyjnić aplikację używając jQuery. Część pierwsza. Część druga będzie jak będę miał więcej czasu, czyli pewnie niezbyt szybko.

Pozdrowienia ze słonecznego Wrocławia, gdzie mam zamiar mieszkać przez najbliższy rok (miła odmiana od Pruszkowa :).

Posted in , ,  | Tags , , , ,  | no comments | no trackbacks

Mod passenger i Ruby Enterprise Edition

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  | Tags , , , ,  | 11 comments | no trackbacks

mod_rubinius

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  | Tags , , , , , , ,  | 2 comments | 1 trackback

Rails 2.0 i js.erb

Posted by Piotr Sarnacki Tue, 15 Jan 2008 20:36:00 GMT

Niedawno pisałem o nieinwazyjnym kodzie javascript w railsach. Wspomniałem tam o pluginie MinusMor, który dodaje pliki ejs. Czyli kod javascript parsowany erbem. Zainstalowałem plugin w mojej aplikacji przeniesionej na rails 2.0. Kod pluginu nie działa… zacząłem przeglądać kod railsów, żeby poprawić minusmor (w internecie nie mogłem znaleźć poprawki). Po chwili przemyślenia sprawdziłem co dzieje się gdy wyrzucę plik rjs. Komunikat o błędzie:


Missing template pages/index.js.erb in view path

Git! W railsach 2.0 możemy przecież wskazać jakiego parsera mają użyć.

Tworzymy plik index.js.erb. I jeszcze mała zmiana, ustawiamy brak layoutu, żeby renderował się sam template:

respond_to do |format|
  render.html
  render.js { render :layout => false }
end

I to wszystko! Po prostu działa.

Nawet lepiej. Można zdefiniować layout, na przykład:


render.js { render :layout => 'jquery' }
a w nim można na przykład napisać:

(function ($) {
  <%= yield %>
})(jQuery);

I można używać w template’ach js.erb $ pomimo tego, że wcześniej użyło się jQuery.noConflict.

Przeszukałem później jeszcze net w poszukiwaniu informacji na ten temat i znalazłem artykuł na mad.ly o dodaniu helperów z MinusMor

Posted in  | Tags , ,  | no comments | no trackbacks

Przyjazne adresy w Ruby on Rails

Posted by Piotr Sarnacki Sat, 12 Jan 2008 10:47:00 GMT

W dzisiejszych czasach, kiedy SEO dla niektórych jest ważniejsze od treści przyjazne adresy można zobaczyć na większości stron. Dlaczego nie wrzucić ich do naszej aplikacji? Nawet jeżeli komuś nie zależy na SEO, przyjazne adresy są… przyjazne! Użytkownik dostający linka http://foo.com/article/kolejny-artykul-o-naszej-klasie będzie miał szansę zastanowić się sekundę i nie kliknąć (nie to żebym miał coś do naszej klasy, ale boję się ostatnio lodówkę otworzyć, jeszcze szynka zacznie nawijać kogo dodała do znajomych). Same korzyści!

Dodanie przyjaznych adresów jest w aplikacji railsowej bardzo proste jeżeli trzymamy się kilku zasad. Jak to działa? ActiveRecord::Base, po którym dziedziczą udostępnia metodę to_param, która jest wykorzystywana przy generowaniu adresów.

W praktyce wygląda to tak, że jeżeli napiszemy:
link_to @article.name, articles_path(@article)
#albo
link_to @article.name, :controller => "articles", :action => "show", :id => @article

wywołana zostanie metoda to_param i @article zostanie zamieniony na jego id. Można to wykorzysta do naszych niecnych celów.

Można nadpisać to_param
  def to_param
    "#{id}-#{name.gsub(/[^a-z0-9]+/i, '-')}" 
  end

Od teraz zamiast id będzie generował się string zawierający name, na przykład: “11-tytuł-artykułu”. Hamerykanie mają mniejszy problem, bo nie mają znaków diakrytycznych i mogą to tak zostawić. gsub zamieni wszystkie znaki nie wchodzące w skład alfabetu na myślnik. U nas jest gorzej, bo nie chcemy mieć adresu: “11-tytu-artyku-u”. I weź teraz zgaduj gdzie dorzucić ogonki. Z pomocą przychodzi Obie Fernandez, który napisał najfajniejszą jaką do tej pory widziałem metodą zamieniającą znaki diakrytyczne na odpowiadające im litery alfabetu Wrzuciłem na serwer wersję z polskimi znakami diakrytycznymi. Taki plik wystarczy wrzucić do folderu initializers dla Railsów 2.0.x, albo do katalogu lib dla 1.2.x (w tym wypadku trzeba też w environment.rb dodać linijkę require ‘ascii’). Zrobiłem jeszcze jedną modyfikację – to_url_format powinien moim zdaniem w miejsce spacji i innych znaków wrzucać myślniki. Lepiej wygląda “tytul-artykulu” niż “tytulartykulu”.

Dzięki temu String udostępnia 2 nowe metody: `to_url_format` i `to_ascii`:
"zażółć gęślą jaźń".to_ascii #=> "zazolc gesla jazn" 

"zażółć gęślą jaźń".to_url_format #=> "zazolc-gesla-jazn" 
Teraz wystarczy zmodyfikować lekko to_param:
  def to_param
    "#{id}-#{name.to_url_format}" 
  end

Dzięki temu urle będą miały upragnioną formę: “12-tytul-artykulu”.

Tylko po co to id na początku? Dzięki temu obędzie się bez żadnych zmian w kontrolerach. Taki string zostanie przed wrzuceniem do bazy automatycznie skonwertowany na liczbę całkowitą. Czyli kolejna rzecz, która “po prostu działa” automagicznie. Jeżeli id będzie potrzebne w jakimś innym miejscu, w którym nie nastąpi konwersja można to zrobić ręcznie: params[:id].to_i, bo:
"11-jakis-napis".to_i #=> 11

Jaki jest minus takiej metody? Adresy mają w sobie id i tylko na podstawie tego id jest pobierany artykuł, więc czy wpiszemy /articles/11-tytul-artykulu, czy /articles/11-tralalala pobierze się ten sam artykuł.

Żeby temu zapobiec najlepiej zrobić dodatkową kolumnę, na przykład “permalink” i wpisywać do niej przekształcony adres. Np. article.permalink = article.name.to_url_format . I zamiast po id szukać po kolumnie permalink: Article.find_by_permalink(params[:id]). Oczywiście z metody to_param też trzeba usunąć id :)

Nie każdemu to drugie podejście będzie potrzebne. Dla mnie wersja z id jest o tyle lepsza, że jak nawet ktoś nie skopiuje całego adresu, albo pomyli się przy wpisywaniu (tak, czasami zdarza się, że ktoś dyktuje jakiś adres), to jest duża szansa, że dotrze na dobrą stronę. Poza tym istnieje wtedy możliwość wklejenia adresu bez dalszej części, która może być dość długa – może nie trzeba będzie korzystać z serwisów typu tinyurl.

Posted in  | Tags , , , , , ,  | 4 comments | no trackbacks

Zmiany w deploymencie Ruby on Rails na horyzoncie?

Posted by Piotr Sarnacki Thu, 10 Jan 2008 18:16:00 GMT

Na blogu dreamhostu narzekają na bóle hostowania railsów. DHH (himself™) odpowiada, że developerzy z Railsów nie mają potrzeby zmian idących w kierunku ułatwienie deployment na dzielonych hostingach, ale chętnie pomogą jeżeli dostaną jakieś propozycje współpracy nad tym problemem. Na RubyInside jest artykuł, w którym autor zastanawia się co można poprawić.

Z jednej strony rozumiem podejście DHH: “nie pracujemy na dzielonych hostingach, więc nigdy nie mieliśmy potrzeby poprawy czegokolwiek”, a z drugiej strony od razu przychodzi na myśl mongrel napisany przez Zeda Shaw. W czasach “pre-mongrel” trzeba było w bólu i cierpieniach próbować zmusić lighttpd (albo apacha, wersja dla prawdziwych masochistów) do hostowania railsów via FastCGI. Gdyby nie Zed prawdopodobnie długo jeszcze przyszłoby męczyć się z takimi configami, nie sądzę, żeby DHH myślał nad jakimiś poprawkami w tym kontekście.

A wszystko zaczęło się od sławnego już artykułu Rails is a ghetto. Czyżby Zed Shaw kolejny raz, tym razem może w całkiem niezamierzony sposób, przyczynił się do poprawy sytuacji?

Mam nadzieję, że prace nad ułatwieniem deploymentu railsów niedługo się zaczną. Oznaczałoby to też poprawę uruchamiania skryptów rubiego w taki sam sposób jak skryptów php – wrzucam na serwer, wpisuję adres, dostaję wynik.

Poczekamy, zobaczymy, może coś się ruszy :)

Posted in  | Tags , , , ,  | 3 comments | 2 trackbacks

Otwarte klasy w Rubim i ich praktyczne wykorzystanie

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  | Tags , , , , ,  | 4 comments | no trackbacks

Thin. Nowy lepszy mongrel. Sphinx. Nowy lepszy ferret.

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  | Tags , , , , ,  | no comments | no trackbacks

God.rb

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  | Tags , , , ,  | no comments | 1 trackback

Older posts: 1 2


Clicky Web Analytics