Čtete statickou verzi tohoto blogu. Nové články autora nalezenete v angličtině na blogu Restafari.

Ruby on Rails a revoluce ve vývoji pro web. Dokončení

Kategorie: Rails, Web design

Publikováno 19. 08. 2007 v 16:57

23 komentářů (poslední napsal/a Petr Krontorad)


Poznámka editora: Autor tohoto blogu neočekával komplikace při dokončování závěrečného článku seriálu. Stihnul se mezitím oženit a stihnul pomoci kamarádovi s administračním rozhraním pro web ve Flashi :) Jak jinak než v Rails. Takže se můžete těšit o tutoriál o tom, jak v Rails zprovoznit podporu pro Flash Remoting. Jako aktuální odčinění naleznete u tohoto článku malou ukázkovou aplikaci, která demonstruje všechny ty techniky, triky a strategie popsané v dosavadních čtyřech článcích.

V předchozích částech seriálu jsme se seznámili se základními principy frameworku Ruby on Rails — s návrhovým vzorem ActiveRecord a s konvencemi, které Rails extrahují ze zkušeností s vývojem webových aplikací (a zpětně je na něj aplikují). Tyto části frameworku silně inspirovaly mnoho dalších klonů (identické pojetí modelů a vztahu mezi controllery a views najdete např. v Cake PHP). V tomto článku náš úvodní seriál dokončíme výkladem o tom, co je v Rails z pohledu vývojáře, jeho produktivity a radosti při vývoji, ojedinělé.

Rozšíření a pomocné metody

Jak jsme viděli v předchozím článku o propojení controllerů a views, principy Rails vycházejí z jednoduchých pravidel a konvencí webového vývoje: předej identifikátor nějakého objektu určitému controlleru a prostřednictvím jeho metody s tímto objektem něco udělej. Název šablony odpovídá názvu metody. Název layoutu odpovídá názvu controlleru. A tak dále.

Podobných opakujících se činností najdeme při vývoji nekonečné množství: neustále musíme formátovat datum nebo čas a počítat rozdíly mezi nimi, formátovat velikost souborů a jiná „ošklivá“ čísla, vypisovat varovná hlášení pro položky formulářů, generovat opakující se části kódu se složitější logikou, … Nepřekvapí nás proto, když v Rails najdeme jak velké množství rozšíření pro nejčastější případy takových úkolů, tak transparentní mechanismus pro integrování rozšíření vlastních. Rails obsahují dvě základní rozšíření: rozšíření jazyka Ruby (Core Extensions) a pomocné metody (helpers) obsažené v modulu Action View.

Rozšíření jazyka Ruby doplňují třídy Array, Date, Hash, Numeric a String o „pohodlné“ metody (convenience methods), které zjednodušují často se opakující operace — typicky např. zjištění, zda je číslo sudé či liché, které využijeme třeba pro střídání barvy pozadí řádků tabulky:

1.even?
=> false

Podobně pro definice velikosti souborů můžeme použít metody pracující s „lidštějšími“ tvary než je neustálé násobení a dělení číslem 1024 a jeho násobky:

10.megabytes
=> 10485760

Důležité je, že tato rozšíření jsou přidána přímo do Ruby, metody even? nebo megabytes lze tedy volat pro všechna čísla.

Většina těchto rozšíření posiluje expresivní charakter Ruby — velmi často chceme kupříkladu vypsat položky pole (seznam kategorií nebo tagů) v čitelném, „neprogramátorském“ formátu: „Zařazeno do kategorií Apple, iPhone a Mac OS X“. Pohodlná metoda to_sentence pro objekt Array zařídí právě to:

['Hynek', 'Vilém', 'Jarmila'].to_sentence
=> "Hynek, Vilém a Jarmila"

Standardně Rails pochopitelně vypíšou „Hynek, Vilém and Jarmila“. Můžeme (jako obvykle) doplnit konfiguraci a napsat ['Hynek', 'Vilém', 'Jarmila'].to_sentence(:connector => 'a'), anebo použít lokalizační plugin, obsahující podporu i pro český jazyk.

Nepřekvapí nás, že nejvíce rozšíření nalezneme pro práci s datem a časem. Datová aritmetika („články za poslední měsíc“) a formátování data a času jsou jedny z nejotravnějších činností při programování. Pokud bychom tedy chtěli vypsat seznam úkolů pro tento měsíc, napsali bychom:

@TodoItems = TodoItem.find( :all, 
                            :conditions => "due_date > '#{Time.now.at_beginning_of_month.to_s(:db)}' AND due_date < '#{Time.now.at_end_of_month.to_s(:db)}'", 
                            :order => 'due_date' )

Pohodlná metoda at_end_of_month nám vrací objekt třídy Time, Tue Jul 31 00:00:00 +0200 2007. Ten si jednoduše převedeme pomocí metody to_s na řetězec v SQL formátu (viz parametr :db), který můžeme využít v porovnávání. K zabudovaným formátům :db, :long, atd. můžeme pochopitelně přidat vlastní a využít je pak podle filosofie Don't repeat yourself v dalším kódu. Pro datovou aritmetiku obsahují Rails celou řadu rozšíření.

Pomocné třídy Rails se od rozšíření tříd Ruby liší — jsou automaticky dostupné pouze ve view, nikoli v controllerech a modelech. (Lze je ale direktivou include zpřístupnit i jim.) Slouží opět ke zjednodušení formátování a generování kódu, typicky formulářových prvků, odkazů (nám již dobře známá metoda link_to nebo výstižně pojmenovaná link_to_unless_current) či pro přístup k chybovým hlášením formulářových prků (error_message_on).

Jedna z nejefektivnějších pomocných metod je ta s výstižným, byť krkolomným názvem distance_of_time_in_words_to_now. Do jisté míry zavedla určitý návrhový vzor pro „polidštění“ výpisu data a času. Byl-li např. článek publikován včera, je většinou mnohem čitelnější říci o něm, že byl publikován včera, než že byl publikován 16. 7. 2007 v 18:15.

