<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/stylesheets/rss.css" type="text/css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>RubyPolRails : Ruby cms - </title>
    <link>http://www.ruby-on-rails.com.pl/articles.rss</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>O Ruby i Rails praktycznie, po polsku</description>
    <item>
      <title>Wezm&#281; udzia&#322; w konferencji RuPy 2008</title>
      <description>&lt;p&gt;W&#322;a&#347;nie dosta&#322;em informacj&#281;, &#380;e m&#243;j wyk&#322;ad &amp;#8220;Complicated SQL queries in Ruby on Rails applications&amp;#8221; (tytu&#322; angielski, bo konferencja po angielsku) zosta&#322; zaakceptowany na konferencj&#281; RuPy 2008, kt&#243;ra odb&#281;dzie si&#281; w Poznaniu 12-13 kwietnia. Szczeg&#243;&#322;y na &lt;a href="http://www.rupy.eu"&gt;stronie konferencji&lt;/a&gt;. Zapraszam serdecznie - poza niezwykle pouczaj&#261;cymi :) wyk&#322;adami b&#281;dzie r&#243;wnie&#380; okazja spotka&#263; si&#281; i pogada&#263;.&lt;/p&gt;</description>
      <pubDate>Wed, 13 Feb 2008 21:36:00 +0000</pubDate>
      <guid isPermaLink="false">urn:uuid:f2fe088d-553f-404c-82bb-b62d5a12681f</guid>
      <comments>http://ruby-on-rails.com.pl/articles/2008/02/13/konferencja-rupy-intro#comments</comments>
      <category>Nowo&#347;ci</category>
      <category>ruby</category>
      <category>rails</category>
      <category>konferencja</category>
      <category>rupy</category>
      <category>szkolenie</category>
      <category>wyk&#322;ad</category>
      <category>kurs</category>
      <link>http://ruby-on-rails.com.pl/articles/2008/02/13/konferencja-rupy-intro</link>
    </item>
    <item>
      <title>Wsp&#243;&#322;bie&#380;no&#347;&#263; - testowanie</title>
      <description>&lt;p&gt;W poprzednim poscie opisa&#322;em typowe problemy wsp&#243;&#322;bie&#380;no&#347;ciowe i zaproponowa&#322;em ich rozwi&#261;zania, natomiast teraz chcia&#322;bym uzupe&#322;ni&#263; ten opis o sposoby testowania pod k&#261;tem wsp&#243;&#322;bie&#380;no&#347;ci w spos&#243;b jak najbardziej zgodny z filozofi&#261; Rails.&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Na pocz&#261;tku zastrze&#380;enie - opisuj&#281; co&#347;, co napisa&#322;em sam na w&#322;asne (tzn. tworzonego projektu) potrzeby. Na pewno wiele element&#243;w da&#322;oby si&#281; ulepszy&#263; i mo&#380;e uda mi si&#281; z tego zrobi&#263; kiedy&#347; plugina, ale na razie m&#243;j &amp;#8220;system testowania wsp&#243;&#322;bie&#380;nego&amp;#8221; pozostaje w pierwszej, oryginalnej wersji zgodnie z zasad&#261;, &#380;e prowizorki s&#261; najbardziej trwa&#322;e :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Warto stworzy&#263; osobny katalog na testy wsp&#243;&#322;bie&#380;no&#347;ci, ja sw&#243;j nazwa&#322;em &lt;code&gt;test/concurrent&lt;/code&gt;.W tym katalogu tworzymy bazow&#261; klas&#281; dla test&#243;w, np. ConcurrentTest:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;    &lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;test/unit/ui/console/testrunner&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

    &lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;ConcurrentTest&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;Test&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Unit&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;TestCase&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;concurrent&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt; &lt;span class="ident"&gt;count&lt;/span&gt; &lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="ident"&gt;saved_config&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;remove_connection&lt;/span&gt;
        &lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;..&lt;/span&gt;&lt;span class="ident"&gt;count&lt;/span&gt;&lt;span class="punct"&gt;).&lt;/span&gt;&lt;span class="ident"&gt;collect&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;i&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
          &lt;span class="ident"&gt;fork&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
              &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;establish_connection&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt; &lt;span class="ident"&gt;saved_config&lt;/span&gt; &lt;span class="punct"&gt;)&lt;/span&gt;
              &lt;span class="keyword"&gt;yield&lt;/span&gt; &lt;span class="ident"&gt;i&lt;/span&gt;
          &lt;span class="keyword"&gt;end&lt;/span&gt;
        &lt;span class="punct"&gt;}.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="punct"&gt;{|&lt;/span&gt;&lt;span class="ident"&gt;process_id&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt; &lt;span class="constant"&gt;Process&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;waitpid&lt;/span&gt; &lt;span class="ident"&gt;process_id&lt;/span&gt;&lt;span class="punct"&gt;}&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;self.run&lt;/span&gt;
        &lt;span class="constant"&gt;Test&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Unit&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;UI&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Console&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;TestRunner&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;run&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Je&#347;li chcemy korzysta&#263; z istniej&#261;cego test helpera, to dodajemy na pocz&#261;tku pliku lini&#281; &lt;code&gt;require File.dirname( __FILE__ ) + '/../test_helper'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My&#347;l&#281;, &#380;e kod jest zrozumia&#322;y - w samym te&#347;cie wywo&#322;ujemy metod&#281; &lt;code&gt;concurrent_test&lt;/code&gt; zadaj&#261;c jej ilo&#347;&#263; wsp&#243;&#322;bie&#380;nych proces&#243;w, a metoda ta forkuje odpowiedni&#261; ich ilo&#347;&#263; i dla ka&#380;dego wykonuje blok. Bardzo wa&#380;ne jest &lt;strong&gt;prawid&#322;owe zarz&#261;dzanie po&#322;&#261;czeniami do bazy danych&lt;/strong&gt; - je&#347;li nie b&#281;dziemy tworzyli osobnego po&#322;&#261;czenia dla ka&#380;dego procesu potomnego, to ten, kt&#243;ry zako&#324;czy pierwszy nie&#378;le namiesza zamykaj&#261;c po&#322;&#261;czenie u&#380;ywane przez pozosta&#322;e.&lt;/p&gt;

