Haml - następna generacja szablonów

Opublikowane przez Jarosław Zabiełło Sat, 27 Jan 2007 03:33:00 GMT

W związku z przestawieniem się całkowicie na najnowszą wersję Railsów (1.2.1), postanowiłem przy zrobić także upgrade oprogramowania do mego bloga (który w wersji jaką miałem nie współpracował z RoR 1.2.1). Na szczęście, dzięki railsowym migracjom proces aktualizacji skryptów jak i struktury baz przebiegł bez problemów i niedestrukcyjnie dla danych w bazie. Typo”:http://trac.typosphere.org/ zawsze był kodem trochę awangardowym, wprowadzającym zaawansowane mechanizmy Rubiego i najnowsze pomysły Railsów. Przekonałem się o tym podczas próby modyfikacji paru szablonów. Typo zamiast dotychczasowego formatu ERb (*.rhtml) Typo używa już szablony nowej generacji – Haml. Chcąc niechcąc, musiałem przejść szybki kurs posługiwania się nimi aby zmodyfikować szablony w swoim blogu.

Czytaj dalej...

Posted in  | Tagi , , ,  | 13 comments

Unikanie niebezpiecznych danych w szablonach RHTML

Opublikowane przez Jarosław Zabiełło Mon, 25 Dec 2006 00:14:00 GMT

W wypadku treści wyświetlanych w szablonach najlepiej zachować zasadę ograniczonego zaufania. Np. jeśli wyświetlamy zawartość komentarzy które ktoś wysłał z formularza, to przy odrobinie złośliwości i/lub głupoty taki użyszkodnik może nam wysłać kod HTML lub JavaScript, który popsuje spójność naszej strony. Generalnie istnieje kilka metod aby się przed tym zabezpieczyć.

  • Wszelkie treści podejrzane o możliwość wysłania kodu HTML wyświetlaj przez funkcję filtrującą html_escape() (lub w skrócie: h) Czyli zamiast <= jakies_dane %> należy używać <=h jakies_dane %> i dobrze aby ten zwyczaj wszedł nam w krew. Dzięki temu helperowi wszelkie dane zawierające znaczniki zostaną wymienione na encje HTML (np. zamiast < będzie &lt; co w efekcie uniemożliwi interpretację takich znaków jako tagów HTML)]
  • W wypadku kiedy potrzebujemy kodu HTML, ale nie chcemy aby zawierał ukryte wstawki w języku JavaScript, dane filtrujemy przez funkcję sanitize(). Wszystkie akcje onXXX oraz linki zaczynające się od javascript: powinny zostać usunięte.
  • W wypadku kiedy chcemy udostępnić użyszkodnikom możliwość wprowadzania treści z możliwością formatowania tekstu, zamiast HTML można udostępnić im możliwość wprowadzania tekstu w formacie Markdown (BlueCloth) lub Textile (RedCloth). To specjalny, uproszczony sposób formatowania tekstu, który jest zamieniany na bezpieczny i dobrze sformułowany kod HTML. Rails posiada wbudowane helpery do ich obsługi (są zdefiniowane w module ActionView::Helpers::TextHelper)
  • Ostatecznie można wyciąć wszystkie znaczniki HTML za pomocą helpera strip_tags().

Aby Rails mógł używać helpery textilize() i markdown() trzeba je doinstalować

gem install RedCloth
gem install BlueCloth

Następnie w pliku config/environment.rb dodać kod:

  require_gem 'RedCloth' 
  require_gem 'BlueCloth'

Przykład użycia:

<%= textilize '"Polskie forum":http://forum.rubyonrails.pl dla RoR.' %>
<%= markdown 'Strona Rubiego [po polsku](http://ruby-lang.org/pl).' %>

Wygenerowany kod HTML:

<p><a href="http://forum.rubyonrails.pl">Polskie forum</a> dla RoR.</p> 
<p>Strona Rubiego <a href="http://ruby-lang.org/pl">po polsku</a>.</p>

Posted in  | Tagi  | 1 comment

Własne konfiguracje w Rails

Opublikowane przez Jarosław Zabiełło Sun, 24 Dec 2006 01:51:00 GMT

W Rails domyślnym miejscem do ustawienia swoich dodatkowych konfiguracji jest plik conf/environment.rb. Jednak wygodniejszym sposobem jest użycie formatu YAML. Np. załóżmy, że pliku config/defaults.yml mamy następujące ustawienia:

default: &defaults
  paging: 10

development:
  show_debugs: true
  paging: 20
  <<: *defaults

production:
  show_debugs: false
  <<: *defaults

Wykorzystałem tu zasadę DRY (unikania powtarzania kodu) stąd sekcja ‘default’ która zawiera wspólne ustawienia dla Railsów pracujących zarówno w trybie roboczym (developerskim) jak i produkcyjnym. Aby ten plik był wciągany i to zgodnie z bieżącym trybem pracy Railsów, do pliku config/environment.rb należy dodać:

require 'ostruct'
yml = YAML.load_file RAILS_ROOT + '/config/defaults.yml'
$defaults = OpenStruct.new(yml).send RAILS_ENV

Do naszej aplikacji RoR zostanie dodana zmienna globalna $defaults, W wypadku pracy w trybie produkcyjnym, zmienna $defaults zawiera hasz z wartościami:

{"paging"=>10, "show_debugs"=>false}

Zamiast użycia zmiennej globalnej, można użyć

::AppConfig = OpenStruct.new(yml).send RAILS_ENV

dzięki temu mamy dostęp do poszczególnych parametrów za pomocą notacji kropkowej:

puts AppConfig.paging # => 10

Posted in  | Tagi  | brak comments

Skype3 i polski czat dla Railsów, Django i Pylonsa

Opublikowane przez Jarosław Zabiełło Thu, 14 Dec 2006 23:48:00 GMT