Zde je zřetelně vidět inspirační zdroj v mentalitě 37Signals: snaha zbavit se žargonu, péče věnovaná jazyku, kterým aplikace s uživateli „mluví“. Rozdíl mezi publikován 16. 7. 2007 v 18:15 a publikován včera se může zdát nepodstatný, nezasluhující si zvláštní pozornosti, můžeme jej odsoudit jen jako „vychytávku“. Z pohledu uživatele však tento rozdíl může být propastný

Jestliže s ním aplikace mluví o „Úkolech na zítra“ a „Změněno včera“, radikálně roste jeho důvěra v to, že má smysl ji používat, že mu rozumí — neboť s ním mluví „jeho“ jazykem a neplete se mu do cesty. Neschopnost takové empatie na straně tvůrců aplikací je jedním z hlavních důvodů, proč aplikace tak často uživatele stresují. (Empatie se dá přitom samozřejmě karikovat a přehánět do podoby přechytralých aplikací, které se stále ptají „Zdá se, že na ploše jsou nepoužívané ikony, chcete je odstranit?“)

Podobné pobídky k „neprogramátorskému“ výpisu dat nalezneme napříč všemi pomocnými metodami, zvláště s ohledem na čísla:

number_with_delimiter(1234567, " ")
=> "1 234 567"

number_with_precision(123.45678, 2)
=> "123.46"

number_to_human_size 10485760
=> "10 MB"
number_to_human_size 1572864
=> "1.5 MB"
number_to_human_size 125952 
=> "123 KB"

number_to_phone(123456789, :delimiter => ' ', :country_code => '420')
=> "+420 12 345 6789"

number_to_currency(12345, :unit => 'Kč ', :separator => ',', :delimiter => ' ')
=> "Kč 12 345,00"

Mnoho dalších tipů pro práci s helpery najdete v loňském Adventním kalendáři pro Rails.

Kromě zabudovaných pomocných metod v Rails nalezneme efektivní a transparentní mechanismus, jak vytvářet helpery vlastní. Najdeme je ve složce app/helpers, a její obsah může vypadat např. takto:

Rails Helpers Folder (/app/helpers/)

Soubor application_helper.rb obsahuje pomocné metody dostupné všem views (to nás nepřekvapí, protože víme, jaký význam v Rails mají komponenty pojmenované application: layout/application.rhtml, public/javascripts/application.js, atd.) Obsahuje-li naše aplikace controller pojmenovaný Todos (controllers/todos_controller.rb), pro „jeho“ views budou automaticky dostupné pomocné metody ze souboru helpers/todos_helper.rb. Samozřejmě, jako obvykle metody definované v helperech pro konkrétní controllery přetěžují ty definované v application_helper.rb přetěžují. A k čemu můžeme helpery nejlépe využít?

Typicky kupříkladu chceme, aby název stránky (<title>) byl na úvodní stránce webu jiný (Veterinární klinika Jezevčí Skála: Nejlepší péče o vaše miláčky. Očkování, vyšetření, ošetření psů, koček a jiných domácích zvířat) než na stránkách ostatních (Naše vybavení | Veterinární klinika Jezevčí Skála). Skládáme tedy název stránky z různých částí. Mohli bychom v šabloně zkusit něco jako

<title><%= if controller.action_name == 'index' then 'Veteri...' else "@page.title | Veteri..." end -%></title>

ale víme, že takový kód je velmi špatně čitelný a jako takový opět náchylný k chybám. Určitě by bylo efektivnější, kdybychom napsali prostě

<title><%= page_title -%></title>

a metodu page_title definovali právě pomocí helperu. Protože název stránky je (většinou) záležitostí celé aplikace, bude nejlépe, když tuto metodu nadefinujeme v globálním application_helper.rb například nějak takto:

def page_title
   if controller.action_name == 'index'
     "Veterinární klinika ... domácích zvířat"
   else
     "#{@page.title} | Veterinární klinika ..."
   end
 end

Obrovskou zásobů helperů obsahují Rails pro práci s formuláři, kdy usnadňují zejména práci s předvyplňováním hodnot u radio buttonů a u select prvků. To je však rozhodně téma na zvláštní článek, a proto se k nim ještě někdy v budoucnu vrátíme.

Nějaké ty pomocné rutiny, které do HTML vypíšou nějaký ten formulářový prvek má totiž leckterý framework. Rails však mají pro vývojáře přichystány také pomocníky velmi specifické. Na tři z nich se nyní podíváme.

Generátory

Lze říci, že počítačové programy obecně řeší dva typy úloh: buď nějaké výpočetně velmi složité úkoly (a fungují tedy jako „tisíc kalkulaček dohromady“) nebo opakující se a mechanické činnosti — případně, a to nejčastěji, kombinaci obou. Jak jsme viděli, vztahy mezi jednotlivými součástmi aplikace v Rails jsou postaveny na několika málo velmi dobře kodifikovaných principech. Není až takový problém si je zapamatovat — většinou stačí napsat to první, co člověka napadne. Bylo by však dosti neefektivní stále dokola vytvářet nové soubory s modely v příslušných složkách s příslušnými jmény a vepisovat do nich příslušné definice dědění tříd… Z tohoto důvodu obsahují Rails tzv. generátory kódu (generators). Ty nám pomohou při vytváření nových modelů, controllerů a jiných součástí aplikace. Podíváme se podrobněji na to, co nám umožňují.

Chceme-li v Rails vytvořit novou třídu objektů, tedy nový model, např. pro úkolovník, postačí, když vytvoříme ve složce app/model soubor todo_item.rb. Museli bychom ale jednak deklarovat jeho odvození z třídy Active Record, jednak bychom nějak a někde museli deklarovat jeho datovou strukturu („po staru“ řečeno sloupečky v tabulce), která je s ním těsně spjatá. V Rails nám totéž — a ještě více — vykoná jeden příkaz:

script/generate model TodoItem

Jeho zkrácený výstup vidíte níže:

create  app/models/todo_item.rb
create  db/migrate/001_create_todo_items.rb
create  test/unit/todo_item_test.rb
create  test/fixtures/todo_items.yml