&lt;p&gt;Teraz przyk&#322;ad testu:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;    &lt;span class="comment"&gt;#!/usr/bin/env ruby&lt;/span&gt;
    &lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="constant"&gt;File&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;dirname&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt; &lt;span class="constant"&gt;__FILE__&lt;/span&gt; &lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;+&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;/concurrent_test_helper&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

    &lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;PostTest&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;ConcurrentTest&lt;/span&gt;

      &lt;span class="ident"&gt;fixtures&lt;/span&gt; &lt;span class="symbol"&gt;:posts&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;test_view_count&lt;/span&gt;
        &lt;span class="ident"&gt;count&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;10&lt;/span&gt;
        &lt;span class="ident"&gt;post&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;Post&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;find&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;
        &lt;span class="ident"&gt;assert_equal&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;post&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;view_count&lt;/span&gt;
        &lt;span class="ident"&gt;concurrent&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt; &lt;span class="ident"&gt;count&lt;/span&gt; &lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
          &lt;span class="ident"&gt;post&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;Post&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;find&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;
          &lt;span class="ident"&gt;sleep&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;
          &lt;span class="ident"&gt;post&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;increase_view_count&lt;/span&gt;
        &lt;span class="keyword"&gt;end&lt;/span&gt;
        &lt;span class="ident"&gt;post&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;Post&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;find&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;
        &lt;span class="ident"&gt;assert_equal&lt;/span&gt; &lt;span class="ident"&gt;count&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;post&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;view_count&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="constant"&gt;PostTest&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;run&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dziesi&#281;ciokrotnie zwi&#281;kszamy licznik ods&#322;on posta (w 10 wsp&#243;&#322;bie&#380;nych procesach), a nast&#281;pnie sprawdzamy, czy warto&#347;&#263; licznika wynosi 10. Prawda, &#380;e proste?&lt;/p&gt;

&lt;p&gt;W&#261;tpliwo&#347;ci mo&#380;e budzi&#263; u&#380;ycie funkcji &lt;code&gt;sleep&lt;/code&gt; - ot&#243;&#380; jedn&#261; z najbardziej wkurzaj&#261;cych cech b&#322;&#281;d&#243;w zwi&#261;zanych ze wsp&#243;&#322;bie&#380;no&#347;ci&#261; jest trudno&#347;&#263; ich powt&#243;rzenia. W podanym wy&#380;ej przyk&#322;adzie trzeba by liczy&#263; na to, &#380;e kt&#243;ry&#347; proces wykona &lt;code&gt;find&lt;/code&gt; i &lt;code&gt;increase_view_count&lt;/code&gt; naprzemiennie z innym, a nie &#380;e ka&#380;dy z proces&#243;w wykona po kolei obie te metody, a dopiero potem pozwoli si&#281; wykona&#263; nast&#281;pnemu. U&#380;ycie &lt;code&gt;sleep&lt;/code&gt; praktycznie gwarantuje nam najpierw wykonanie &lt;code&gt;find&lt;/code&gt; przez wszystkie procesy, a dopiero potem &lt;code&gt;increase_view_count&lt;/code&gt;, co (przy za&#322;o&#380;eniu, &#380;e &lt;code&gt;increase_view_count&lt;/code&gt; nie dba o wsp&#243;&#322;bie&#380;no&#347;&#263;) pozwoli nam na zreprodukowanie b&#322;&#281;du za ka&#380;dym razem.&lt;/p&gt;

&lt;blockquote&gt;
    &lt;p&gt;Nie w ka&#380;dym przypadku umieszczenie &lt;code&gt;sleep&lt;/code&gt; w ciele testu pozwoli na odtworzenie b&#322;&#281;du. Czasami trzeba t&#281; funkcj&#281; wywo&#322;a&#263; w inkryminowanej metodzie albo nawet w procedurze bazodanowej (dla Postgresa by&#322;oby to &lt;code&gt;pg_sleep&lt;/code&gt;). Pami&#281;tajcie u usuni&#281;ciu tego wywo&#322;ania po zako&#324;czeniu testowania!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Teraz wystarczy odpali&#263; nasz plik z testem i czeka&#263; na wynik.&lt;/p&gt;

&lt;h3&gt;Integracja&lt;/h3&gt;