Nowy Skype 3 wprowadza małą rewolucję w stos. do poprzedniej wersji. Można nie tylko rozmawiać, ale pograć w szachy, kółko i krzyżyk i inne gry (są b. ładnie zrobione we Flashu 9). Można nagrywać rozmowy na dysk. Można tworzyć publiczne czaty. I właśnie w tej sprawie piszę ten tekst bo stworzyłem polski czat dla miłośników frameworków Ruby on Rails, Django i Pylons. Wygodniej jest czasem skonsultować coś w czasie rzeczywistym niż na grupie czy forum dyskusyjnym.

Posted in , , , , ,  | Tagi , ,  | 17 comments

Ruby on Rails w W-wie

Opublikowane przez Jarosław Zabiełło Fri, 01 Dec 2006 16:17:00 GMT

11 grudnia w Warszawie odbędzie się kolejne spotkanie dla osób zainteresowanych Ruby on Rails, rozwojem internetu i e-biznesem. Szczegóły tutaj.

Posted in  | Tagi ,  | brak comments

Agile Web Development with Rails - w druku

Opublikowane przez Jarosław Zabiełło Wed, 29 Nov 2006 03:45:00 GMT

Drugie wydanie kultowej książki na temat Railsów jest już w druku. Wersja papierowa powinna być gotowa na 15 grudnia. Pierwsze wydanie zostało uznane za najlepszą książką techniczną na świecie. Drugie jest jeszcze ciekawsze, bo opisuje wszystkie nowości jakie mają się pojawić w Railsach 1.2. Właściciele wersji PDF mogą kupić wersję papierową ze zniżką.

Posted in  | Tagi  | brak comments

Ruby on Rails 1.2 RC1

Opublikowane przez Jarosław Zabiełło Sat, 25 Nov 2006 13:52:00 GMT

Nadchodzi wielkimi krokami Ruby on Rails 1.2. Właśnie udostępniono wersję RC1. Wersja finalna powinna pojawić się w najbliższych tygodniach. Wprowadzono szereg istotnych usprawnień w działaniu Railsów powiększając tym samym jeszcze bardziej komfort pracy i dystans w stosunku do pozostałych frameworków.

Np. dodano dodatkowy parametr “format” do metody respond_to. Aby z tego korzystać, należy w routes.rb dodać regułę:

map.connect ':controller/:action/:id.:format'. 

Nie trzeba już więcej bawić się w analizę nagłówków HTTP.

class WeblogController < ActionController::Base
  def index
    @posts = Post.find :all
    respond_to do |format|
      format.html
      format.xml { render :xml => @posts.to_xml }
      format.rss { render :action => "feed.rxml" }
    end
  end
end

GET /weblog       # zwróci HTML
GET /weblog.xml   # zwróci XML
GET /weblog.rss   # zwróci RSS

Inną, istotną zmianą jest dodanie Multibyte. Nie jest to co prawda tak zaawansowane jak w Pythonie, ale do czasu pojawienie się Ruby 2 (który ma mieć pełne wsparcie dla Unicode), Multibyte poprawia trochę sytuację. programisty. Generalnie Ruby jakoś dawał sobie radę z UTF-8, ale w niektórych sytuacjach pojawiały się problemy. Np. metoda size() zwraca ilość bajtów a nie znaków. Multibyte wprowadza metodę proxy dla napisów – chars. Np.

Napisałeś <%= @post.body.chars.length %> znaków.

Jedną z bardziej rewolucyjnych zmian, jest dodanie obsługi REST. (która zakłada, że każdy zasób sieci powinien być jednoznacznie identyfikowany za pomocą samego adresu URL). W praktyce oznacza, to znacznie skrócenie kontrolerów w Railsach po przez wysłanie na ten sam adres różnych komend, tzn,. nie tylko GET i POST ale także PUT i DELETE. Mimo, że te polecenia są cześcią standardu protokołu HTTP, większość przeglądarek implementuje tylko GET I POST. W takim wypadku Rails emuluje działanie tych komend poprzez dodawanie do adresu znaku średnika i polecenia.

Plugin Simply RESTful już działa i można go używać. Np. wygeneruj sobie kod nowego, REST’owego scaffoldingu:

ruby script/generate scaffold_resource

Oczywiście, musisz mieć odpowiednią najnowszą wersję Railsów, którą najlepiej zainstalować za pomocą gemsów:

gem install rails --source http://gems.rubyonrails.org -y

Nie przejmuj się jak wyświetli ci się coś w stylu

Successfully installed rails-1.1.6.5618

To jest tymczasowe oznaczenie dla wersji RC1. Jak wyjdzie wersja finalna to będzie wyświetlać się numer 1.2.

Trwają jeszcze końcowe prace nad Active Resource, modułem obsługującym REST i nowy sposób mapowania.

I ostatnia wiadomość, nadchodzące w grudniu, drugie wydanie książki Agile Web Development in Rails powinno większość tych zmian uwzględniać, bo książka jest pisana z założeniem że używany jest Edge Rails (czyli najnowsza wersja z repozytorium SVN).

Posted in  | Tagi  | 2 comments

Nginx - Apache killer

Opublikowane przez Jarosław Zabiełło Tue, 07 Nov 2006 23:47:00 GMT

W ostatnim artykule (Railsy: Lighttpd czy Apache 2.2.x?) porównywałem najbardziej popularne serwery HTTP dla Railsów. Zaintrygowany paroma wpisami w blogu postanowiłem przyjrzeć się dosyć mało znanemu serwerowi HTTP który zaczyna zdobywać coraz więcej uwagi na Zachodzie. Chodzi o ultraszybki serwer nginx napisany przez rosyjskiego programistę Igora Sysojewa.