Generátor nám nejenom vytvořil soubor s prázdným modelem Active Record, ale vygeneroval i soubory pro testování (které prozatím necháme stranou) a soubor se zajímavým názvem 001_create_todo_items. Ten obsahuje silnou zbraň Rails jménem migrace.

Migrace odstiňují vývojáře od definování datových struktur modelů (sloupečků v tabulce) pomocí SQL příkazů (CREATE TABLE ...) a mají dvě základní výhody: jsou čitelnější než SQL a tudíž méně náchylné k chybám v kódu a jsou přírůstkové a odvolatelné. Obsahují tak historii definic datové struktury aplikace, kterou lze vracet zpět, upravovat, jednoduše přidávat objektům nové atributy („sloupečky“).

Definice datové struktury tudíž může probíhat skutečně agilně, z hlediska znalosti věcí v daném okamžiku a vývojář se nemusí stresovat tím, jestli náhodou nepojmenoval nějaký sloupeček špatně nebo jestli mu (bože!) v tom hezkém obrázku propojených tabulek náhodou některý nechybí a „zákazník to pak bude chtít…“

Typická migrace pro náš model s úkoly může pak vypadat např. takto:

class CreateTodoItems < ActiveRecord::Migration
  def self.up
    create_table :todo_items do |t|
      t.column :title, :string
      t.column :due_date, :date
      t.column :completed, :boolean

      t.column :position, :integer
      t.column :created_at, :datetime
    end
  end

  def self.down
    drop_table :todo_items
  end
end

Vidíme, že se jedná o standardní třídu Ruby, obsahující metody up pro provedení migrace a down pro její „odvolání“. Číslo 001 v názvu souboru slouží pro identifikaci dané migrace. Migraci provedeme příkazem rake db:migrate pro změnu datové struktury aplikace na poslední definici v pořadí, příkazem rake db:migrate VERSION=1 se vrátíme na datovou strukturu odpovídající souboru 001_....

Vidíme také, že definujeme objekt s atributy pro název, datum plánovaného dokončení a informací o tom, zda byl úkol splněn, a to ve velmi srozumitelných datových typech string, date a boolean. (Rails obsahují abstraktní datový typ boolean velmi dobře popsaný v knize Agile Web Development With Ruby On Rails.)

Řekněme, že v náhlém hnutí mysli se nám velmi znelíbí pojmenování pro atribut názvu úkolu, title. Chtěli bychom, aby se jmenoval (logičtěji) name a tento nedostatek nám v pokročilé hodině téměř znemožňuje programovat dále. Je třeba to napravit, a to co nejdříve. A nepřijít přitom o všechna data, která již databáze obsahuje — takže na nějaké to DROP TABLE... můžeme zapomenout. Generátor migrace obsažený v Rails nám pomůže právě v takové situaci. Jediné, co musíme udělat, je vymyslet srozumitelný název pro migraci a vyplnit údaje o tom, jak se má příslušný sloupeček příslušné tabulky přejmenovat:

script/generate migration ChangeAtrTitleOfTodoItemToName
=> create  db/migrate/002_change_atr_title_of_todo_item_to_name.rb

Vygenerovaný soubor pak obsahuje prázdnou třídu migrace, do níž doplníme jen těla metod up a down.

class ChangeAtrTitleOfTodoItemToName < ActiveRecord::Migration
  def self.up
    # rename_column :table_name, :column_name, :new_column_name
    rename_column :todo_items, :title, :name
  end

  def self.down
    rename_column :todo_items, :name, :title
  end
end

Příkazem rake db:migrate a restartem aplikace pak provedeme migraci na tuto definici. Z tohoto důvodu nečekejte u Rails aplikací žádné SQL dumpy apod. Ty případně využijete jen pro přenášení dat — nikoliv datových struktur — mezi databázemi.

Podobně snadno můžeme Rails nechat vytvořit soubory pro určitý controller (např. Article) a jeho metody (např. index, list a show):

script/generate controller Article index list show

create  app/controllers/article_controller.rb
create  test/functional/article_controller_test.rb
create  app/helpers/article_helper.rb
create  app/views/article/index.rhtml
create  app/views/article/list.rhtml
create  app/views/article/show.rhtml

Vidíme, že generátor vytvořil soubory pro samotný controller, jeho helper a test, stejně jako views pro metody, které jsme uvedli při jeho volání. V postupně vznikajících IDE pro Rails nalezneme tyto generátory obalené grafickým rozhraním, jejich funkce je ale totožná a možnosti stejné.

Konzole

Podobná specialita Rails, která se též odehrává v příkazové řádce, je konzole aplikace, která se spouští příkazem script/console. Umožňuje nám pracovat s živou, běžící aplikací v prostředí terminálu. Ve fázi psaní kódu i pro rychlý pohled na data aplikace je její pomoc neocenitelná: umí číst data z modelů, upravovat je a zase zapisovat, má přístup k helper metodám a v omezené míře i ke controllerům. Můžeme si tak vyzkoušet různá formátování výstupu za pomoci helperů, složitější operace s modely „nanečisto“, bez použití prohlížeče, neustálého znovunačítání stránky a vypisování různých „pomocných“ dumpů. Mnoho triviálních operací můžeme navíc provádět pouze přes konzoli a vůbec k nim nevytvářet grafické rozhraní v HTML a odpovídající controllery. Typickou takovou úlohou je např. změna hesla u nějaké velmi jednoduché aplikace bez složité struktury uživatelských práv:

script/console production

u = User.find_by_login('admin')
=> #<User:0x35a0ed0 @attributes={"name"=>"Administrator", ...
u.password = 'simsalabim'
=> "simsalabim"
u.save
=> true

A je to. Podobně neocenitelnou pomoc nám konzole poskytuje pro rychlý administrátorský zásah nebo rychlou kontrolu:

User.find_by_email('persona_non_grata@botmail.com').destroy!
=> true
User.find_all_by_is_logged_in(true).size
=> 21