&lt;p&gt;Mo&#380;emy zintegrowa&#263; nasze wsp&#243;&#322;bie&#380;ne testy nieco bardziej z infrastruktur&#261; testow&#261; Rails. W tym celu tworzymy nast&#281;puj&#261;cego taska Rake:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;    &lt;span class="ident"&gt;namespace&lt;/span&gt; &lt;span class="symbol"&gt;:test&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;

      &lt;span class="ident"&gt;task&lt;/span&gt; &lt;span class="symbol"&gt;:concurrency&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;db:test:prepare&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
        &lt;span class="constant"&gt;Dir&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;glob&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;test/concurrent/*test.rb&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;).&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;test&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt; &lt;span class="ident"&gt;system&lt;/span&gt; &lt;span class="ident"&gt;test&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I teraz mo&#380;emy odpali&#263; wszystkie nasze testy u&#380;ywaj&#261;c &lt;code&gt;rake test:concurrency&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Mam jeszcze taki pomys&#322;, &#380;eby wyprodukowa&#263; &#322;adny raport dot. ilo&#347;ci b&#322;&#281;d&#243;w (taki jak podaje &lt;code&gt;rake test&lt;/code&gt;) i u&#380;y&#263; kodu wyj&#347;cia programu w celu podliczenia ilo&#347;ci test&#243;w, kt&#243;re obla&#322;y, ale na razie pozostawiam to jako &#263;wiczenie dla czytelnika :)&lt;/p&gt;

&lt;h3&gt;Uwagi&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;jako &#380;e aplikacje Rails nie oszcz&#281;dzaj&#261; specjalnie pami&#281;ci, a my odpalamy ich np. 10 naraz, nale&#380;y zaopatrzy&#263; si&#281; w odpowiedni&#261; ilo&#347;&#263; RAM. Moja praktyka pokazuje, &#380;e przy 10 wsp&#243;&#322;bie&#380;nych procesach laptop z 1GB RAM potrafi swapowa&#263; (oczywi&#347;cie uruchomione by&#322;y te&#380; r&#243;&#380;ne inne programy) - specjalnie do test&#243;w wsp&#243;&#322;bie&#380;nych dorzuci&#322;em jeszcze 1GB :)&lt;/li&gt;
&lt;li&gt;ilo&#347;&#263; wsp&#243;&#322;bie&#380;nych proces&#243;w mo&#380;na zdefiniowa&#263; tak: minimalna ilo&#347;&#263;, kt&#243;ra pozwala na w miar&#281; regularne odtworzenie b&#322;&#281;du. W powy&#380;szym przyk&#322;adzie w&#322;a&#347;ciwie wystarczy&#322;oby uruchomi&#263; 2 procesy, ale czasami i 10 to za ma&#322;o.&lt;/li&gt;
&lt;li&gt;je&#347;li b&#322;&#261;d nie chce si&#281; pojawi&#263; mimo wielokrotnego uruchamiania test&#243;w, to nale&#380;y jeszcze raz dok&#322;adnie przeanalizowa&#263; kod: aplikacji, &#380;e potwierdzi&#263;, &#380;e b&#322;&#261;d naprawd&#281; mo&#380;e wyst&#261;pi&#263; i testu, by sprawdzi&#263;, &#380;e stworzone zosta&#322;y odpowiednie warunki do jego wyst&#261;pienia (patrz uwaga nt. &lt;code&gt;sleep&lt;/code&gt;). Pami&#281;tajcie, &#380;e b&#322;&#281;dy &amp;#8220;wsp&#243;&#322;bie&#380;no&#347;ciowe&amp;#8221; s&#261; ma&#322;o deterministyczne i kilkukrotny bezb&#322;&#281;dny przebieg test&#243;w mo&#380;e &#347;wiadczy&#263; zar&#243;wno o poprawno&#347;ci programu jak i niskiej jako&#347;ci test&#243;w&lt;/li&gt;
&lt;li&gt;zamiast testowa&#263; ponownie podobne kawa&#322;ki kodu (np. licznik ods&#322;on posta, licznik ods&#322;on artyku&#322;&#243;w u&#380;ytkownika, licznik ods&#322;on komentarza) lepiej jest u&#380;y&#263; sprawdzonego kawa&#322;ka kodu jako wzorca, a czas przeznaczony na testowanie po&#347;wi&#281;ci&#263; na przetestowanie innego przypadku &lt;/li&gt;
&lt;/ul&gt;</description>
      <pubDate>Mon, 11 Feb 2008 21:14:00 +0000</pubDate>
      <guid isPermaLink="false">urn:uuid:2e6b669e-5ba1-4054-9bc0-b0f94a0a1ef3</guid>
      <comments>http://ruby-on-rails.com.pl/articles/2008/02/11/wspolbieznosc-testowanie#comments</comments>
      <category>Tips and tricks</category>
      <category>Dobre praktyki</category>
      <category>rails</category>
      <category>ruby</category>
      <category>concurrency</category>
      <category>wsp&#243;&#322;bie&#380;no&#347;&#263;</category>
      <category>mongrel</category>
      <category>lock</category>
      <category>race</category>
      <category>test</category>
      <category>testowanie</category>
      <link>http://ruby-on-rails.com.pl/articles/2008/02/11/wspolbieznosc-testowanie</link>
    </item>
    <item>
      <title>Niepopularne problemy ze wsp&#243;&#322;bie&#380;no&#347;ci&#261;</title>
      <description>&lt;p&gt;Chyba ka&#380;dy programista obcuj&#261;cy z Rails d&#322;u&#380;ej ni&#380; 3 miesi&#261;ce mia&#322; do czynienia z aplikacj&#261; uruchamian&#261; w wielu (w sensie &lt;em&gt;wi&#281;cej ni&#380; jednej&lt;/em&gt;) instancjach za po&#347;rednictwem np. Mongrela lub FastCGI. Za to niestety bardzo niewielu zdaje sobie spraw&#281; z problem&#243;w, jakie wielo&#347;&#263; instacji przysparza - a tak&#380;e, jak wiele wygodnych, skr&#243;towych metod w Rails jest potencjalnie niebezpiecznych.&lt;/p&gt;

&lt;h3&gt;Houston, czy naprawd&#281; mamy problem?&lt;/h3&gt;

&lt;h4&gt;Przyk&#322;ad pierwszy: unikalno&#347;&#263;&lt;/h4&gt;

&lt;p&gt;Na pocz&#261;tek we&#378;my naprawd&#281; trywialny przyk&#322;ad:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;category = Category.find_or_create_by_name( "Tips and tricks" )
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Czytelne? Tak. Eleganckie? Tak. Mo&#380;e spowodowa&#263; jaki&#347; b&#322;&#261;d? Niestety odpowied&#378; r&#243;wnie&#380; brzmi &amp;#8220;tak&amp;#8221;. Zobaczmy, jakie kwerendy SQL &amp;#8220;lec&#261; do bazy&amp;#8221; po wywo&#322;aniu powy&#380;szej linii kodu:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT * FROM categories WHERE (categories."name" = 'Tips and tricks') LIMIT 1
INSERT INTO categories ("name") VALUES('Tips and tricks')
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Na razie dobrze - nie ma si&#281; czego czepi&#263;. A teraz za&#322;&#243;&#380;my, &#380;e mamy dwa Mongrele i &#380;e inkryminowana linia kodu jest wykonywana przez oba (np. dlatego, &#380;e u&#380;ytkownik znudzi&#322; si&#281; czekaniem na odpowied&#378; serwera i nacisn&#261;&#322; przycisk &amp;#8220;Submit&amp;#8221; formularza tworz&#261;cego kategori&#281; drugi raz). Oto jak mog&#261; wygl&#261;da&#263; zapytania SQL (cyfra na pocz&#261;tku to numer serwera):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; 1: SELECT * FROM categories WHERE (categories."name" = 'Tips and tricks') LIMIT 1
 2: SELECT * FROM categories WHERE (categories."name" = 'Tips and tricks') LIMIT 1
 2: INSERT INTO categories ("name") VALUES('Tips and tricks')
 1: INSERT INTO categories ("name") VALUES('Tips and tricks')
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;i Mongrel pierwszy zwraca b&#322;&#261;d, bo pr&#243;ba stworzenia kategorii o nazwie ju&#380; istniej&#261;cej spowoduje obiekcje naszego indeksu UNIQUE na polu &lt;em&gt;name&lt;/em&gt; (bo przecie&#380; zawsze zak&#322;adamy takie indeksy, prawda? :).&lt;/p&gt;

&lt;h4&gt;Przyk&#322;ad drugi: zmiana atrybutu&lt;/h4&gt;

&lt;p&gt;Za&#322;&#243;&#380;my, &#380;e mamy model &lt;code&gt;Post&lt;/code&gt; (tak, wiem, niezbyt to oryginalne :)) z atrybutem &lt;code&gt;view_count&lt;/code&gt;. Dzia&#322;anie zwi&#281;kszaj&#261;ce licznik ods&#322;on posta na poziomie Rails wygl&#261;da typowo mniej wi&#281;cej tak:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;post = Post.find 1
post.update_attribute( :view_count, post.view_count + 1 )
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;co t&#322;umaczy si&#281; na nast&#281;puj&#261;ce linie SQL:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT * FROM post WHERE id = 1
UPDATE post SET view_count = 1 WHERE id = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
    &lt;p&gt;Wstawka nie na temat - co sprytniejsi czytelnicy w tym momencie zauwa&#380;&#261;, &#380;e wystarczy&#322;oby po prostu troch&#281; si&#281; pobawi&#263; i wywo&#322;a&#263; &amp;#8220;r&#281;cznie&amp;#8221; SQL: UPDATE post SET view_count = view_count + 1 WHERE id = 1, ale po pierwsze wymaga to r&#281;cznej zabawy (a nie po to piszemy w Rails, &#380;eby cokolwiek robi&#263; r&#281;cznie, prawda?), a po drugie nie pasuje mi to do koncepcji przyk&#322;adu :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Teraz ponownie we&#378;my nasze 2 Mongrele i obejrzyjmy zapytania SQL powsta&#322;e wskutek oparcia &#322;okcia naszego u&#380;ytkownika na przycisku F5 (oczywi&#347;cie gdy otwarta jest przegl&#261;darka na stronie z postem), co skutkuje seri&#261; szybko nast&#281;puj&#261;cych po sobie request&#243;w zwi&#281;kszaj&#261;cych nasz licznik &lt;code&gt;view_count&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;1: SELECT * FROM post WHERE id = 1
2: SELECT * FROM post WHERE id = 1
1: UPDATE post SET view_count = 1 WHERE id = 1
2: UPDATE post SET view_count = 1 WHERE id = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tym razem Jedynka wykona&#322;a UPDATE przed Dw&#243;jk&#261;, ale efekt jest b&#322;&#281;dny - mimo 2 ods&#322;on postu nasz licznik ma warto&#347;&#263; 1! Dlaczego? Po prostu oba Mongrele pobra&#322;y post z poprzedni&#261; warto&#347;ci&#261; licznika (czyli 0), zwi&#281;kszy&#322;y go w pami&#281;ci i zaktualizowa&#322;y w bazie. Nie wiedzia&#322;y jednak biedaczki sprawy o sobie nawzajem, wi&#281;c jeden z nich mia&#322; &lt;em&gt;de facto&lt;/em&gt; nieaktualn&#261; warto&#347;&#263; licznika.&lt;/p&gt;

&lt;h3&gt;To si&#281; naprawd&#281; zdarza&lt;/h3&gt;

&lt;p&gt;Powy&#380;sze przyk&#322;ady nie s&#261; wyssane z palca, ale trafiaj&#261; si&#281; w rzeczywistych, powa&#380;nych aplikacjach. Lista potencjalnie niebezpiecznych sytuacji jest o wiele d&#322;u&#380;sza, np. eleganckie &lt;code&gt;validates_uniqueness_of&lt;/code&gt; wbrew pozorom nie gwarantuje nam unikalno&#347;ci, je&#347;li nie zadbamy o ni&#261; sami na poziomie bazy danych; praktycznie wszystkie sytuacje wymagaj&#261;ce unikalno&#347;ci czegokolwiek nara&#380;aj&#261; nas przynajmniej na b&#322;&#281;dy ze strony bazy danych (zamiast &#322;adnych i eleganckich komunikat&#243;w Railsowych); itd.&lt;/p&gt;

&lt;h3&gt;&amp;#8220;I co teraz, i co teraz, co z denatem?&amp;#8221;&lt;/h3&gt;

&lt;p&gt;Mo&#380;liwych rozwi&#261;za&#324; jest kilka - najpierw om&#243;wi&#281; najog&#243;lniejsze, a potem przejd&#281; do takich, kt&#243;re maj&#261; zastosowanie w specyficznych wypadkach. Na pewno zdecydowanie najgorsz&#261; mo&#380;liwo&#347;ci&#261; jest zignorowanie problemu z nadziej&#261;, &#380;e &amp;#8220;jako&#347; to b&#281;dzie&amp;#8221;. Uwierzcie mi - nie b&#281;dzie, a je&#347;li nie zadbali&#347;my o w&#322;a&#347;ciwie indeksy, klucze itp. na poziomie bazy danych, to w ko&#324;cu obudzimy si&#281; z r&#281;k&#261; w nocniku i np. pi&#281;cioma kategoriami o nazwie &amp;#8220;Tips and tricks&amp;#8221; mimo obecno&#347;ci linii &lt;code&gt;validates_uniqueness_of :name&lt;/code&gt;&amp;#8230;&lt;/p&gt;

&lt;h4&gt;Transakcja i lock&lt;/h4&gt;

&lt;p&gt;Zdecydowanie najbardziej og&#243;lnym, a cz&#281;sto r&#243;wnie&#380; najprostszym rozwi&#261;zaniem jest u&#380;ycie transakcji, a nast&#281;pnie zapewnienie, &#380;e b&#281;d&#261; si&#281; one wykonywa&#263; po kolei (a nie jednocze&#347;nie). Mo&#380;na to osi&#261;gn&#261;&#263; ustawiaj&#261;c poziom izolacji transakcji bezpo&#347;rednio w naszej bazie danych, ale jako &#380;e brzydzimy si&#281; (przynajmniej na razie) brzydkimi s&#322;owami typu &lt;code&gt;set transaction isolation level serializable&lt;/code&gt; oraz umieszczaniem SQL bezpo&#347;rednio w kodzie naszej aplikacji poka&#380;&#281; spos&#243;b bardziej &amp;#8220;railsowaty&amp;#8221;.&lt;/p&gt;

&lt;p&gt;We&#378;my drugi przyk&#322;ad (ten z &lt;code&gt;Post&lt;/code&gt; i &lt;code&gt;view_count&lt;/code&gt;). Podany kod z u&#380;yciem transakcji i locka wygl&#261;da&#322;by mniej wi&#281;cej tak:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Post.transaction do
    post = Post.find( 1, :lock =&amp;gt; true )
    post.update_attribute( :view_count, post.view_count + 1 )
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I ju&#380;. Niepoliczona ods&#322;ona posta ju&#380; si&#281; nam nie zdarzy. Prawda, &#380;e proste?&lt;/p&gt;

&lt;p&gt;Niestety, obieca&#322;em, &#380;e rozwi&#261;zanie b&#281;dzie og&#243;lne, a powy&#380;szy kod nie daje si&#281; zastosowa&#263; do pierwszego przyk&#322;adu z pocz&#261;tku artyku&#322;u (&lt;code&gt;Category.find_or_create...&lt;/code&gt;), poniewa&#380; nie mamy rekordu, kt&#243;ry potencjalnie mogliby&#347;my zalockowa&#263; (tzn. kategoria zostanie potencjalnie stworzona, a na razie by&#263; mo&#380;e nie istnieje). Na szcz&#281;&#347;cie z regu&#322;y nasz obiekt nie wyst&#281;puje sam, ale jest powi&#261;zany z innymi obiektami ju&#380; istniej&#261;cymi (a co istnieje, mo&#380;e zosta&#263; potraktowane lockiem :). Za&#322;&#243;&#380;my, &#380;e kategoria, kt&#243;r&#261; chcemy znale&#378;&#263; lub stworzy&#263; ma by&#263; przypisana do postu. Wtedy nasz kod m&#243;g&#322;by wygl&#261;da&#263; np. tak:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;post = Post.find 1
transaction do
    post.lock!
    category = Category.find_or_create_by_name( "Tips and tricks" )
    ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tutaj niew&#261;tpliwe przyda si&#281; kilka s&#322;&#243;w wyja&#347;nienia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Post.find&lt;/code&gt; jest umieszczone przed blokiem transakcji, poniewa&#380; nie istnieje konieczno&#347;&#263; posiadana aktualnych informacji o jakim&#347; atrybucie posta (jak by&#322;o w przypadku &lt;code&gt;view_count&lt;/code&gt;) - nawet je&#347;li ustawiamy np. &lt;code&gt;post.category_id&lt;/code&gt;, to poprzednia warto&#347;&#263; nas w sumie nie obchodzi&lt;/li&gt;
&lt;li&gt;u&#380;y&#322;em &lt;code&gt;transaction&lt;/code&gt; zamiast &lt;code&gt;Post.transaction&lt;/code&gt;, ale przyznam si&#281; bez bicia, &#380;e nie widz&#281; specjalnej r&#243;&#380;nicy mi&#281;dzy nimi. Prawdopodobnie zauwa&#380;y&#322;bym, gdyby jeden z modeli bior&#261;cych udzia&#322; by&#322; w innej bazie danych :) Ja osobi&#347;cie stosuj&#281; tak&#261; zasad&#281;: je&#347;li transakcja dotyczy tylko jednego modelu, to stosuj&#281; np. &lt;code&gt;Post.transaction&lt;/code&gt;, je&#347;li kilku r&#243;&#380;nych - go&#322;e &lt;code&gt;transaction&lt;/code&gt;. Ale nie upieram si&#281;, &#380;e to idealne rozwi&#261;zanie i jestem otwarty na sugestie :)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;clue&lt;/em&gt; ca&#322;ej sprawy (i r&#243;&#380;nic&#261; w por&#243;wnaniu z &amp;#8220;prostym&amp;#8221; lockowaniem przedstawionym wy&#380;ej) jest fakt, &#380;e kod zadzia&#322;a dobrze nawet je&#347;li nie zmienamy nic w obiekcie &lt;code&gt;post&lt;/code&gt;! Innymi s&#322;owy je&#347;li w kodzie w miejsce kropek nie wstawimy nic, to transakcje b&#281;d&#261; i tak wykonywane po kolei. Dzieje si&#281; tak dlatego, &#380;e metoda &lt;code&gt;lock!&lt;/code&gt; powoduje wykonanie odpowiedniej, specyficznej dla danej bazy danych operacji (np. w Postgresie b&#281;dzie to co&#347; w stylu &lt;code&gt;SELECT * FROM post WHERE id = 1 FOR UPDATE&lt;/code&gt;), kt&#243;ra spowoduje, &#380;e inne transakcje chc&#261;ce wykona&#263; t&#281; sam&#261; kwerend&#281; b&#281;d&#261; musia&#322;y poczeka&#263;, a&#380; pierwsza transakcja si&#281; zako&#324;czy. Obiekt zalockowany w ten spos&#243;b zostanie odblokowany automatycznie po zako&#324;czeniu transakcji.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opisana metoda jest niew&#261;tpliwie najbardziej uniwersalna, natomiast czasami rzeczywi&#347;cie nie da si&#281; znale&#378;&#263; obiektu, kt&#243;ry mogliby&#347;my zalockowa&#263; (mo&#380;na w tym celu u&#380;y&#263; np. aktualnie zalogowanego u&#380;ytkownika, bo raczej nie wykona on zbyt wiele operacji jednocze&#347;nie, ale u&#380;ycie np. kategorii u&#380;ywanej przy co drugiej akcji fatalnie wp&#322;ynie na wydajno&#347;&#263;). Wtedy pozostaje nam u&#380;ycie jednej z pozosta&#322;ych, mniej uniwersalnych, za to czasami szybszych metod.&lt;/p&gt;

&lt;h4&gt;Uproszczenie do jednego zapytania SQL&lt;/h4&gt;

&lt;p&gt;O tym by&#322;o ju&#380; w drugim przyk&#322;adzie. Zamiast:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT * FROM post WHERE id = 1
UPDATE post SET view_count = 1 WHERE id = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;mo&#380;emy u&#380;y&#263;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;UPDATE post SET view_count = view_count + 1 WHERE id = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Trzeba to zrobi&#263; albo r&#281;cznie (nie lubimy&amp;#8230;) albo zrobi&#263; sobie metod&#281; w stylu:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def increase_attribute( attribute, step = 1 )
  self.class.connection.execute( self.class.sanitize_sql(["UPDATE #{self.class.table_name} SET #{attribute} = #{attribute} + ? WHERE id = ?", step, self.id]) )
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(tak naprawd&#281;, &#380;eby unikn&#261;&#263; tego powtarzanego &lt;code&gt;self.class&lt;/code&gt;, robi&#281; z tego z regu&#322;y metod&#281; klasy).&lt;/p&gt;

&lt;p&gt;Polecam t&#281; metod&#281;, bo jej skutkiem jest bardzo prosty (i szybki) kod SQL.&lt;/p&gt;

&lt;h4&gt;U&#380;ycie funkcji/procedury bazodanowej&lt;/h4&gt;

&lt;p&gt;Ta metoda jest do&#347;&#263; ma&#322;o elegancka, ale czasami jest jedynym wyj&#347;ciem, kiedy chcemy stworzy&#263; nowy obiekt lub zupdateowa&#263; stary, a nie mamy &#380;adnego &amp;#8220;jelenia&amp;#8221; do zalockowania. Wr&#243;&#263;my do przyk&#322;adu z kategori&#261; - za&#322;&#243;&#380;my, &#380;e zamiast kategorii mamy model &lt;code&gt;SearchString&lt;/code&gt;, gdzie nie tylko przechowujemy wszystkie przeszukiwania naszych u&#380;ytkownik&#243;w, ale r&#243;wnie&#380; ilo&#347;&#263; ich wyst&#261;pie&#324;. W takim wypadku funkcja dla PostgreSQL mog&#322;aby wygl&#261;da&#263; nast&#281;puj&#261;co:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CREATE FUNCTION add_search_string(str varchar) RETURNS boolean AS $$
BEGIN
  LOCK TABLE search_strings IN ACCESS EXCLUSIVE MODE;
  UPDATE search_strings SET string_count = string_count + 1, updated_at = current_timestamp WHERE string = str;
  IF NOT FOUND THEN
    INSERT INTO search_strings (string, created_at, updated_at) VALUES (str, current_timestamp, current_timestamp);
  END IF;
  RETURN true;
END;
$$ LANGUAGE 'plpgsql';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Mam nadziej&#281;, &#380;e jest ona zrozumia&#322;a, cho&#263; niew&#261;tpliwie wyja&#347;ni&#263; nale&#380;y lini&#281; &amp;#8220;LOCK TABLE&amp;#8230;&amp;#8221;. Ot&#243;&#380; linia ta lockuje ca&#322;&#261; tabel&#281; (trzeba z tym uwa&#380;a&#263; - akurat w tym przyk&#322;adzie jedynie ta funkcja dobiera si&#281; do tabeli &lt;em&gt;search_strings&lt;/em&gt;, ale w innych wypadkach pozosta&#322;e transakcje b&#281;d&#261; czeka&#263;, a&#380; odblokujemy tabel&#281;), &#380;eby&#347;my mogli doda&#263;/zupdateowa&#263; naszego search_stringa bez ryzyka, &#380;e kto&#347; w tym czasie doda nam takiego samego stringa do tabeli.&lt;/p&gt;

&lt;p&gt;Wywo&#322;anie funkcji w Rails wygl&#261;da tak:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class SearchString &amp;lt; ActiveRecord::Base
  def self.store( string )
     self.connection.execute( sanitize_sql( ["SELECT add_search_string (?)", string] ))
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nie polecam tej metody, poniewa&#380; jest skomplikowana, ale czasami naprawd&#281; nie mamy innego wyj&#347;cia. I jeszcze 2 uwagi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;w PostgreSQL w funkcjach/procedurach nie dzia&#322;aj&#261; transakcje, wi&#281;c nie liczcie na nie!&lt;/li&gt;
&lt;li&gt;w MySQL jest co&#347; o wiele wygodniejszego, czyli dyrektywa &lt;code&gt;REPLACE&lt;/code&gt;, kt&#243;ra tworzy nowy wiersz lub zmienia istniej&#261;cy&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Podsumowanie&lt;/h3&gt;

&lt;p&gt;Jak wida&#263;, w&#322;a&#347;ciwe rozwi&#261;zanie problem&#243;w wsp&#243;&#322;bie&#380;no&#347;ci nie jest tak proste, jak by&#347;my chcieli, ale na szcz&#281;&#347;cie nie tak skomplikowane, jak by&#347;my si&#281; obawiali :)&lt;/p&gt;

&lt;p&gt;Poniewa&#380; artyku&#322; si&#281; &amp;#8220;nieco&amp;#8221; rozr&#243;s&#322;, kwesti&#281; testowania kodu pod wzgl&#281;dem wsp&#243;&#322;bie&#380;no&#347;ci opisz&#281; wkr&#243;tce w osobnych artykule.&lt;/p&gt;</description>
      <pubDate>Thu, 31 Jan 2008 22:29:00 +0000</pubDate>
      <guid isPermaLink="false">urn:uuid:18a3ae79-c0b2-4bc2-b125-19a27bd4d226</guid>
      <comments>http://ruby-on-rails.com.pl/articles/2008/01/31/wspolbieznosc-sql#comments</comments>
      <category>Dobre praktyki</category>
      <category>rails</category>
      <category>ruby</category>
      <category>concurrency</category>
      <category>wsp&#243;&#322;bie&#380;no&#347;&#263;</category>
      <category>mongrel</category>
      <category>lock</category>
      <category>race</category>
      <category>unique</category>
      <category>unikalno&#347;&#263;</category>
      <link>http://ruby-on-rails.com.pl/articles/2008/01/31/wspolbieznosc-sql</link>
    </item>
    <item>
      <title>Rails 2.0</title>
      <description>&lt;p&gt;W zesz&#322;ym miesi&#261;cu pojawi&#322; si&#281; d&#322;ugo oczekiwany release Rails 2.0. Nie b&#281;d&#281; tutaj wymienia&#322; wszystkich nowych &amp;#8220;ficzer&#243;w&amp;#8221;, bo je mo&#380;na znale&#378;&#263; chocia&#380;by &lt;a href="http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done"&gt;tutaj&lt;/a&gt;, natomiast postaram si&#281; odpowiedzie&#263; na pytanie &amp;#8220;Czy warto migrowa&#263; na now&#261; wersj&#281;?&amp;#8221;.&lt;/p&gt;

&lt;h2&gt;Nowe aplikacje&lt;/h2&gt;

&lt;p&gt;W przypadku nowych aplikacji odpowied&#378; jest prosta - na pewno warto (cho&#263; w tym przypadku ci&#281;&#380;ko jest m&#243;wi&#263; o &amp;#8220;migracji&amp;#8221;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails 2.0 s&#261; po prostu lepsze (wydajno&#347;ciowo i architektonicznie, nie licz&#261;c nowych funkcjonalno&#347;ci)&lt;/li&gt;
&lt;li&gt;nale&#380;y si&#281; spodziewa&#263;, &#380;e nowe wersje plugin&#243;w b&#281;d&#261; tworzone dla nowych wersji Rails, wi&#281;c u&#380;ywanie Rails 2.0 pozwoli na unikni&#281;cie problem&#243;w z kompatybilno&#347;ci&#261;&lt;/li&gt;
&lt;li&gt;zmiany nie s&#261; na tyle wielkie, &#380;eby trzeba by&#322;o si&#281; czego&#347; na nowo uczy&#263;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Nierozwijane aplikacje&lt;/h2&gt;

&lt;p&gt;W przypadku aplikacji istniej&#261;cych, lecz ju&#380; nie rozwijanych odpowied&#378; r&#243;wnie&#380; jest prosta - nie ma sensu migrowa&#263; na Rails 2.0, bo zyski b&#281;d&#261; niewielkie, a wysi&#322;ek mo&#380;e by&#263; spory. Jedynym wyj&#261;tkiem jest sytuacja, kiedy istnieje ryzyko, &#380;e Rails 1.x zniknie z serwera, na kt&#243;rym jest zainstalowana nasza aplikacja :)&lt;/p&gt;

&lt;h2&gt;Pozosta&#322;e aplikacje&lt;/h2&gt;

&lt;p&gt;Tutaj nie da si&#281; ju&#380; odpowiedzie&#263; w dw&#243;ch zdaniach. Za migracj&#261; przemawiaj&#261; na pewno argumenty z sekcji &amp;#8220;Nowe aplikacje&amp;#8221;. Przeciwko da si&#281; te&#380; jednak co&#347; znale&#378;&#263;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;niekt&#243;re rzadko aktualizowane lub &#378;le napisane pluginy mog&#261; przesta&#263; dzia&#322;a&#263;&lt;/li&gt;
&lt;li&gt;mo&#380;e zaistnie&#263; konieczno&#347;&#263; dokonania wielu zmian s&#322;abo poddaj&#261;cych si&#281; automatyzacji (np. zmiany w &lt;em&gt;named routes&lt;/em&gt; typu &lt;code&gt;admin_new_post_path&lt;/code&gt; =&gt; &lt;code&gt;new_admin_post_path&lt;/code&gt; - takich &#347;cie&#380;ek bywa w aplikacji kilkadziesi&#261;t lub wi&#281;cej)&lt;/li&gt;
&lt;li&gt;nowe funkcjonalno&#347;ci niewiele nam daj&#261;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Poni&#380;sza lista ma na celu pomoc w podj&#281;ciu decyzji o migracji lub jej braku. Generalna zasada jest taka, &#380;e im wi&#281;cej odpowiedzi twierdz&#261;cych, tym bardziej zaleca&#322;bym przej&#347;cie na Rails 2.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tworzysz (lub chcesz tworzy&#263;) aplikacje zgodne z REST (czyli u&#380;ywasz &lt;em&gt;resources&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;u&#380;ywasz &lt;em&gt;multiviews&lt;/em&gt;, tzn. serwujesz strony jako HTML, RSS, TXT itd.&lt;/li&gt;
&lt;li&gt;stosujesz r&#243;&#380;ne metody generacji tre&#347;ci, np. erb, xml builder itd.&lt;/li&gt;
&lt;li&gt;pozwalasz u&#380;ytkownikom twojej aplikacji tworzy&#263; w&#322;asn&#261; tre&#347;&#263; zawieraj&#261;c&#261; potencjalne niebezpieczne elementy np. HTML&lt;/li&gt;
&lt;li&gt;stosujesz zasad&#281; trzymania w sesji jak najmniejszej ilo&#347;ci danych&lt;/li&gt;
&lt;li&gt;twoja aplikacja obs&#322;uguje bardzo du&#380;o sesji&lt;/li&gt;
&lt;li&gt;u&#380;ywasz serializacji obiekt&#243;w ActiveRecord&lt;/li&gt;
&lt;li&gt;masz problemy z zale&#380;no&#347;ciami pomi&#281;dzy pluginami&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Przed sam&#261; migracj&#261; na Rails 2.0 nale&#380;y niew&#261;tpliwie zainstalowa&#263; ostatni&#261; wersj&#281; z serii 1.x (1.2.5) i zaj&#261;&#263; si&#281; pojawiaj&#261;cymi si&#281; warningami (je&#347;li nie pojawi&#261; si&#281; &#380;adne, to migracja nie powinna by&#263; zbyt k&#322;opotliwa).&lt;/p&gt;</description>
      <pubDate>Sun, 27 Jan 2008 07:37:00 +0000</pubDate>
      <guid isPermaLink="false">urn:uuid:5b8b38c7-740e-41fe-8bd6-708164dfafb6</guid>
      <comments>http://ruby-on-rails.com.pl/articles/2008/01/27/czy-warto-migrowac-na-rails-2-0#comments</comments>
      <category>Nowo&#347;ci</category>
      <category>2.0</category>
      <category>czy_warto</category>
      <category>rails</category>
      <category>ruby</category>
      <category>zmiany</category>
      <link>http://ruby-on-rails.com.pl/articles/2008/01/27/czy-warto-migrowac-na-rails-2-0</link>
    </item>
    <item>
      <title>Witam na moim blogu</title>
      <description>&lt;p&gt;Niniejszym og&#322;aszam oficjalne otwarcie mojego blogu o RubyOnRails. Dalsze informacje znajduj&#261; si&#281; na stronie &lt;a href="http://www.ruby-on-rails.com.pl/pages/about"&gt;About&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Jest to m&#243;j pierwszy blog w &#380;yciu, wi&#281;c prosz&#281; o wyrozumia&#322;o&#347;&#263; :)&lt;/p&gt;</description>
      <pubDate>Sat, 26 Jan 2008 16:28:00 +0000</pubDate>
      <guid isPermaLink="false">urn:uuid:19232c49-8c23-4e09-9d85-faa5d145d914</guid>
      <comments>http://ruby-on-rails.com.pl/articles/2008/01/26/witam#comments</comments>
      <link>http://ruby-on-rails.com.pl/articles/2008/01/26/witam</link>
    </item>
  </channel>
</rss>