Na pierwszy bój poszedł prosty test wyświetlenia “Hello World!” Na używanym przeze mnie serwerze dedykowanym (Athlon 64 3000+, 1GB RAM, Linux Ubuntu 64bit) dla 100 tys. zapytań (musiałem użyć aż tyle, bo serwer jest za szybki na mniejszą liczbę zapytań) uzyskałem następujące wyniki (dla polecenia “ab -n 100000 -c 1 http://localhost”):

  • Apache 2.2.3 = 4439 req/s
  • Lighttpd 1.4.11 = 7150 req/s
  • Nginx 0.4.12 = 8700 req/s

Co prawda Nginx wykazuje miażdzącą przewagę wydajności nad Apachem ale, z racji tego, że używam Lighttpd, który co prawda odstaje od Nginxa ale nie aż tak, postanowiłem na razie zaczekać z ewentualną migracją.

Okazało się jednak, że będę musiał przeprowadzić taką migrację szybciej niż bym chciał. Coś złego zaczęło się dziać z Ligthttpd. Po paru godzinach pracy, przestawał odpowiadać na zapytania a nawet w ogóle proces znikał z pamięci. Ki diabeł? Trudno powiedzieć, nie mam czasu aby analizować dokładniej problem. Jedyne co pomagało to regularny restart Lighttpd. Trochę głupie rozwiązanie. Postanowiłem zatem zrobić wcześniejszą migrację do Nginxa. Wg statystyk Netcraftu z Nginxa korzysta już ponad 90 tys. domen. Wydaje się to wystarczającą ilością aby można było uznać ten serwer za stabilny.

Jednakże moja migracja ma pewną trudność. Używam bowiem równocześnie PHP, Django, Rails i Zope (ściślej: Plone). Czyli całkiem niezła mieszanka aplikacji. Z PHP i Railsami było najmniej problemów, bo przykłady konfiguracji są z grubsza podane w Wiki.

Z Plone było troszkę gorzej. Musiałem bowiem znaleźć odpowiednik mniej więcej takiego kodu w Apache:

RewriteRule "^/(.*)$" 
"http://88.198.15.160:6001/VirtualHostBase/http/apologetyka.com:80/app/VirtualHostRoot/$1"  [P,L]

To nie jest zwykły rewrite, to jest połączenie proxy z rewrite.

W Lighttpd (też się swego czasu namęczyłem aby to uzyskać) uzyskuje się to tak:

url.rewrite-once = (
  "^/(.*)$" => "/VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1"
)
proxy.server = (
   "/VirtualHostBase/" => (
     ( "host" => "88.198.15.160" , "port" => 6001 ) )
)
Trochę prób i się udało. Nginx potrzebował takiego wpisu:
location / {
  rewrite ^/(.*)$ /VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1;
}
location /VirtualHostBase/ {
  include /opt/nginx/conf/proxy.conf;
  proxy_pass http://88.198.15.160:6001;
}

Najtrudniej było z konfiguracją Django bo opisu do Django na Nginx nie ma ani w dokumentacji do Django, ani w dokumentacji do NGinxa. Zmęczony eksperymentowaniem napisałem na listę dyskusyjną Django i dostałem całkiem pożyteczną wskazówkę odnośnie strony http://www.python.rk.edu.pl/w/p/django-pod-serwerem-nginx/. Niestety miałem pecha, bo akurat trafiłem na zmianę wpisów w DNS i artykuł był niedostępny. Udało mi się na szczęście wyłuskać jego kopię z cache Googli. Autor międzyczasie podesłał mi też pliki z artykułami. Zauważyłem że napotkał pewne problemy ze zmuszeniem Django do wyświetlania statycznej treści. Trochę podłubałem w kodzie i problem rozwiązałem. :)

Zauważyłem że Django wyświetlał mi pliki statyczne w trybie debug. Wynikało to pewnie z tego, że w urls.py stosuję zawsze taki wpis:

...
if DEBUG:
  urlpatterns += patterns('',
    (r'^images/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/images', 'show_indexes': True}),
    (r'^stylesheets/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/stylesheets', 'show_indexes': True}),
    (r'^javascripts/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/javascripts', 'show_indexes': True}),
    )

Dla trybu produkcyjnego (settings.DEBUG=False) trzeba zmusić serwer HTTP aby się zajmował plikami statycznymi. Django ma tylko przetwarzać Pythona. Mozna to zrobić np. tak:

...
location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
  access_log   off; # po co mi logi obrazków :)
  expires      30d; 
}
location / {
  include /opt/nginx/conf/fastcgi.conf;
  fastcgi_pass 127.0.0.1:6002;
  fastcgi_pass_header Authorization;
  fastcgi_intercept_errors off;
}

Gdzie plik fastcgi.conf:

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

fastcgi_param  PATH_INFO          $fastcgi_script_name;

Plik startowy napisałem sobie już w Pythonie:

#!/usr/bin/env python

import os, sys, time
DEBUG = True
# All Django project are inside /home/app/django/

path = '/home/app/django'

projects = {
    'biblia' : {
        'project': 'searchers',
        'port':6002,
        'pidfile': '/var/run/django_searchers.pid',
        'children': 4,
        },
    'koran': {
        'project': 'django_project',
        'port':6003,
        'pidfile': '/var/run/django_koran.pid',
        'children': 4
        },
    }

def start(name):
    project = projects[name]['project']
    os.chdir('%s/%s/' % (path, project))
    appl = './manage.py runfcgi host=127.0.0.1'
    cmd = '%s port=%s minspare=1 maxspare=%s pidfile=%s --settings=%s.settings' \
          % (appl, projects[name]['port'], projects[name]['children'], projects[name]['pidfile'], project)
    if DEBUG: print cmd
    os.system(cmd)

def stop(name):
    pidfile = projects[name]['pidfile']
    if os.path.exists(pidfile):
        cmd = '/bin/kill -TERM %s' % open(pidfile).read()
        if DEBUG: print cmd
        os.system(cmd)
        os.unlink(pidfile)

def restart(name):
    stop(name)
    time.sleep(1)
    start(name)

if __name__ == '__main__':
    try:
        action, project = sys.argv[1], sys.argv[2]
        if action in ['start','stop', 'restart'] and project in projects:
            if action == 'start':
                start(project)
            elif action == 'stop':
                stop(project)
            elif action == 'restart':
                restart(project)
            else:
                raise IndexError
        else:
            raise IndexError
    except IndexError:
        print "Usage: %s {start|stop|restart} {%s}" % (sys.argv[0], '|'.join(projects.keys()))