Z pohledu vývojáře je zkrátka konzole jedna z nejužitečnějších věcí v Rails; nástroj, který šetří obrovské množství času a psaní různých podpůrných pseudo-metod controllerů a pseudo-views pro ladění výstupu aplikace. Všechny příklady Rails kódu v našich článcích jsou prováděny právě v konzoli. Implicitně je konzole spuštěna v development režimu aplikace — pokud bychom ji chtěli spustit nad „ostrými“ daty (což chceme dost často), musíme režim uvést při jejím spuštění: script/console production. Rozsáhlý seznam tipů a triků pro práci v konzoli naleznete na blogu Amy Hoy, Err the Blog a v příslušné epizodě Railscasts. Zde z nich uvedeme pouze dva nejužitečnější.

Při práci v konzoli zároveň často měníte kód modelu. Konzole však o změněném kódu neví. Abyste ji nemuseli stále ukončovat příkazem exit a znovu startovat, postačí znovu načíst aplikaci příkazem reload!. Druhým nedocenitelným tipem je zkratka pro zformátování výstupu do velmi čitelného YAML formátu — postačí příkaz uvést jediným písmenem y:

y TodoItem.find(:all)
--- 
- !ruby/object:TodoItem 
  attributes: 
    name: "Dopsat článek"
    completed: f
    id: "1"
    ...

Ba co víc. Znak podtržítko slouží jako zkratka po vyvolání výstupu předchozího příkazu. Můžeme tedy psát příkazy „jak jsme zvyklí“, a jen výstup, který nás zajímá, rychle převést na YAML, aniž bychom museli daný příkaz psát znova.

