Co nowego w Rails3?

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

Refaktoryzacja Railsów

Posted by Piotr Sarnacki Sat, 03 Jan 2009 20:45:00 GMT

Niestety nie będzie to wpis o tym jak refaktoryzować swoją aplikację, ale raczej post o refaktoryzacji samego frameworka w ramach przygotowań do Rails 3.

Railsy pożarły mózg Merba i posiądą jego tajemne moce (zachęcam do wysłuchania tego podcastu, chłopaki od railsenvy genialnie mówią o tym co się dzieje w świecie railsów) – o tym już każdy na pewno wie. Czy to dobrze? Oczywiście, że tak :)

Jeżeli ktoś dalej ma problemy z tą decyzją (podejrzewam, że może tak być w wypadku fanów merba), polecam prezentacją Living with legacy software wygłoszoną przez Davida Heinemeiera Hanssona – nie jest w żaden sposób bezpośrednio związana z połączeniem, ale zmienia podejście do aplikacji i ich kodu.

Ale nie o to, nie o to, nie o to

Yehuda Katz ostro wziął się do pracy jeszcze podczas świąt i co jakiś czas wrzuca na bloga informacje o tym jak refaktoryzuje i jakie ma plany wobec Railsów. Informacje traktujące o projektowaniu, refaktoryzacji i architekturze ciężko jest znaleźć w sieci, więc gorąco polecam śledzenie tego co pisze Yehuda – do tej pory można przeczytać między innymi o enkapsulacji ActionView i ActionController czy o optymalizacji respond_to. Dodajcie sobie tego bloga do czytnika, naprawdę warto.

Co najlepsze, wiele z tych zmian dotyka tylko wewnętrznej implementacji railsów, więc można liczyć, że będą one dostępne dla ruby on rails 2.3.0.

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

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.

Wrzuciłem nową wersję skryptu działającą z nowszymi wersjami Ruby on Rails

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

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 | no trackbacks

Nieinwazyjny javascript razem z Ruby on Rails

Posted by Piotr Sarnacki Tue, 25 Dec 2007 20:15:00 GMT

Jakiś czas temu Riddle napisał o tym dlaczego nie można zakładać, że ktoś ma włączony javascript. Temat był już wcześniej wiele razy poruszany, mi bardzo podobał się artykuł Chrisa Heilmanna The seven rules of Unobtrusive JavaScript. Zachęcam do zapoznania się z tymi dwoma tekstami – pomogą zrozumieć dlaczego “tracić” czas na rozwiązywanie problemów, o których napiszę poniżej. Streszczając krótko artykuły mogę napisać, że jeżeli to możliwe, należy pisać aplikację tak, żeby działała bez włączonego javascriptu. Skrypty mogą nie działać z kilku powodów:

  • użytkownik ma wyłączony javascript w przeglądarce
  • wystąpi błąd w kodzie (nie możemy sprawdzić kodu na wszystkich urządzeniach)
  • strona nie załaduje się do końca
  • boty wyszukiwarek nie używają javascriptu, więc w skrajnym wypadku możemy uniemożliwić zindeksowanie naszej strony

Jak to wygląda w Railsach?

Railsy są wyposażone w zestaw helperów generujących różnego rodzaju kawałki kodu javascript. Pomysł jest z pozoru bardzo fajny. Początkujący mogą szybko zacząć używać javascriptu razem z dobrodziejstwami, które daje nam Ajax bez znajomości samego języka. Jest jednak sporo minusów używania helperów:

  • generują one dodatkowy niepotrzebny kod javascript. jeżeli na stronie mamy kilkadziesiąt linków, a każdy z nich ma wklejony kod `onclick=”new Ajax.Request(’/controller/action?n=33’, {asynchronous:true, evalScripts:true, onComplete:function(request){undoRequestCompleted(request)}}); return false;”` strona będzie ważyć dużo więcej.
  • javascript w nich użyty jest “inwazyjny”. Jeżeli javascript nie będzie działał, to element wygenerowany w taki sposób również nie zadziała1
  • ciężko jest się przy nich trzymać zasady DRY. Mając 5 linków wygenerowanych metodą link_to_remote z takimi samymi opcjami, za każdym razem gdy coś musimy zmienić, zmieniamy to w 5 miejscach. Powinno się oczywiście napisać helpera, który wygeneruje link z danymi opcjami. Tylko chyba nie tędy droga – w dalszej części artykułu postaram się pokazać dlaczego nieinwazyjny javascript jest lepszy w tego typu zadaniach.
  • z mojego doświadczenia wynika, że bardzo często, gdy ilość kodu się powiększa i javascript generowany przez railsy miesza się z tym z plików js można łatwo się pogubić. Tyczy się to także różnego rodzaju api – na przykład google maps. Najłatwiej działać, gdy javascript jest odseparowany od kodu railsów
  • jesteśmy związani z jedną biblioteką (w tym wypadku prototype+script.aculo.us) – jeżeli chcemy zamienić ją na coś innego (ja ostatnio przesiadłem się na jQuery, zastanawiałem się też nad YUI) helpery przestaną działać – można je oczywiście przepisać, ale komu by się chciało. DHH nie zamierza nic w tej kwestii zmieniać, więc na zmianę tego w Railsach nie ma co czekać.