Migracja się udała. Plone, PHP, Django i Rails śmigają mi teraz na ultraszybkim (i zajmującym mało pamięci!) serwerze Nginx. Acha, zapomniałem dodać: Nginx to nie tylko duża wydajność i oszczędność pamięci. Nginx ma dużo modułów. Może nie tyle, co Apache, ale znacznie lepiej niż Lighttpd.

BTW, ciekawie wygląda także serwer Cherokee. Nginx działa tylko pod systemami POSIX (Unix, MacOS-X, Linux, FreeBSD). Cherokee natomiast posiada… binarną instalację pod Windows! Ale o tym może napiszę coś innym razem. :)

Posted in ,  | Tagi , , , ,  | 16 comments

RadRails i snippety TextMate

Opublikowane przez Jarosław Zabiełło Tue, 07 Nov 2006 23:13:00 GMT

RadRails, zintegrowany edytor dla Rubiego i Railsów zaczyna coraz bardziej niwelować wcześniejsze zachwyty nad komercyjnym edytorem TextMate. Nie dość, że jest darmowy i działa na Windows, MacOS-X i Linuksie (TextMate tylko na MacOS-X), to na dodatek uzyskał to, co do tej pory było główną atrakcją TextMate – makra przyśpieszające pisanie kodu, czyli tzw. snippety. RadRaila ma już przygotowanych 199 snippetów do Rubiego i 50 do RHTML!

Do tego wystarczy dorzucić kolorowanie kodu a’la TextMate i RadRails ma już wszystko (no może do pełni doskonałości brakuje mu uzupełniania kodu tak, jak to robi komercyjny edytor Komodo)

Posted in ,  | Tagi , , ,  | brak comments

Railsy: Lighttpd czy Apache 2.2.x?

Opublikowane przez Jarosław Zabiełło Sun, 29 Oct 2006 14:53:00 GMT

Odnośnie użycia Railsów w celu produkcyjnym, dużo się zmieniło od czasu pojawienia się Mongrela – małego, ale dosyć szybkiego serwera napisanego w Ruby i C. Następuje generalne odejście od podpinania kilku procesów FastCGI do Rubiego z różnych powodów. W drugim wydaniu Agile Web Development in Rails autorzy przyznali, że próbowali różnych opcji użycia modułu FastCGI. Zawsze ostatecznie były jakieś problemy.

Aktualnie zalecanym podejściem jest użycie szybkiego serwera HTTP (Lighttpd, Apache lub LiteSpeed) na froncie do obsługi statycznych plików (głównie obrazki, style kaskadowe i pliki Javascript). Aplikację RoR należy uruchomić za pomocą kilku procesów Mongrela (nie jak wcześniej: kilka procesów FastCGI). A tym, co zepnie je z serwerem HTTP jest moduł rozkładający ruch (load balancing).

Niestety, okazało się że w wypadku Lighttpd ten moduł zawiera błędy. Z kolei nowy Apache 2.2 posiada dobrze działający moduł load balancingu. Spowodowało to zrezygnowanie przez wielu z Lighttpd na rzecz nowego Apache 2.2.x.

Z racji tego, że mam trochę swobody w doborze technologii w pracy, postawiłem serwer na nowym Apache’u. Aplikacje Railsów podpinamy za pomocą mongrela i wbudowanego w Apache modułu load_balancer. Działa faktycznie sprawnie.

Czy to znaczy, że Lighttpd nie ma sensu używać? Skąd! W wersji 1.5 mają już mieć naprawiony moduł load balancingu. Ale nawet i bez tego, istnieje bardzo dobry, mały i szybki load balancer – Pound. Skuszony jednak potrzebą eksperymentowania oraz nabytym niedawno serwerem dedykowanym (w miejsce wcześniejszego VPS’a) postanowiłem przenieść wszystkie swoje prywatne serwisy (a jest tego sporo) z Lighttpd do Apache 2.2.3. Musiałem przenieść dwie aplikacje Rails, dwie aplikacje Django, dwie aplikacje PHP 5.1 oraz jedną duża aplikację Zope/Plone. I jak wrażenia?

Otóż, po 2 tygodniach używania, stwierdziłem, że to nie ma sensu. Apache pożera za dużo pamięci. Dzieje się dokładnie tak, jak pisałem wcześniej. Nie mogłem użyć trybu wielowątkowego, nie tyle ze względu na PHP ale ze względy na Django! Napisali bowiem, że nie należy tego trybu używać w wypadku ich frameworka. Django nie jest wielowątkowy. Musiałem więc przełączyć Apache z trybu worker na prefork. Zgodnie z obawami, każdy fork musiał marnować pamięć na moduł mod_php i mod_python nawet jak to nie ma sensu (np. podczas obsługi obrazków). Na dodatek nie mogłem coś uruchomić jednej aplikacji Django. Kompletnie nie wiem dlaczego. Druga była prawie identycznie skonfigurowana i działała.

Wróciłem zatem z powrotem do Lighttpd , Pounda i Mongrela (dla RoR) i FastCGI (dla Django i PHP). Efekty były bardzo widoczne: zwolniło mi się ponad 200 MB pamięci. Może to nie ma znaczenia w pracy, gdzie mamy 2GB ale ja mam tylko 1GB i muszę jeszcze uruchomić zasobożerny Plone.

Dokładniej wszystkie sprawdzone i działające konfiguracje opiszę w powstającej książce o Railach. Moja ostateczna konlkluzja jest taka: jeśli zależy ci na oszczędzeniu pamięci – wybieraj Lighttpd. Jeśli nie jest to kluczowa sprawa, a potrzebujesz jakieś specjalne, dodatkowe moduły (których Apache ma pełno) nowy Apache 2.2 spełni twe oczekiwania.

Posted in  | Tagi , , , ,  | 9 comments

Polski kanał IRC dla RoR

Opublikowane przez Jarosław Zabiełło Thu, 12 Oct 2006 14:55:31 GMT