TodoItem.find(:all)
=> [#<TodoItem:0x362c91c @attributes={"name"=>"Dopsat článek"...]
y _
--- 
- !ruby/object:TodoItem 
  attributes: 
    name: "Dopsat \xC4\x8Dl\xC3\xA1nek"
        ...

Konzole dokonce obsahuje všechny vlastnosti terminálu, na které jste zvyklí: šipkami nahoru a dolů se pohybujete v historii příkazů, pomocí ctrl+R můžete v historii i vyhledávat, klávesa tab doplňuje příkazy — těm, kteří se terminálu nebojí, tedy poskytuje maximální komfort a efektivitu při budování a ladění aplikace.

Pluginy a komunita

Protože nejste sněhová vločka, je velká pravděpodobnost, že funkcionalitu, kterou potřebujete, potřeboval už dříve někdo před vámi. A ten někdo mohl být dokonce chytřejší než vy. Pro Rails existuje obrovské množství rozšíření formou pluginů, od těch určených pro specifické případy, po pluginy, které řeší obecné problémy téměř každé aplikace. Podívejme se na dva takové případy.

V aplikaci můžeme mít určitý uspořádaný seznam položek: stránek v menu, souborů ke stažení, úkolů v úkolovníku. Položky se mohou vypisovat v určitém pořadí (podle data, názvu, apod.) nebo podle manuálně zadaného pořadového čísla. Je standardním požadavkem ze strany klienta, aby řazení položek bylo možné „manuálně“ ovlivnit i u takových seznamů, které jsou řazeny dle data — např. v případě výpisu tiskových zpráv. Tuto funkčnost je třeba implementovat snad v každé webové aplikaci. Lze říci, že kde je nějaký podobný seznam, dříve či později se objeví požadavek na to, aby bylo možné jeho položky manuálně řadit. Nepřekvapí nás proto, že Rails obsahují standardně plugin s příznačným názvem acts_as_listTodoItem acts_as_list („Úkol se chová jako seznam“).

Každému modelu můžeme jednoduše nastavit, že se chová jako seznam:

class TodoItem < ActiveRecord::Base
    acts_as_list
end

Jediné, co musíme navíc udělat, je definovat v dané tabulce sloupeček s názvem position. Pro instance třídy TodoItem pak máme automaticky k dispozici metody k pohodlné manipulaci s položkami seznamu: move_higher, move_lower, move_to_top, higher_item a další. V controlleru si pak nadefinujeme jen jednoduché obslužné metody:

def move_higher
  if TodoItem.find(params[:id]).move_higher
    flash[:message] = "Položka byla posunuta výše v seznamu"
  else
    flash[:message] = "Bohužel, položku se nepodařilo posunout&hellip;"
  end
  redirect_to :action => 'list'
end

(Pro kompletní kód si stáhněte ukázkovou aplikaci z tohoto článku.)

Psát podobnou funkcionalitu „vlastnoručně“ by bylo naprostým plýtváním času. Právě v těchto případech Rails oceníte nejvíce: vždy, když si uvědomíte, kolika vlastností dosáhnete prostými deklaracemi typu acts_as_list.

Obsluha uspořádaného seznamu je ale pořád ještě slabý čaj proti práci se soubory nahrávanými do aplikace pomocí formulářů: ošetřením práv, zmenšováním obrázků, hlídáním správných datových typů, … Právě pro práci s uploady obsahují Rails plugin actsas_attachment, resp. jeho novější inkarnaci attachmentfu. Tento plugin si musíme do aplikace nainstalovat:

script/plugin install acts_as_attachment

Pro uploadovaná data si vytvoříme model a deklarujeme jeho chování:

class Photo < ActiveRecord::Base
  acts_as_attachment  :storage => :file_system, 
                      :max_size => 3.megabytes, 
                      :content_type => :image,
                      :resize_to => '800x>',
                      :thumbnails => { :small => '125>', :tiny => '25>' }                      
  validates_as_attachment
end

Vidíme, že definujeme způsob ukládání dat (na disku), maximální velikost (3 MB), povolený typ souborů (pouze obrázky) a necháváme je zmenšit na 800 pixelů na šířku. Zároveň vytváříme dva thumbnaily (small a tiny) o velikostech 125 a 25 pixelů na šířku. Na posledním řádku deklarujeme, že se má před uložením objektu provést validace — zda vůbec něco bylo nahráno, zda není nahrávaný dokument větší, než povolené tři megabyty a zda má správný mime-type.

Jediné, co musíme zajistit ve view, je to, aby se příslušný file input jmenoval korektně, tedy:

file_field :photo, :uploaded_data
=> <input id="photo_uploaded_data" name="photo[uploaded_data]" type="file" />

V controlleru pak jednoduše zavoláme:

@photo = Photo.create(params[:photo])

Což je jen zkratka pro případný zápis Photo.create(:uploaded_data => params['nahrany_soubor']). V případě, že data z formuláře odesíláme „zabalená” v kontejneru photo a nazvaná uploaded_data, vystačíme si s kratším zápisem. V každém případě pozorně sledujte log soubory (log/development.log a log/production.log), jestliže vám něco nefunguje – dozvíte se přesně, s jakými parametry voláte metody a pod jakým názvem vám uploadovaný soubor přichází.

Soubor pak máme uložen jak na disku, tak v databázi a můžeme k němu a k jeho atributům snadno přistupovat:

photo = Photo.find(:first)
=> #<Photo:0x3576c84 @attributes={"content_type"=>"image/jpeg", "size"=>"132716", ...
number_to_human_size photo.size
=> "129.6 KB"
photo.content_type
=> "image/jpeg"
photo.width
=> 500
photo.public_filename
=> "/photos/1/sample-image-01.jpg

Pokud si budete chtít výše uvedený Ruby kód vyzkoušet v Rails konzoli, nezapomeňte nejprve nalinkovat helpery pro čísla: include ActionView::Helpers::NumberHelper. Konzole je sama od sebe připojené nemá.

Na disku ukládá plugin actsasattachment dokumenty do podsložky pojmenované stejně jako tabulka pro daný model, v našem případě tedy photos, a pro každý dokument vytváří dále podsložky pojmenované podle jejich ID:

Složka, kam acts_as_attachment ukládá soubory

Ve view, kde chceme daný soubor zobrazit, pak jen jednoduše napíšeme:

<%= image_tag photo.public_filename(:small) # Malý thumbnail %>
<%= image_tag photo.public_filename         # Plná velikost %>

Jak vidíme, práce s uploady je díky pluginu actsas_attachment zcela jednoduchá. Rails však obsahují ve svých útrobách skrytou ještě jednu _vychytávku: třídu TestUploadedFile. Ta je primárně určena především k testování uploadů, jak její název napovídá. Můžeme ji ale skvěle využít k něčemu jinému: k automatizovanému importu dat z disku do naší aplikace.

Představme si, že programujeme fotogalerii, a fotografie máme na disku. Chceme využít plugin acts_as_attachment a mít všechny fotografie přístupné jako objekty v Rails. Jistě nebudeme stovku fotografií uploadovat jednu po druhé pomocí formuláře v HTML stránce. Napíšeme si importní metodu:

# class Photo
def self.import_photo file
    raise "PhotoNotFound (#{file})" unless File.exists? file
    self.create( :uploaded_data => ActionController::TestUploadedFile.new(file, 'image/jpeg') )
    print "."
end

Nyní nám jen stačí projít celý adresář a fotografie naimportovat:

# class Photo
def self.import_directory dir
    t = Time.now
    count ||= 0
    Dir.foreach(dir) do |file|
        # Trivialni kontrola vyhovujicich souboru
        if file.to_s.downcase.include? "jpg"
            Photo.import_file "#{dir}/#{file}"
            count += 1
        end
    end
    puts "\n-- Nahrál jsem #{@count} fotografií z adresáře #{dir} za #{Time.now-t} sekund\n\n"
end

Tyto metody nyní můžeme spustit pomocí Rails konzole, nebo je můžeme pouštět třeba periodicky pomocí cronu a script/runner. Můžeme tak pomocí dvou metod o sedmnácti řádcích naplnit databázi stovkami megabytů dat, která pak máme ve webové aplikaci přístupná jako objekty v databázi. Co to přesně znamená? Stačí jen trochu rozpálit fantazii a představit si, že pomocí callbacku before_save a funkce get_exif_by_entry RMagicku přečteme data třebas o fotoaparátu a můžeme si dělat podobně krásné statistky jako má Flickr… a s takovými hezkými grafy

Pluginy pro Rails a knihovny (standard libraries) nebo balíčky (rubygems) pro Ruby jsou totiž možná to úplně nejlepší na psaní aplikací v Rails. Máloco vás potěší tak jako to, když zjistíte, že funkčnost, kterou potřebujete, napsal už někdo před vámi, a možná dokonce i líp, než byste to udělali vy. Pluginy a gemy jsou takový svět Rails/Ruby v malém — základní infrastrukturu máte hotovou, nemusíte o ní přemýšlet a nemusíte ji programovat. Můžete se zabývat „detaily“, které celý svět tak obdivuje na Basecampu, Flickru, Joyent Connectoru. Můžete zkrátka řešit věci, které jsou z pohledu uživatelů vaší aplikace daleko důležitější než „prostá funkcionalita“. Tu očekávají automaticky — neohromíte je tím, že jim ukážete pár text fieldů ve formuláři, pomocí nichž mohou zadat novou fakturu do účetní aplikace. Pluginy, gemy a ostatní rozšíření vám šetří čas, který můžete věnovat úplně jiným věcem.

Například podobně standardní potřebou jako manipulace s položkami uspořádaného seznamu nebo upload souborů je i to, aby vám aplikace poslala e-mailem notifikaci, když dojde k nějaké chybě či výjimce. Pro Rails existuje výborný plugin Exception Notifier, který vám zašle vyčerpávající informaci o tom, jaká chyba v aplikaci nastala, při jakém requestu, jaký řádek kódu chybu způsobil. A vy se můžete zabývat sortováním takových mailů, nebo třeba jejich automatickým parsováním do ticket systému...

Co když ale potřebujete něco méně obvyklého? Chtěli byste například exportovat ActiveRecord recordsety do CSV formátu. A ejhle, on existuje pěkně napsaný plugin convertibleto_csv. Potřebujete provést request na nějaké URL na internetu a zpracovat je? Využijete už nám dobře známou knihovnu Net::HTTP_. Chcete použít jako datové úložiště třeba Google Spreadsheet? Stáhněte si knihovnu GData Ruby a polovinu práce máte hotovou.

Samozřejmě, ne všechno v Ruby a pro Ruby je ukázkou elegantního kódu. (I když ty nejhorší výstřelky bývají dost často portem z jiného jazyka.) Díky úspornosti a čitelnosti kódu v Ruby vám ale trvá neporovnatelně méně času přepsat části kódu podle „svého“.

Okolo Ruby a Rails však především existuje na celém světě přátelská, energická komunita programátorů, vývojářů a web designérů, takže pokud vládnete angličtinou, najdete na blozích a diskusních fórech spřízněných s Rails jedny z nejzajímavějších a nejtalentovanějších tvůrců dneška.

Ruby on Rails je jedním z nejpoutavějších duchovních výkonů v oblasti informačních technologií za poslední dekádu: zcela změnil zavedené postupy ve vývoji pro web, a za jeho vlak se dále připojují vagony Standardizace deploymentu, Integrace version-control, Výstup v různých formátech pomocí respond_to, Sdílené aplikační rozhraní aplikací — REST, Škálování aplikací, … Právem za Rails David Heinemeier Hansson obdržel cenu 2005 Open Source Best Hacker.

V současné době je již samotný kód Rails dílem mnoha a mnoha přispěvatelů, nikoliv solitérským geniálním činem. Právě účast na tomto „společném díle“, objevování netušených možností Ruby a Rails, integrování funkcí a vlastností, které byste si v jiných jazycích a frameworcích dvakrát rozmysleli, je to co přináší vývojářům v Rails takovou radost z práce.

Tento „nakažlivý optimismus“ lidí okolo Rails je možná jedním ze zdrojů nedůvěry a obav, které se v souvislosti s Rails stále dokola objevují. Práce má být přece otrava. Pro vývoj v Rails to ale tak úplně neplatí. Existuje-li něco srovnatelného s radostí ze hry (běžně chápané jako protiklad práce, neboť nevytváří žádnou hodnotu), je to psaní webové aplikace v Rails… A začnou-li vás Rails bavit doopravdy, můžete nejen začít opravovat a vylepšovat Rails samotné, ale také si nabrousit ostruhy v utkání nejlepších Rails hackerů.

O Rails už každém případě víte dost na to, abyste se mohli rozhodnout, zda-li vás zajímají a mohou vám přinést nějaký užitek. Tímto článkem jsme skončili náš úvod o nejdůležitějších principech Rails. V dalších článcích se podíváme podrobněji na syntaxi jazyka Ruby, abychom porozuměli všem jejím zákoutím, a budeme se věnovat konkrétním návodům a postupům v Rails. Mezi připravená témata patří práce s formuláři a Ajax v Rails, nasazení aplikací na serveru (deployment), implementace Flash Remoting a další. A samozřejmě též slíbený článek o Rails z pohledu projektových manažerů a vůbec „neprogramátorů“. Máte-li návrh na nějakou problematiku, o níž byste se chtěli dozvědět více, využijte komentáře k článku!

Obrovský dík tentokrát patří Jiřímu Kubíčkovi za chladnokrevnou asistenci při zprovozňování Rails, SQLite a RMagicku na Windows XP, aby si autor sám prošel trnitou cestu, již budou muset projít ti z čtenářů, kteří si budou chtít ukázkovou aplikaci rozběhat v tomto operačním systému.

 

Výukovou mini-aplikaci doprovázející tento seriál článků si můžete stáhnout, abyste si vše mohli vyzkoušet v pohodlí svého http://127.0.0.1:

Je vhodná i pro naprosté začátečníky v Rails, obsahuje celou řadu doporučených postupů při vývoji v Rails, a na 120 řádcích kódu pokrývá tato témata:

  • Použití metod find, update_attributes a toggle ActiveRecordu, definování vlastních metod pro třídu i instanci modelu
  • Práci s layouty, kombinaci scaffoldu v Rails a vlastních metod controllerů a views
  • Metody controllerů pro zobrazování i manipulaci s objekty, včetně ošetření chyb, přesměrování
  • Definování vlastních helperů
  • Ukázky embedded Ruby (ERB) v šablonách, ukázky volání parciálních šablon
  • Použití pluginu acts_as_list a využití jeho metod pro manipulaci s objekty v controllerech
  • Použití pluginu acts_as_attachment pro upload obrázků do aplikace a jejich zmenšování

Screenshot ukázkové aplikace

Aplikace vyžaduje jako databázi SQLite nebo MySQL, pro zmenšování obrázků pak ImageMagick a RMagick. Podrobný návod naleznete přibalený k aplikaci v doc/README_FOR_APP. Pokud se dostanete do nesnází, využijte komentářů k článku, diskusního fóra na Rubyonrails.cz nebo IRC kanálu #rubyonrails na irc.felk.cvut.cz.
 

~


23 komentářů

# 1
Martin Stiborský napsal před rokem:

Díky za opět perfektní článek. Rails a Ruby mě opět překvapují a hlavně inspirují k tvoření nových (obšhlehnutých :-)) fičůrek do mých Pythonních věcí, nějak se od toho hada neumím odtrhnout ...

