Ruby on Rails

in Lithuania

"Elgsena paremtas" programavimas(angl. Behaviour Driven Development). I dalis.

Sveiki. Pagaliau radau šiek tiek laisvo laiko. Šįkart kiek linksmiau:RSPEC!
RSpec!

BDD yra gana jauna metodika, evoliucionavusi iš gerai žinomo TDD(angl. Test Driven Development). TDD pagrindinė idėja yra kodą rašyti tik po to, kai parašomas tą kodą testuojantis testas. BDD perima šią metodiką, tačiau testo struktūra labiau panaši į specifikaciją ir ją iliustruojantį kodą, nei į sausus testus. Taigi naudojant BDD parašomas dokumentas, panašus į specifikaciją. Bene vienintelis patogus ir naudingas BDD įrankis Rails'ams yra RSpec. Jis leidžia testuoti modelius, kontrolerius, view'us ir helper'ius. Taigi pradedam nuo modelių, paprastai, ten būna daugiausia logikos, kuri tiesiog prašosi ištestuojama.
./script/generate rspec_model notebook
mėgstamu editoriumi parašome pirmą spec'ą - užrašų knygutės turi turėti 100 puslapių
 # spec/models/notebook.rb<br/>
require File.dirname(__FILE__) + '/../spec_helper'

describe  "New notebook"  do
  fixtures :notebooks
  before(:each) do
    @notebook = Notebook(:my)
  end

  it "should have 100 pages" do 
    @notebook.pages.should eql(100)
  end
end
ir fixture:
# spec/fixtures/notebook.yml
my:
  id: 1
  pages: 3 
paleidžiame testą:
# ruby spec/models/notebook_spec.rb F
1)#ActiveRecord::StatementInvalid in "New notebook should have at least 2 pages"
Mysql::Error: Table 'project_test.notebooks' doesn't exist: SHOW FIELDS FROM notebooks
spec/models/notebook_spec.rb:5:in `new'
spec/models/notebook_spec.rb:5:
spec/models/notebook_spec.rb:3:

Finished in 0.482204 seconds

1 example, 1 failure
priminė, kad neturime notebooks lentelės. Perleidžiam migracijas ir klonuojame development duombazės struktūrą į test duombazę.
 rake db:migrate
rake db:test:clone_structure 
Vėl perleidžiame testą:
 #ruby spec/models/notebook_spec.rb 
F

1)#ActiveRecord::StatementInvalid in 'New notebook should have at least 2 pages'
Mysql::Error: Unknown column 'pages' in 'field list': INSERT INTO notebooks (`id`, `pages`) VALUES (1, 3)
spec/models/notebook_spec.rb:3:

Finished in 0.074569 seconds

1 example, 1 failure#
Hmm...neturime lauko "pages" , tad sugeneruojame migraciją
  ./script/generate migration AddColumnPagesToNotebook
ir ją užpildome:
 class AddColumnPagesToNotebook < ActiveRecord::Migration
  def self.up
    add_column :notebooks, :pages, :integer
  end
  def self.down
  end
end
perleidžiame migraciją ir klonuojame sturktūrą:
rake db:migrate
rake db:test:clone_structure 
vėl testas
  #ruby spec/models/notebook_spec.rb 
F
1)#'New notebook should have 100 pages' FAILED
expected 100, got 3 (using .eql?)
spec/models/notebook_spec.rb:11:
spec/models/notebook_spec.rb:3:

Finished in 0.093053 seconds

1 example, 1 failure
  
Testas puikiai indikuoja, kad yra klaida naujos užrašų knygelės puslapių skaičiuje, tad pataisome fixture į:
# spec/fixtures/notebook.yml
my:
   id: 1
   pages: 100   
perleidžiam testą:
   #ruby spec/models/notebook_spec.rb  
.

Finished in 0.140526 seconds

1 example, 0 failures#
pagaliau praėjome testą!!!Tvarkoj, dabar mums norime, kad išplėšus lapą iš užrašų knygutės, puslapių skaičius sumažėtų vienu. Pridedam:
#spec/models/notebook_spec.rb
require File.dirname(__FILE__) + '/../spec_helper'

describe  "New notebook"  do
  fixtures :notebooks
  before(:each) do
    @notebook = notebooks(:my)
  end
  it "should have a 100 pages"  do
    @notebook.pages.should eql(100)
  end
  it  "should have one page less after removing one page"  do
    pages_before = @notebook.pages
    @notebook.remove_page
    @notebook.pages.should eql(pages_before - 1)
  end
end
perleidžiam testą:
 ruby spec/models/notebook_spec.rb 
.F

1)
NoMethodError in 'New notebook should have one page less after removing one page'
undefined method `remove_page' for #<Notebook:0x9a85aa8 @attributes={ id => 1 ,  pages => 100 }>