Poza polskim forum mamyod niedawna kanał #rubyonrails.pl na serwerze irc.freenode.net gdzie można bardziej interaktywnie wymieniać się doświadczeniem na temat Rubiego i/lub Railsów. Na razie nie ma wiele osób, ale może się bardziej rozkręci.

Posted in ,  | 5 comments

Elastyczność Railsów

Opublikowane przez Jarosław Zabiełło Wed, 04 Oct 2006 15:01:00 GMT

Większość osób, które zetknęły się z frameworkiem Ruby on Rails zauważyła, że domyślne ustawienia są tam tak dobrane, aby maksymalnie uprościć i zminimalizować ilość ręcznej pracy przy ustawieniach. Jeśli więc ktoś trzyma się flozofii i konwencji nazw (np. dla tabel i ich pól) będzie miał znacznie mniej dodatkowej roboty z konfiguracją frameworka do pracy. Typowa definicja modelu wyglądać może nawet tak prosto:

class User < ActiveRecord::Base
end

Powyższa definicja zakłada, że tabela w bazie nosi nazwę “users”, a jej primary key nazywa się “id”. W przeciwieństwie do innych ORM’ów, railsowy ActiveRecord nie wymaga żadnego wcześniejszego definiowania pól. Ruby bez problemu sam sobie wyciagnie wszystkie nazwy pól w sposób dynamiczny wtedy, kiedy będzie mu to potrzebne.

Nietypowe struktury

Niestety, nie zawsze jesteśmy w takiej komfortowej sytuacji, że mamy wpływ na nazewnictwo tabel i ich pól. Jednak to nie jest większym problemem, bo Rails sobie z tym radzi równie prosto.

class User < ActiveRecord::Base
  set_table_name 'uzytkownicy'
  set_primary_key 'IdUzytkownika'
end

Jak widać, dużo pracy nam nie przybyło. W tym wypadku musieliśmy tylko jawnie podać jak się nazywa nasza tabela i jak się nazywa jej klucz główny.

Korzystanie z wielu baz

W momencie tworzenia projektu aplikacji RoR generowany jest automatycznie plik konfiguracyjny zawierający połączenie do bazy danych. Przykładowy plik config/database.yml mogłby wyglądać np. tak:

default: &defaults
  adapter: mysql
  host: localhost
  encoding: utf8
  username: jakis_login
  password: jakies_haslo

development:
  database: bookstore_dev
  <<: *defaults

test:
  database: bookstore_test
  port: 3307
  <<: *defaults

production:
  database: bookstore
  socket: /var/run/mysqld/mysqld.sock
  <<: *defaults

Co robi powyższy kod? Definiuje 3 bazy: produkcyjną, roboczą i testową. (Ta ostatnia jest używana tylko do testów jednostkowych więc nie powinno się tam wstawiać żadnych istotnych danych) W w/w konfiguracji zdecydowaliśmy, że wszystkie bazy będą korzystać z MySQL >=4.1 z ustawionym wew. kodowaniem UTF-8 (zalecane). Baza produkcyjna działać będzie na Linuksie, więc zamiast portu TCP, podano uniksowy socket. Baza testowa działa na porcie 3307, a robocza na domyślnym (3306). Główna sekcja “defaults” jest wspólna dla wszystkich trzech baz zgodnie z zasadą DRY (Don’t Repeat Yourself).

Załóżmy jednak, że chcemy korzystać z dodatkowej bazy i chcemy aby z niej korzystały niektóre modele. Żaden problem. Do pliku dopisujemy tylko dodatkową definicję połączenia do innej bazy:

other:
  adapter: mysql
  host: 192.168.0.123
  database: customers
  <<: *defaults

Jak widać, możemy tą metodą tworzyć aplikację korzystającą z rozproszonych baz. (W tym wypadku baza customers leży na zupełnie innym serwerze, ale korzysta z tego samego usera i hasła.)

No dobrze, ale jak modele mają rozpoznać z jakiego połączenia korzystają? To też prosta sprawa. Domyślnym połączeniem jest to, co zdefiniowano w sekcji domyślnej. Jedynie modele, które korzystają z innego połączenia, muszą to mieć jawnie zadeklarowane w definicji, np.

class Client
  establish_connection :other
end

Model Client oraz wszystkie modele utworzone z niego na drodze dziedziczenia, będą korzystać z innego połączenia niż domyślne.

BTW, to jedna z bardziej brakujących mi cech we frameworku Django (Pylons nie ma z tym żadnego problemu). Mam nadzieję, że wkrótce to poprawią i w Django będzie można bez problemu korzystać z wielu baz.

Wspolne modele dla wielu aplikacji Rails

W tym scenariuszu mamy za zadanie stworzyć szereg aplikacji webowych korzystających z oddzielnych baz o tej samej strukturze tabel. Chcemy uniknąć powielania kodu niezbędnego dla modeli ORM. Nie mamy też czasu (ani ochoty) aby siedzieć i stworzyć definicje do wszystkich tabel wraz z wyposażeniem ich w nasze własne, dodatkowe metody uwzględniające każdą, możliwą potrzebę w przyszłości. Zamiast tego, wolimy tworzyć i rozbudowywać obiektowy opis naszej bazy dla wszystkich aplikacji w miarę ich rozwoju i wzrostu złożoności. Chcemy oby nasz model biznesowy, opisywany przez ActiveRecord, rósł stopniowo wraz ze wzrostem naszych aplikacji RoR.

Do takiego zadania, najlepiej aby wszystkie aplikacje RoR korzystały ze wspólnego repozytorium modeli. W wypadku systemów POSIX (Linux, czy MacOS-X) najprościej to rozwiązać za pomocą linków symbolicznych. Jednakże, jeśli chcemy aby nasz kod był bardziej przenośny i działał także pod Windowsami musimy inaczej podejść do tego problemu: wyniesiemy definicje naszych modeli do zewnętrznych modułów Rubiego aby domieszkowały one klasy poszczególnych modeli Rails.