# 2
Martin Stiborský napsal před rokem:

Ještě jsem zapoměl, gratuluju k svatbě !! Ta fotka kterou jsi linkoval je perfektní, moc se mi líbí !

# 3
embuck napsal před rokem:

Pripajam sa ku gratulacii, skvela fotka...

# 4
Karel Minařík napsal před rokem:

@Martin & @embuck: Dík :)

# 5
Filip napsal před rokem:

Úžasný a vyčerpávající článek. Díky! Zároveň se rád připojuji k předešlým komentátorům a gratuluji k svatbě.

Těším se na článek o spolupráci Rails + Flash. A neplánuješ náhodou nějaký malý článeček o Rails + Subversion? A vůbec programování Rails v týmu?

# 6
Karel Minařík napsal před rokem:

@Filip: Dík. :) Plánuji článek o deploymentu, tedy zprovozňování „ostré“ Rails aplikace, a to se dnes bez Subversion neobejde. Takže nějaký základní úvod o SVN lze rozhodně očekávat, včetně minimalistické informace o kooperaci více vývojářů.

Mezitím doporučuji rozhodně výborné shrnutí v angličtině: Basic Work Cycle.

# 7
jan napsal před rokem:

Opet skvely clanek, diky. Teprve nedavno jsem zjistil, jak je vyhodne pomoci konzole popr. script/runner delat datove databazove migrace. Cokoliv jen mirne slozitejsiho je v SQL ohromne nachylne k chybam, napsat totez spravne v Ruby otazka chvile.