spec/models/notebook_spec.rb:16:
spec/models/notebook_spec.rb:3:

Finished in 0.101506 seconds

2 examples, 1 failure
Nepasiskė...neturime tokio metodo, tad sukuriame jį:
#app/models/notebook.rb 
class Notebook < ActiveRecord::Base
  def remove_page
  end
end
Testuojam:
 ruby spec/models/notebook_spec.rb 
.F

1)
'New notebook should have one page less after removing one page' FAILED
expected 99, got 100 (using .eql?)
spec/models/notebook_spec.rb:17:
spec/models/notebook_spec.rb:3:

Finished in 0.120552 seconds

2 examples, 1 failure
Hmm...puslapių skaičius nesumažėjo, tad ištaisome klaidą:
#app/models/notebook.rb
class Notebook < ActiveRecord::Base
  def remove_page
    self.pages -= 1
  end
end 
Testuojam:
#  ruby spec/models/notebook_spec.rb 
..

Finished in 0.106724 seconds

2 examples, 0 failures
Pagaliau, po ilgos darbo dienos kodas praeina testus, tad galime ramiai eiti namo:) O jei rimtai, tai idėja yra labai paprasta - rašome specifikaciją, o testas pats indikuoja ką programuotuoju reikia padaryti, kad kodas atitiktų specifikaciją. Be to, išlieka ir visi TDD privalumai: užaugus sistemai, gana paprasta daryti pakeitimus, nes testai indikuoja vietas, kurios yra paveikiamos pakeitimo. Nereikia galvoti apie visą sistemą, o spręsti tik mažą specifikacijos reikalavimą, jei tai paliestų kitas sistemos dalis, testai tai parodytų, ir kita. Beje, ar pastebėjote, kad nebuvo naudota naršyklė. Būtent, naudojant šią metodiką nebereikia "mėgėjiškai" ieškoti klaidų.

Published on 07/05/2007 at 22h20 by Saulius Grigaitis

HAML - geras ERB pakaitalas

HAML (XHTML Abstraction Markup Language ) - tai dar viena šablonų sistemą. Pagrindinis HAML principas - kodas turi būti gražus ir lengvai skaitomas. Išmokti HAML labai paprasta, jums tai užtruks maždaug 20 minučių.

Kodo pavyzdžiai:

# Rhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd ">
<html
  <head>
    <title>Užkandinės meniu</title>

  </head>
  <body>
    <p>
      <b>Pagrindiniai patiekalai:</b>
    </p>

    <table id="menu">
      <tr>
        <td>Pica</td>
        <td>Koldūnai</td>

        <td>Dešrelės</td>
     </tr>
    </table>
  </body>
</html>

Perdarant tai į HAML teks dažnai pasinaudoti 'Delete' klavišu.

!!!
%html
  %head
    %title Užkandinės meniu
  %body
    %p
      %b Pagrindiniai patiekalai:
    %table#menu
      %tr
        %td Pica
        %td Koldūnai
        %td Dešrelės

Kodas akivaizdžiai paprastesnis ir gražesnis. Nereikia rašyti uždarančių tagų. Kada tagas turi užsidaryti sprendžiama pagal indentacija. Indentacija turi būti 2 tarpai. Papildoma dokumentacija:

SASS (Syntactically Awesome StyleSheets) - meta-kalba kuri supaprastina CSS stilių "gamybą". Stilių sintaksė tampa aiškesnė, atsiranda naujų galimybių.

Pavyzdžiui:

.funky
  :font
  :family fantasy
  :size 30em
  :weight bold
  p
    :font-size 2em

sugeneruos toki css failiuką:

.funky {
font-family: fantasy;
font-size: 30em;
font-weight: bold; }

.funky p {
font-size: 2em }

.css failas generuojamas tik vieną kartą, todėl dėl jo stabdžių neatsiras. Taip pat galima naudoti ir konstantas. Daugiau informacijos oficialioje dokumentacijoje.

Sintaksės spalvinimas skirtingoms IDE HAML Syntax Highlighting

Jei turite minčių kaip galima patobulinti HAML arba suradote klaidą, užeikite į http://groups.google.com/group/haml?hl=en

Ten aptarinėjamos paskutinės naujovės, klaidos. Galima užduoti klausimą ir pačiam autoriui.

Published on 01/05/2007 at 22h18 by Fiodor Vereščiaka