Wpierw musimy dla każdej aplikacji RoR wskazać gdzie leży repozytorium z wpołdzielonymi modułami. Najlepiej wstawić do pliku conf/environment.rb zmienną globalną Rubiego o nazwie, np. $SHARED.

$SHARED = File.dirname(__FILE__)+'/../../shared/'

Konstrukcja File.dirname… jest zalecana, gdyż daje pewny i jednoznaczny dostęp do pliku bez względy na to gdzie przekopiujemy cały nasz projekt. (Identycznie należy robić pod PHP aby uniknąć problemów z ustawieniami zmiennej include_dirs. Większość pehapowego lamerstwa używa zapisu require(“plik.php”) i potem się dziwi że coś im nie działa)

Załóżmy że wcześniej mieliśmy model zdefiniowany następująco:

class Book
  set_table_name 'ksiazki'
  belongs_to :author
  def self.commented(user_id)
    self.find(:all, :conditions=>['user_id=?',user_id])
  end
end

Mamy tu więc nie tylko nazwę tabeli niezgodną z konwencją Railsów, ale także dodatkową metodę klasową. Wpierw zróbmy z tego moduł:

module BookModule
  def self.extended(c)
    c.set_table_name 'ksiazki'
    c.belongs_to :author
  end
  def commented(user_id)
    self.find(:all, :conditions=>['user_id=?',user_id])
  end
end

Kod korzystający z tego modułu przyjmie postać:

if RAILS_ENV == "development"
  load $SHARED + 'models/book.rb'
else
  require $SHARED + 'models/book.rb'
end

class Book< ActiveRecord::Base
  include BookModule
  extend BookModule
end

Górny fragment jest potrzebny jeśli chcemy aby Webrick/Mongrel w trybie development się przeładowywał podczas zmiany kodu w Ruby. “include” włącza metody instancji. Zaś “extend” włącza wszystkie emetody modułu _BookModule _do klasy jako jej metody klasowe. Zwróć uwagę, że w module nie definiowaliśmy ich jako z prefiksem self. Co prawda, efektem ubocznym tego przykładu jest to, że metoda commented() będzie możliwa do uruchomienia jako metoda zarówno instancji jak i klasy, ale to raczej nie ma żadnego znaczenia.

Posted in  | Tagi , ,  | 11 comments

Spotkanie polskiej grupy RoR w Warszawie 18 sierpnia

Opublikowane przez Jarosław Zabiełło Mon, 14 Aug 2006 15:02:00 GMT

Polska grupa RoR postanowiła się po raz drugi spotkać na żywo. Spotkanie ustalono na 18 sierpnia o godzinie 18:00 w sali konferencyjnej firmy Sec-Bos w Warszawie.

Spotkanie odbedzie się na 99% w przypadku wystąpienia problemów z salą niezwłocznie zostanie dołączona adnotacja.

Polska społeczność RoR chciałaby również podziękować firmie Sec-Bos za pomoc w organizacji spotkania.

Szczegóły na forum http://forum.rubyonrails.pl .

Posted in  | Tagi  | brak comments

Skalowanie RoR

Opublikowane przez Jarosław Zabiełło Sun, 06 Aug 2006 12:54:00 GMT

Mit o rzekomej niskiej wydajności Railsów wydaje się być przesadzony. Interpreter Rubiego jest co prawda wolniejszy od interpretera Pythona (i tym samym Django jest szybsze od Railsów). Ale już porównanie Django, Railsów i pehapowego Symphony pokazuje że Rails jest wyraźnie szybszy od Symphony (który używa PHP5 i akceleratora!). Skoro PHP jest wolniejszy, to z szybkością Railsów nie jest aż tak źle, prawda? Ale to nie wszystko, Rails posiada wygodny mechanizm który pozwala bez trudu powalić na kolana typową aplikację Django.

Siła skalowania poziomego.

Gdy mamy na myśli wydajność całej aplikacji to nie sposób pominąć ważny jej element: skalowalność. Aplikację webową można skalować pionowo (dokładając więcej RAM’u, mocniejszy procesor) lub poziomo (dokładając po prostu kolejny serwer). Ta ostatnia opcja jest szczególnie interesująca, bo dołożenie mocniejszego procesora nie można ciągnąć bez końca.

Ruby on Rails świetnie skaluje się poziomo. Zamiast klasycznego zestawu Lighttpd + kilka procesów fasctgi polecam znacznie lepszy zestaw oparty na procesach mongrel.

Scenariusz 1:

Lighttpd na froncie obsługuje wszystkie statyczne pliki. Pozostałe kieruje do Pound (szybkiego i lekkiego load balancera1), który z kolei kieruje ruch do kilku procesów mongrela (każdy z nich może być nawet na innej maszynie).

Scenariusz 2:

Apache 2.2 na froncie z wbudowanym modułem load balancera rozkłada ruch na kilka procesów mongrela.

Przykład.

Zakładam, że użyję Lighttpd + Pound + 4 procesy mongrela z rozłożonym ruchem na 2 serwery. Fragment pliku konfiguracyjnego dla serwera Lighttpd:

$HTTP["host"] == "domena.host" {
  $HTTP["url"] !~ "^/(cgi-bin|images|stylesheets|javascripts)" {
    proxy.server = ( "/" => ( ( "host" => "127.0.0.1" , "port" => 9000 ) ))
  }
  server.document-root = "/home/httpd/domena.host/public"
  server.indexfiles = ("dispatch.fcgi")
  server.error-handler-404 = "/dispatch.fcgi"
}

Lighttpd obsługuje wszystkie pliki statyczne. Robi to wyjątkowo szybko, lepiej niż Apache. Cały pozostały ruch jest spięty za pomocą proxy z procesem na porcie 9000. Na tym porcie będzie nasłuchiwał Pound, który zajmie się rozkładaniem ruchu na pozostałe serwery. Plik konfiguracyjny dla Pound’a może wyglądać:


User "nobody"
ListenHTTP
  Address 0.0.0.0
  Port    9000
  Service
    BackEnd
      Address 127.0.0.1
      Port    9001
    End
    BackEnd
      Address 127.0.0.2
      Port    9002
    End
    BackEnd
      Address 127.0.0.3
      Port    9003
    End
    BackEnd
      Address 127.0.0.4
      Port    9004
    End
  End
End

Pozostało tylko uruchomić mongrela. Po zainstalowaniu (gem install mongrel), przechodzimy do folderu z naszą aplikacją railsów i odpalamy

mongrel_rails cluster::configure -e production \
    -p 9000 -N 4 -c /opt/ror/mojprojekt -a 127.0.0.1 \
    --user mongrel --group mongrel 

(Parametr -a chroni nas przed możliwością zewnętrznego odpalenia w przeglądarce Railsów działającej bezpośrednio na którymś z procesów mongrela).

Powinien powstać plik: config/mongrel_cluster.yml

---
port: "9000"
environment: production
address: 127.0.0.1
pid_file: log/mongrel.pid
servers: 4

Cluster odpalamy za pomocą

mongrel_rails cluster::start

Dodatkową zaletą używania mongrela jest możliwość wygodnego restartowania dowolnej aplikacji Railsów (a można ich mieć przecież wiele) bez konieczności restartowania serwera HTTP (mongrel_rails cluster::restart)

Poza tym mongrel świetnie nadaje się do zastąpienia Webricka, standardowego serwera HTTP zalecanego do tej pory do pracy developerskiej. Zamiast

ruby script/server
można użyć mongrela:
mongrel_rails start

Domyślnie podniesie się serwer HTTP na porcie 3000 i w trybie development (czyli identycznie jak Webrick). Mongrel jest jednak znacznie szybszy!

Dla użytkowników windozy też jest dobra wiadomość. Mongrel można zainstalować jako usługę windowsów i zarządzać standardowo tak jak inne usługi. Można np. odpalić sobie na jednym porcie Rails w trybie produkcyjnym, a na drugim – w trybie developerskim. W tym drugim wypadku nie trzeba restartować serwera bo zmiany w kodzie automatycznie przeładowują serwerek (tak jak Webrick)

Oczywiście to wszystko ślicznie działa z Capistrano, railsowym systemem automatycznie synchronizującym zmiany na n-serwerach.

Jak dla mnie, mongrel wnosi nową jakość do pracy z Railsami i warto przyjrzeć się mu dokładniej. Nie zdziwiłbym się, jakby następne wersje Railsów standardowo zalecały używanie mongrela, a nie Webricka.

Tak jak siłą Google są tysiące mniejszych maszyn a nie jedna – super szybka, tak nawet najszybszy serwer wyposażony w Apache i mod_python (który używa domyślnie Django) nie ma szans w starciu z clusterem Railsów opartym na procesach mongrel i korzystających z kilku serwerów.

Wykres z artykułu Scaling Rails with Apache 2.2, mod_proxy_balancer and Mongrel

-

1 Dlaczego nie wykorzystywany jest load balancer wbudowany w Lighttpd? Bo jest kiepsko zaimplementowany i są problemy z restartem procesów. Dlatego zalecany jest zewnętrzny load balancer lub użycie najnowszej wersji Apache 2.2 który ma ten moduł dobrze zaimplementowany.

Posted in  | Tagi  | 15 comments

RadRails 0.7, Aptana, Typo4

Opublikowane przez Jarosław Zabiełło Tue, 01 Aug 2006 03:09:00 GMT

Pojawiła się nowa wersja RadRails 0.7. Niestety, trochę się pośpieszono. Zawiera wiele błędów. Lepiej poczekać na następną wersję.

Pojawił się bardzo ciekawy edytor oparty na Eclipse: Aptana. Służy głównie do pracy z HTML, CSS i JavaScript. Warto jednak obejrzeć załączone filmy pokazujące w jaki sposób edytor podpowiada kod i uzupełnia metody, bo robi to lepiej niż Dramweaver 8.

Pojawiła się kolejna, stabilna wersja aplikacji do tworzenia bloga opartej na Ruby on Rails – Typo 4.

Posted in  | Tagi , ,  | brak comments

Django, Rails i współdzielenie danych

Opublikowane przez Jarosław Zabiełło Wed, 19 Jul 2006 19:22:00 GMT

Osoby mające wcześniej do czynienia z innymi frameworkami, gdy sięgną do Django mogą być trochę zdezorientowane sposobem w jaki należy przekazywać zmienne do wspólnych części serwisu. Aby lepiej zrozumieć problem, podam wpierw jak jest on rozwiązywane w Railsach.

Otóż, Railsy zakładają, że każdy kontroler dziedziczy po wspólnym kontrolerze zwanym ApplicationControler. A że jest to normalna klasa Rubiego, więc nic dziwnego, że wszelkie jej metody i zmienne (instancji) są automatycznie dziedziczone we wszystkich jej klasach potomnych. To bardzo intuicyjne rozwiązanie. Jeśli chcemy przekazać jakieś wspólne dane do wszystkich kontrolerów i ich szablonów, wystarczy te dane zdefiniować w tym miejscu. Weźmy np. poniższy kod.

# plik: app/controllers/application.rb
class ApplicationController < ActionController::Base
  before_filter :defaults
  def initialize
    @name = "Jarek"
  end
  def defaults
    @msg = "Hello!"
  end
end

# plik: app/controllers/home_controller.rb
class HomeController < ApplicationController 
end

# plik: app/controllers/about_controller.rb
class AboutController < ApplicationController 
  def index
    @dodatkowa = "wartość"
  end
end

Zakładając, że nie zmienialiśmy domyślnego działania resolvera adresów URL, wpisanie strony http://localhost:3000/home wywoła domyślną metodę index() z klasy HomeController. Jeśli jej nie ma (jak na naszym przykładzie), to Rails zakłada, że chcemy wywołać jej szablon tak, jakby ta metoda istniała. Wczytywany jest zatem plik app/views/home/index.rhtml. Dzięki temu, że w klasie ApplicationController zdefiniowaliśmy 2 zmienne instancji (@name i @msg) automatycznie są one dziedziczone przez klasę HomeController i tym samym dostępne w w/w szablonie. Identycznie będzie z drugą klasą, jej szablon również będzie miał obie, zdefiniowane wyżej, zmienne1.