# 8
kolisko napsal před rokem:

Dobry den,

chtel bych se zeptat, zda existuje nejaka ceska e-mailova konference o Ruby. Nasel jsem nejaky web-fora, ale ty neni mozny cist via email. Forum na builder.cz se mi nejevi jako uplne nejzivejsi a navic se tam neresi technicky problemy, ale spis ideologicky.

Diky kolisko

# 9
Karel Minařík napsal před rokem:

@kolisko:

O české e-mailové konferenci pro Ruby nevím. K Ruby/Rails však existují dvě webové konference: http://forum.rubyonrails.cz/ a http://rails-forum.cz/, kde určitě najdete spřízněné duše. Pro rychlejší komunikaci můžete také zkusit český IRC kanál: #rubyonrails na irc.felk.cvut.cz .

# 10
pavel krusek napsal před rokem:

Diky za dalsi skvely clanek. Rovnez gratuluji k svatbe, fotka je skutecne "artova" :)

K actsas_attachment: ja presel na update tohoto pluginu, attachmentfu. Ucinil jsem tak kvuli cropped images, tuto vec plugin nativne nepodporuje (ma pouze geometry string) a je treba tuto funkcionalitu dopsat. V actsas_attachment jsem to delal nejak pres callback metodu beforesave (aspon co si mlhave pamatuju, je to uz nejaky mesic). V attachment_fu je mozne velmi elegantne dosahnout teto funkcionality, sve reseni zde publikovat nebudu, nebot jsem narazil na lepsi na tomto blogu

Dale se pak muze hodit hromadne pregenerovani vsech thumbnails na jinou velikost ci pridani dalsich - pomoci metody createor_updatethumbnail, zde je navod

Jinak Karlovi dekuji za tip na TestUploadedFile, uz si pomoci toho delam funkcionalitu, ze uzivatel posle na server vsechny fotky v zipu, ktery se pak rozbali a obrazky se naimportuji :)

zatim

Pavel

# 11
pavel krusek napsal před rokem:

malem bych zapomnel - osetreni maleho bugu v attachment fu. Pri ulozeni thumbnailu je u thumbnailu v atributu size ulozena spatna velikost - stejna jako u originalu. Staci do rmagickprocessor.rb po ulozeni thumbnailu - metoda resize image pridat: self.size = File.size(self.temppath)

# 12
pavel krusek napsal před rokem:

Omlouvam se za ty kurzivy, markdown vidim poprve a nevedel jsem, ze podtrzitko vyrobi kurzivu :)

# 13
Karel Minařík napsal před rokem:

@pavel:

Dík za tipy, já docela marně pátral v čem se attachmentfu_ doopravdy liší od actsasattachment. A aaa ma rozhodně popisnější název. :)

TestUploadedFile je rozhodně zásadní záležitost, já bych bez toho teď jinak vůbec nemohl rozhýbat už dva projekty...

Karel

# 14
Karel Minařík napsal před rokem:

@pavel:

Jo, ještě ad Markdown -- je občas docela psychotický, jen co je pravda. To se ale dá přežít v klidu. :)

# 15
Pavel Krusek napsal před rokem:

Zdravim a mel bych dotaz.

Pouzil jsem pro jednojazykovy (cesky) web zde doporuceny 'siplified' plugin a stahl si cesky lokalizacni file (diky Karle za nasdileni!) Nicmene mam s tim jeden problem: pred nasazenim pluginu v poradku fungovalo odeslani datumu, viz:




35443caa79b0a9784776f9c148dfb3e4





47beb0b9bed1212f4fa4dfec46fc06e5


Se systemem jsem pracoval, vlozil a nekolikrat editoval cca 30 zaznamu a vse bylo OK. Po nasazeni pluginu prestal date select fungovat, v databazi se datum setne na 0000-00-00.

Po vyhozeni pluginu vse funguje.

V hashi - predavane parametry bez pluginu:

firstrun(1i)"=>"2007", firstrun(2i)"=>"1","latestrun(1i)"=>"2012", "latestrun(2i)"=>"8", "firstrun(3i)"=>"2", "latestrun(3i)"=>"6"}

a s pluginem: "firstrun(1i)"=>"2007", "firstrun(2i)"=>"1", "latestrun(1i)"=>"2012", "latestrun(2i)"=>"8", "firstrun(3i)"=>"2", "latestrun(3i)"=>"6"

Data, ktere lezou do kontrolleru jsou tedy identicka s pluginem i bez.

Nevis Karle (nebo nekdo z navstevniku blogu), kde je zakopany pes? Krome lokalizacniho fajlu je urcite treba nekde neco fixnout, ptam se, jestli tento problem uz nekdo vyresil?

dik

Pavel

# 16
Pavel Krusek napsal před rokem:

Tak jeste jeden komentar :)

Mas chybku (spis preklep) v kodu pro hromadny import fotek:

v metode self.import directory volas metodu import file, spravne podle prikladu ma byt nazev volane metody import photo.

Dalsi postreh: jsem ted pod windows a metoda import photo nahlasi chybu, ze nevidi soubor, predany jako argument. Minimalne pod Windows (linux momentalne nemam k dispozici) je treba jako argument predat jeste nazev adresare, takze volani metody:

Photo.import photo(dir + file)

Pavel

PS pak to jede jak po masle, diky moc za tip, usetri mi to hromadu prace :)

# 17
Karel Minařík napsal před rokem:

@pavel:

To je blbá zpráva, že to ten plugin nějak rozhodí. Díky tomu si uvědomuju, že mně dost zlobily sloupečky v databázi nastavené v migraci na date a musel jsem je všechny nastavit na datetime, aby fungovaly. Jediné, co mně napadá, že ten formulářový helper může spoléhat na defaultní formátování data -- nemám ale teď čas to zkoumat. Podívám se na to.

Ad chyba: jo, je tam stupidní chyba, omlouvám se všem, opraveno. To je tak, když se kód vyzobává a nekopíruje přímo ze script/console nebo z irb. :) Jsem rád, žes to využil, já z toho TestUploadedFile byl docela v rauši, když jsem to objevil. :))

# 18
kutny napsal před rokem:

No teda, tohle je Pan autor. Měl bys dělat spíš v marketingu, protože přesvědčování lidí ti fakt jde. Ještě včera večer jsem měl k RoR vesměs odmítavý přístup (i když PHP sucks, tak sem si myslel, že se mi přechod časově nemůže vyplatit). Dneska jsem strávil půl dne čtením článků a o RoR a jsem unešen. Abychom nezůstali jen u superlativů, má RoR také nějaké nevýhody/špatné vlastnosti? Nevěřím, že to může být až tak dokonalé ;) Síla pluginů je asi nezpochybnitelná, ale zajímalo by mne jejich porovnání třeba s PEARem nebo rozšířeníma pro Zend Framework (nepoužívám ani jedno, takže jsem možná řekl blbost). Taky bych uvítal nějaký ucelenější porovnání se ZF obecně, protože jsem se dočetl, že jeho autoři z RoR dost vycházeli (na internetu jsem toho moc nenašel, ale zas tak moc jsem nehledal). ZF je každopádně mladší, takže na tom asi nebude zas až tak dobře co se týče komunity... Ještě jednou moc díky za osvětu. PHPčko mě sere už dost dlouho, ale nebyla síla přejít na něco jinýho

# 19
Karel Minarik napsal před rokem:

@kutny:

Diky. :)

Co se tyce otazek, ktere klades, skutecne je zodpovedet by bylo na cely clanek... :) A to nemuzu z dovolene ve Spanelsku na vypujcenem pocitaci. Tak zkusim aspon kratce...

K "nevyhodam" Rails se rozhodne chci jeste v nejakem clanku vratit. Lide za "nevyhody" Rails povazuji leccos. Nejvetsi nevyhoda Rails je dle mne zvyk, ktery je zelezna kosile. :)

Nicmene, kdo chce delat v Rails, nesmi se bat par veci: napr. prikazove radky, protoze spousta a spousta veci se v Rails deje prave zde. Viz koneckoncu i tento clanek.

Musi mit take k dispozici dobry hosting a admina, ktery zna Rails, Ruby, RubyGems, server Mongrel, idealne i Capistrano na deploy aplikaci, atd. V Ceske republice nastesti mame Rails hosting s vylozene svetovymi parametry: Railshosting.cz, doporucuju vyzkouset. Jiri Kubicek, ktery jej provozuje, je odbornik na slovo vzaty.

S tim souvisi tez to, ze velke rozcarovani muze zazit vyvojar, ktery prichazi z PHP a je zvykly na v nem obvykly zpusob prace: neco napise na lokalu, nahraje na server, da refresh, neco nekde zmeni, zase da refresh, neexistuje rozdil mezi development a production modem aplikace, atd. To v Rails tak snadne neni -- jsou daleko vice integrovany do prostredi, v nemz bezi (coz jim samozrejme dava uplne jinou silu). Na druhe strane ale pro deployment aplikaci disponuji Rails obludne vykonnym nastrojem jmenem Capistrano.

Porovnani se Zendem, s PEARem -- to bohuzel neni moje parketa. S PHP jsem se natrapil v zivote tolik, ze uz se o jeho vyvoj dlouho nezajimam. :) Z vlastni vule bych v PHP uz neprogramoval nic, co presahuje jednu webovou stranku s minimalni funkcionalitou. Bylo by proto idealni, kdyby takove srovnani napsal nekdo, kdo oboje dobre zna. Ja bych asi nebyl objektivni. :)

Hasta la vista,

Karel

# 20
Karel Minařík napsal před rokem:

@kutny:

Vyčerpávající odpověď lze nalézt na diskusi na RubyOnRails.cz.

# 21
Dan napsal před 10 měsíci:

Proc tento blog uz nepokracuje? Uz bylo vse receno a neni co dodat? :)

# 22
Karel Minařík napsal před 10 měsíci:

@Dan:

Dobrá otázka :) Přestože že to tak bohužel vypadá, blog by měl pokračovat, přinejmenším proto, že mnoho anoncovaných témat mám připravených, jen nezbývalo času na jejich dotažení a publikaci... Nyní by se to mohlo zlepšit.

Díky za zájem!

# 23
Petr Krontorad napsal před 7 měsíci:

Pro pripadne zajemce, ukazkova aplikace je zive k dispozici na karmidemo.dev.isonrails.com.

Lze se i divat, pripadne editovat zdrojove kody, databazi apod. - staci se: a) registrovat a zvolit aplikaci ze seznamu preddefinovanych aplikaci a nebo b) prihlasit se jako "karmidemo" s heslem "1" :).

*.isonrails.com je Rails Playground - idealni cesta pro zacinajici vyvojare, bez nutnosti instalace a s pristupem odkudkoliv.



Karel Minařík navrhuje a programuje webové stránky a aplikace s důrazem na čistý design a interaktivitu. Přednáší o web designu na Institutu Digitálních Médií a o Ruby na FF UK. Píše blog o Ruby On Rails, díky němuž ho opět začalo bavit programovat. Žije v Praze se svojí ženou a dvěma dcerkami, které mu to vše umožňují. Můžete mu napsat e-mail.

Karel Minařík is an independent web designer and developer with focus on clean UI and rich interactivity. He lectures on web design at Institut Digitálních Médií and on Ruby at FF UK. He writes blog about Ruby On Rails (in Czech), which re-ignited his passion for programming. He lives in Prague with his wife and two little daughters, who are making it all possible. You can send him an e-mail.

˜