Centralizuota autentikacija

Sveiki. Vienas dalykas, kuris nuolat mane žavi dirbant su Ruby ir Rails, tai kalnai kokybiško ir naudingo kodo, kuriuo gana lengva pasinaudoti. Šįkart trumpai apie centralizuotą autentikacijos servisą(CAS). CAS - servisas, suteikiantis "Single Sign-On" mechanizmą. Praktiškas pavyzdys - google.com. Turite vieną paskyrą (angl. "account"), prisijungiate prie sistemos vieną kartą ir galite naudotis visais servisais, nors jie yra visiškai skirtinguose serveriuose, stovi po atskirais domenais ir išsibarstę po visą pasaulį. Trumpai apie patį paprasčiausią CAS protokolą, kuris leidžia tai įgyvendinti paprastose sistemose. Reikalingas CAS serveris ir klientas, įsimylėjusiems Ruby - rubycas-server ir rubycas-client. Be abejo, reikalinga šiek tiek pasiskaityti dokumentaciją ir sukonfigūruoti juos, tai gana paprasta, tereikia išsirinkti CAS servisą aptarnaujantį serverį, surašyti domenus ir panašiai. Dabar įdomiausia dalis - autentikacijos procesas: 1. Jungiatės prie serverio klientodomenas.lt, kuris jus automatiškai nukreipia į CAS serverį
https://serveriodomenas.lt/login?service=http://klientodomenas.lt
2. Atlikus sėkmingą autentikaciją, jus nukreipia atgal, bet jau su prikabintu bilietu
http://klientodomenas.lt/?ticket=ST-956-Lyg0BdLkgdrBO9W17bXS
3. O dabar iškeičiame bilietą į vartotojo vardą
https://serveriodomenas.lt/serviceValidate?ticket=ST-956-Lyg0BdLkgdrBO9W17bXS&service=http://klientodomenas.lt
4. Gauname vartotojo vardą:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
	<cas:authenticationSuccess>
		<cas:user>vartotojo_vardas</cas:user>

	</cas:authenticationSuccess>
</cas:serviceResponse>
O jungiantis iš kito CAS kliento, jūs atpažįstamas pagal sausainiuką, tad nebereikia atlikti autentifikaciją dar kartą. Be abejo, visu tuo mechanizmu rūpinasi rubycas-server ir rubycas-client, jums tereikia juos susikonfigūruoti ir naudoti.

Published on 28/04/2007 at 22h17 by Saulius Grigaitis

Saugumas. I dalis

Sveiki! Pradedame ciklą užrašų apie saugumą. Dauguma programuotojų, ypač nepatyrusių, lipa ant vieno ir to pačio grėblio, t.y. neapsaugo kritinių saugumo atžvilgiu objekto atributų nuo neleistino jų pakeitimo. Tarkime programuojama sistema yra gana paprasta ir naudojamas labai lengvas autorizacijos mechanizmas, tarkime klasė "Vartotojas" turi atributą "admin". Pagal šį atributą sistema ir autorizuoja vartotoją. Viskas atrodytų lyg ir gerai, vartotojo registracijos formoje nėra laukelio "admin", tad vartotojas pats negali pasikelti sau privilegijų, migracijoje "admin" laukelio reikšmė nustatytą "false" nutylint. Net ir po "Vartotojo" egzemplioriaus sukūrimo "create" "action'e", jo "admin" reikšmė nustatoma "false". Tačiau pamirštama tai padaryti "Vartotojo" egzemplioriaus atnaujinimo "action'e". Įsivaizduojame blogiausią situaciją: kažkas sužino apie atributą "admin". Tada supakuoja parametrus ir pasiunčia "POST" užklausą į "update" actioną. Pavyko! Dabar vartotojas turi administratoriaus privilegijas... Bene paprasčiausias būdas apsisaugoti nuo tokio tipo klaidų - naudoti "protected" atributus: class User < ActiveRecord::Base attr_protected :admin end

Published on 26/04/2007 at 22h15 by Saulius Grigaitis

Prilipęs Footer'is

Kas susidūrė su CSS, žino kokia velniava yra padaryti puslapį be šlykščių ir niekaip nenusibaigiančių lentelių "table", vienodai atrodantį (ir identiškai besielgiantį) visose naršyklėse. IE košmarai, Firefox bug'ai, dar prakeiktas Safari (Opera dzin :)... Žodžiu vieno standarto nėra, kiekvienai mažai užduotėlei realizuoti nusistovėjo purvini "hakai" ir visi (naršyklių kūrėjai bei web programuotojai) toliau gyvena laimingi ir patenkinti.