W wypadku Django, sprawy wyglądają inaczej dlatego, że kontrolery3 nie są metodami klas Pythona! Są one metodami modułu Pythona. Co prawda moduł też jest obiektem, ma własną przetrzeń nazw itp. ale posiada jedną zasadniczą różnicę wobec klasy – nie można dziedziczyć modułu od modułu. Tym samym próba współdzielenia zmiennych musi być zrobiona zupełnie inną metodą.

Trochę z tym problemem się męczyłem, bo w dokumentacji mało na ten temat piszą. Dopiero na IRC podsunięto mi link do artykułu Django tips: Template context processors, który wszystko ładnie wyjaśnił2. Okazuje się, że jest kilka sposobów rozwiązania tego problemu. Napisanie własnego MiddleWare (ech, nie chciało mi się, to armata na muchę), napisanie własnego znacznika dla szablonów (w wypadku mojej małej aplikacji, to też byłaby przesada) lub wykorzystanie tzw. procesora kontekstu (Context Processor). I to było to!

Wystarczy stworzyć sobie plik (o nazwie np. context_processors.py) z funkcjami implementującymi to, co chcemy wrzucić do wszystkich szablonów.

def name(request):
  return 'Jarek'

def msg(request):
  return 'Hello!'

Następnie trzeba dodać w settings.py informację że będziemy tego używać:

TEMPLATE_CONTEXT_PROCESSORS = (
  'myapp.context_processors.name',
  'myapp.context_processors.msg',
  ) 

W wypadku generic views to wszystko, bo one automatycznie wciągają RequestContext dla swoich szablonów. W wypadku naszych kontrolerów (zakładając, że używamy render_to_response() aby wyświetlać szablon) trzeba na końcu metody render_to_response() dodać jeden parametr ‘context_instance’. Czyli:

# wpierw wciągamy RequestContext
from django.template import RequestContext

def home(request):
  return render_to_response(
    'home.html', 
    context_instance=RequestContext(request))

def about(request):
  return render_to_response(
    'about.html', 
    {'dodatkowa': 'wartość'},
    context_instance=RequestContext(request))

Tak coraz bardziej wgłębiając się w Rails i Django widzę, że Rails jest jednak bardziej intuicyjny i łatwiej się go nauczyć. Jednakże Django wydaje się mieć sporo potężnych mechanizmów, których odkrywanie (mam nadzieję) się uprości jak tylko jego twórcy opublikują pierwszą książkę na jego temat (ma być dostępna także w wersji online za darmo!)

-

1 W podanym wyżej przykładzie wykorzystałem dwa różne sposoby przekazania zmiennych. Za pomocą konstruktora i za pomocą metody ‘before_filter’. Wiadomo, że konstruktor nie nadaje się do przekazywania wszystkiego. Czasami musimy przekazać coś, co istnieje już po wywołaniu konstruktora. Wtedy przydaje się metoda ‘before_filter’. To jedna z wielu sztuczek jakie ma w swym arsenale Rails aby kod był czysty, klarowny i zgodny z zasadą DRY (nie powtarzania się).

2 Zobacz też artykuł How Django processes a request?.

3 W Django nazywane są one metodami widoku. Osobiście nie podoba mi się zamieszanie terminologiczne jakie Django zrobiło z modelem MVC. Kontroler to dla nich Widok (View), zaś Widok to Szablon. Trochę to bez sensu, bo nikt tak tych rzecz nie nazywa. Czyli djangowe metody widoku to nic innego jak akcje kontrolera dla Rails i reszty świata.

Posted in ,  | Tagi ,  | 2 comments

Django i Rails biją PHP

Opublikowane przez Jarosław Zabiełło Fri, 14 Jul 2006 15:34:00 GMT

Porównanie wydajności trzech frameworków: Symfony, Ruby on Rails i Django pokazuje że Rails jest znacznie szybszy od pehapowego Symfony, a Django znacznie szybszy od Railsów. Co ciekawe, PHP5 używał akceleratora.

Posted in , ,  | Tagi , ,  | 19 comments

Mamy pl.comp.lang.ruby!

Opublikowane przez Jarosław Zabiełło Fri, 23 Jun 2006 07:13:00 GMT

Są już oficjalne wyniki głosowania na rzecz stworzenia grupy dyskusyjnej pl.comp.lang.ruby. 96 głosów za, 4 wstrzymujące się i 6 nieważnych. Nowa grupa powinna być wkrótce dostępna na serwerach usenetowych.

Posted in ,  | Tagi  | 3 comments

pl.comp.lang.ruby - głosowanie rozpoczęte!

Opublikowane przez Jarosław Zabiełło Sat, 03 Jun 2006 14:03:00 GMT

Po długim okresie oczekiwań w końcu doczekaliśmy się rozpoczęcia głosowania nad stworzeniem grupy pl.comp.lang.ruby. Oczywistą zaletą grupy newsowej jest m.in. to, że jest indeksowana i archiwizowana przez Google oraz jest bardziej odporna na “pady” serwera, bo posty są replikowane po wielu serwerach. Formalna treść ogłoszenia (wraz z informacją o sposobie głosowania) jest dostępna tutaj…

Posted in ,  | Tagi  | brak comments

JEdit i Rails

Opublikowane przez Jarosław Zabiełło Sun, 28 May 2006 01:46:00 GMT

RadRails ma silną konkurencję. Edytor JEdit jest nie tylko szybszy i mniej zabiera zasobów komputera, to na dodatek, przy dodaniu odpowiednich pluginów doskonale nadaje się do pracy z Railsami. Wygląda to bardzo dobrze, wystarczy rzucić okiem na tę stronę.

Posted in ,  | Tagi ,  | 3 comments

Starsze wpisy: 1 2