Jakie są minusy? Trzeba lepiej poznać javascript (właściwie dla mnie to nie jest minus, ale dla niektórych być może tak). Nie jest to jednak przeszkoda nie do pokonania dla początkujących. Javascript, który jest potrzebny do zadań możliwych do wykonania z użyciem samych helperów nie jest z reguły przesadnie trudny do nauczenia. Ratunkiem dla osób, które nadal chcą korzystać z helperów jest plugin UJS. Jeżeli bardzo nie chcesz pisać wszystkiego w czystym javascripcie, to jest to bardzo fajne połączenie prostoty helperów i zalet nieinwazyjnego javascriptu. Jest ona jednak pisana dla Prototype’a, więc tak jak w ostatnim punkcie z powyższej listy można o niej zapomnieć, jeżeli używana jest jakakolwiek inna biblioteka.

W komentarzach apohllo zauważył, że plugin UJS nie jest już rozwijany. Używacie na własną odpowiedzialność. :)

Przejdę do przykładów, bo przecież nie samą teorią człowiek żyje.

Przerzuciłem się ostatnio na jQuery i chyba przy niej zostanę. Rozumiem jednak, że większość użytkowników railsów jest związana z Prototype’em, więc kod będę podawał w dwóch wersjach, dla Prototype’a i jQuery.

Na początek wprowadzenie. Co zrobić, żeby wyrzucić z htmla (i railsów) wstawki Javascript? Wszystko wstawiamy do aplikacji używając zdarzeń. Przypuśćmy, że mamy linka o id=”someLink”. Zamiast dopisania onclick:


    <a href="#" onclick="alert('Klik!'); return false;">Link</a>
  
należy użyć:

Prototype:

    Event.observe($('someLink'), 'click', function(event) {
      alert('Klik!');
      Event.stop(event);
    }
  
jQuery:

    $('#someLink').click(function (){
      alert('Klik!');
      return false;
    });
  

Oba przykłady dodają zdarzenie uaktywniane kliknięciem w linka. Ostatnia linijka w obu funkcjach, które są wykonywane po kliknięciu (nazywane są z reguły handlerami) jest wstawiona po to, żeby kliknięcie linka nie przeładowało strony.

Kod taki w aplikacji Rails można wrzucić do pliku application.js, lub jakiegoś specyficznego pliku js ładowanego na danej stronie. Należy też go załadować dopiero po wczytaniu się całego dokumentu. Normalnie coś takiego uzyskiwało się wpisując w body: `onload=”jakasFunkcjaJavascript();”`, ale takie dodawanie jest passe, więc:

Prototype:

    Event.observe(window, 'load', function() {
      //kod który wykona się po załadowaniu strony
    }

    // lub zdefiniowana wcześniej funkcja, która wykona się po załadowaniu strony
    Event.observe(window, 'load', jakasFunkcjaJavascript());
  
jQuery:

    $(function() {
      //kod który wykona się po załadowaniu strony
    });

    // lub zdefiniowana wcześniej funkcja, która wykona się po załadowaniu strony
    $(jakasFunkcjaJavascript);
    //powyższe przykłady, to skrócone wersje document.ready:
    $(document).ready(function () {});
  

Dzięki tym konstrukcjom mamy pewność, że kod wykona się dopiero gdy załaduje się cały dokument, a nie w momencie, gdy dołączony jest plik js.

Teraz przykład prostego zapytania ajax (przykład z dokumentacji railsów):

  link_to_remote 'hello', :url => { :action => "action" }, 
    404 => "alert('Not found...? Wrong URL...?')",
    :failure => "alert('HTTP Error ' + request.status + '!')" 
  # Wygeneruje: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
  #            on404:function(request){alert('Not found...? Wrong URL...?')},
  #            onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
Jak widać powyżej wygenerowanego kodu jest całkiem sporo. Jeżeli będzie trzeba wstawić taki link w paru miejscach dobrze by było napisać swojego własnego helpera, który automatycznie będzie wklejał komunikaty o błędach. Jak można to zrobić lepiej? Na początek wystarczy stworzyć zwykłego linka z jakąś klasą, lub id:

  link_to 'hello', { :action => 'action' }, :class => 'ajax'
Teraz trzeba użyć trochę javascriptu :

        $$('a.ajax').each(function (element) {
          Event.observe(element, 'click', function(event) {
            new Ajax.Request(this.readAttribute('href'), {asynchronous:true, evalScripts:true, 
              on404:function(request){alert('Not found...? Wrong URL...?')}, 
              onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); 
            Event.stop(event);
          });
        });

Na początku pobieramy wszystkie linki z klasą ajax i dla każdego z nich wywołujemy funkcję `Event.observe(element, ‘click’....`. Dalszy kod wykona się więc po kliknięciu w danego linka. W tym wypadku wykonujemy zapytanie ajaxowe (`new Ajax.Request`). Pierwszy argument to atrybut href linka (uwaga, kod ten nie zadziała w starszych wersjach prototype’a, które niepoprawnie obsługiwały this w tego typu funkcji). Reszta kodu to standardowe opcje, po więcej odsyłam do dokumentacji Prototype.

A w jQuery wyglądać to będzie tak:

    $('a.ajax').click(function (){
      $.ajax({
        url: this.href,
        dataType: "script",
        beforeSend: function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");},
        error: function(){
          alert( "Error loading page");
        }
      });     
      return false;
    });

Kod zasadniczo robi to samo, co poprzedni przykład. Można przy okazji porównać prostotę jQuery i porównać ją z Prototype’em (ostatnio dużo się w tej bibliotece pozmieniało, a ja nie jestem na bieżąco, więc jeżeli ktoś zna lepszy sposób na napisanie czegoś takiego, to proszę o komentarz). Skomentuję tylko atrybuty dataType i beforeSend w funkcji ajax(). Ustawiając je w taki sposób przekazujemy serwerowi, że chcemy dostać odpowiedź jako skrypt i akceptujemy typ MIME “text/javascript”. Należy te 2 rzeczy dodać, ponieważ inaczej nie będzie renderować się plik RJS. Więcej na ten temat w artykule jQuery Ajax + Rails

Rozwiązanie proste i efektywne. Żeby link wykonał javascript wystarczy dodać do niego klasę ajax. Jeżeli strona i kody javascript nie wczytają się, link dalej będzie działał poprawnie. Przy założeniu, że poprawnie obsłużymy wszystko w kontrolerze. Służy do tego metoda `respond_to`


  respond_to do |format|
    format.js # jeżeli to zapytanie wykonane ajaxem uruchomi się plik RJS
    format.html # w przeciwnym wypadku wyrenderowany zostanie template rhtml
  end

Więcej o takim sposobie renderowania templatów pisał na przykład Jamis Buck.

Można też w podobny sposób zamienić zwykłą formę na taką wysyłaną ajaxem:

Prototype:

       $$('form.ajax').each(function (element) {
          Event.observe(element, 'submit', function(event) {
            new Ajax.Request(this.readAttribute('action'), {
              parameters: Form.serialize(this),
              asynchronous:true, 
              evalScripts:true
              }); 
            Event.stop(event);
          });
        });  
  

Powyższy kod jest bardzo podobny do poprzedniego przykładu. Różnica polega na tym, że zdarzeniem nie jest ‘click’ tylko ‘submit’ i jako parametry podajmy wynik funkcji `Form.serialize(this)` – zbiera ona wartości pól i zwraca string typu: “pole1=wartosc1&pole2=wartosc2”

jQuery:

    $("form.ajax").ajaxForm({
      dataType: 'script',
      beforeSend: function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");},
      resetForm: true
    });
  

W jQuery najłatwiej skorzystać z pluginu jQuery Form – załatwia on za nas wszelkie formalności ;-)

W ten sposób zmiana jakiegoś linka lub formy na jego ajaxową formę to kwestia dodania jednej klasy. Można oczywiście napisac wiele takich funkcji dla różnych przypadków, dowiązanych do tagów z innymi klasami, lub z konkretnym id.

Na koniec krótkie podsumowanie.

Kod javascript dodajemy do aplikacji tak, żeby nie zablokować dostępu w wypadku braku jego wykonania. Zapomnieć można o wszelakich “onclick” i innych tego typu sprawach. Wszystko powinno być dołączone jako zdarzenia. Dzięki temu zmniejsza i upraszcza się kod railsów i ten przez nie generowany.

Warto obejrzeć również plugin MinusMOR, który zmienia trochę podejście do javascriptu. Zamiast plików rjs, w których używamy rubiego zamienianego później na javascript, mamy pliki ejs, w których wpisujemy kod javascript z możliwością wstawiania kodu rubiego. Tak samo jak w rhtmlu poprzez <% %>.

W komentarzach apohllo zauważył, że plugin MinusMOR nie jest już rozwijany. Trzeba o tym pamiętać zaczynając go używać. Z drugiej strony widziałem kod pluginu i rejestruje on tylko nowe rozszerzenie “ejs”. Na początku szukane będą pliki z tym rozszerzeniem, a jeżeli ich nie będzie Railsy wyrenderują RJS. W każdym razie zaczynacie używać na własną odpowiedzialność. :)

Zapraszam do komentowania – prosiłbym o opinie dotyczące tego typu artykułów. Czy są zrozumiałe? Czy przydają się wam? Konstruktywna krytyka mile widziana. :)


  1. W tym miejscu trzeba zaznaczyć, że jest możliwość wygenerowania linku, czy formy, która będzie działała przy wyłączonym javascripcie, ale niewiele osób o tym wie i z tego korzysta. I trzeba dopisać 2 url, który z reguły jest taki sam – łamana jest zasada DRY

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

Książki

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