Taigi šiandien papasakosiu, kaip išpręsti prilipusio footer'io problemą ištęstuotą ant IE ir Firefox.

Pačią problemą galima būtų taip apibūdinti: puslapyje yra footer'is - fiksuoto aukščio apatinis (paskutinis matomas) blokas (juosta). Kaip žinome, visi blokai (ne float) išsidėsto vienas po kito ir jeigu turinio yra daug tuose blokose, footeris nustumiamas į apačią, kuri yra "žemiau" naršyklės lango apačios ir "paskrolinus" puslapį žemyn matome prie apačios prilipusį footerį. Tačiau, ką daryti, jei puslapis neturi daug turinio, bet mes vistiek norime, kad footeris butų naršyklės lango apačioje?

Geras mano draugas vardu Google patarė štai ką:

# Layout'o šablonas
<html>
  <body>

    <div id="nonFooter">
      # Čia talpinam visą puslapio turinį
    </div>

    <div id="footer">
      # Footerio turinys
    </div>

  </body>
</html>

Ir į CSS failą įterpiam tokį kodą:

# CSS failiukas

html {
  height: 100%;
}

body {
  height: 100%;
}

#nonFooter {
  position: relative;
  min-height: 100%;
}

* html #nonFooter {
  height: 100%;
}

#footer {
  position: relative;
  /* Nurodome margin tokio paties aukščio kaip ir footeris */
  margin-top: 50px;
}

Neįprasta, keista, bet viskas veikia, užsakovas švyti, o mes pasikeikiam ir dirbam toliau...

Published on 24/04/2007 at 22h14 by Mindaugas Kurlavičius

Laiko juostos

Sveiki, tęsiam globalizacijos/lokalizacijos problemą. Šiandien prireikė korektiško laiko nustatymo mechanizmo skirtingose laiko juostose esantiems vartotojams. Deja, standartinės Rails priemonės nesusitvarko su lygiadieniais, t.y. apie pusmetį kasmet turėsite neteisingą laiką :). Klientų tai visai nenudžiugintų, tad teko rasti sprendimą. Bene vienintelis patogus būdas tinkantis 1.2 versijos Rails karkasui yra tzinfo_timezone įskiepis, kuriam taip pat reikia tzinfo gem'o. Nurodom Rails'ų Active Record'ui naudoti UTC laiką, nustatydami config.active_record.default_timezone = :utc config/environment.rb faile. Pridedame vartotojų lentelėje stulpelį "time_zone". Na ir belieka pasirašyti helperius laiko konvertavimui iš UTC į vartotojo lokalų laiką ir atvirkščiai, metodo "utc_to_local" pagalba. Iki!

Published on 24/04/2007 at 22h10 by Saulius Grigaitis

Paveikslėlių įkrovimas

Tinklapis be paveikslėlių? Na, nebent kokioje dokumentacijoje jie nereikalingi, visur kitur jau beveik nebeišsiverčiama be vaizdo ar garso klipų, ką jau kalbėti apie paveikslėlius. Logotipai, nuotraukos šiaip visumą pagyvinantys elementai... Ir svarbiausia, turi būti galimybė visą tai įkelti paprastam nepriviligijuotam vartotojui. Be to, vartotojui neturi rūpėti įkeliamo paveikslėlio dydis, t.y. tuo turi pasirūpinti tinklapio serveris.

Taigi, išties aktualus klausimas praktiškai kiekvieno tinklapio kūrėjui. Kaipgi į jį atsako "Rails"?

Deja, pats "Rails" karkasas kol kas nieko nesiūlo. Tenka kapstytis tarp trečių šalių deimančiukų (gems) ir įskiepių (plugins). Siūlomos dvi dėmesio vertos alternatyvos: "Acts as attachment" ir "File Column".

Abu įskiepiai dėl manipuliavimo paveikslėlių parametrais reikalauja "RMagick" bibliotekos, kurią man teko matyti tik "Unix" ir "Linux" platformose, bet atrodo nesunkiai galima įdiegti ir į visų taip mėgiamus "Windows".

Acts as attachment - galingas ir lankstus, sakyčiau netgi šiek tiek griozdiškas įrankis, leidžiantis įkraunamus failus saugoti DB arba failų sistemoje ir patį įkraunamą failą realizuoti kaip modelį saugomą lentelėje, kurį vėliau būtų galima perdengti kitais modeliais. Tarkime:

class File < ActiveRecord::Base
end

class Picture < File
  # Paveikslėlio matmenys, leidžiami tipai, max dydis ir t.t
end

class Xxx < File
  # ...
end

Žodžiu, per daug nesigilinant, "acts_as_attachement" API leidžia pasireikšti fantazijai, galima išspręsti sudėtingą specifinę problemą, tačiau esminis šio įskiepio trūkumas - pakankamai sudėtingas valdymo mechanizmas, be to dažniausiai tereikia tik 5% jo galimybių, o joms realizuoti nemažai laiko.

File Column - lengvesnis įrankis. Paveikslėliai (galimi ir kitokie failai) saugomi failų sistemoje, nereikia atskiro modelio ir atskiros lentelės, failus tiesiog "prikabiname" per papildomą stulpelį DB, prie jau egzistuojančio modelio. Vaizdo elementuose (rhtml) pasiekiame tuos failiukus, paruoštais pagalbiniais metodais (helpers). Taip pat "File Column" automatiškai saugo tarpinį, jau pakrautą į formą, paveiksliuką. Tad, klientui neteisingai užpildžius tam tikrus formos laukus ir "renderinus" vaizdo elementą atgal, paveikslėlis bus laikinai išsaugomas į katalogą serveryje ir tik priėmus teisingai užpildytą formą, "prikabintas" į failų sistemą ir duomenų bazę.

# Migracija
class CreateBooks < ActiveRecord::Migration
  def self.up
    create_table "books" do |t|
      #t.column :name,   :string
      #t.column :author, :string
      t.column :cover,  :string
    end
  end

  def self.down
    drop_table "books"

  end
end

# Modelis
class Books < ActiveRecord::Base
  file_column :cover, :magick => {:geometry => "100x200>"}
end

# Vaizdas
<%= image_tag url_for_file_column(@book, "cover") %>
    

Published on 23/04/2007 at 22h08 by Mindaugas Kurlavičius

Unicode'as Ruby on Rails programose

Sveiki. Ramus sekmadienio vakaras. Ryt vėl įsuks į darbų verpetą, tad puikus metas apšilti ir parašyti pirmą techninį šio blogo straipsnį. Šiuo metu vystome projektą, kuriame daugiakalbystė vaidina ne paskutinį vaidmenį, tad draugausime su Unicode'u. Prireiks Ruby on Rails 1.2 ar naujesnės versijos ir keturių žingsnių:
1. Susitvarkome savo redaktorius taip, kad būtų naudojama utf-8 koduotė(dažnas šiuolaikinis redaktorius tai jau padarė pats).
2. Užtikriname, kad kiekvieno view'so antraštėje nurodyta utf-8 koduotė:
 <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

  ...
  </head>
3.Nurodome adapteriui, kad naudotų utf-8 koduotę faile config/database.yml:
development:
  adapter: mysql
  database: sample
  username: root
  password:
  encoding: utf8
...
Naudojant PostgreSQL, "utf8" keičiame "unicode".
4.Na ir linksmiausia dalis - nurodome duomenų bazių valdymo sistemai(DBVS), kad ji naudoti utf-8. Kiekvienai DBVS tai atliekama skirtingai, parodysiu kaip tai padaryti MySQL DBVS. Susirandame my.cnf failą, paprastai jis guli /etc direktorijoje Unix tipo sistemose. Papildome "[mysqld]" sekciją dviem įrašais:
[mysqld]
character-set-server = utf8
default-collation= utf8_unicode_ci
...
Perkrauname. Viskas. Tolesniam programų globalizavimui rekomenduoju Globalize įskiepį, bet apie jį vėliau. Iki!

Published on 22/04/2007 at 22h04 by Saulius Grigaitis

3, 2, 1, Startas!!!

Sveiki! Pagaliau startavom! Paklausite, kas ir ką startavo?! Trys sumanūs, veržlūs ir pamišę dėl Ruby ir Rails jaunuoliai įkūrė naują vietelę šiame didžiajame tinkle. O kam tai? Tai mums visiems – Ruby programuotojams, fanatams, entuziastams, visiems tiems, kuriems Ruby ne tik programavimo kalba, bet ir gyvenimo būdas. Čia vieta, kurioje Tu sužinosi ne tik apie Ruby ir Rails ar net kuo gyvena, kuo kvėpuoja, ką ir su kuo valgo, kaip dirba ir kaip ilsisi, apie ką svajoja,…Ruby entuziastai, o gal būt ir pats apie tai parašysi. Pasiruošk…

Published on 22/04/2007 at 22h03 by Saulius Grigaitis

Powered by Publify Theme Frederic de Villamil | Photo Glenn