Ruby on Rails

in Lithuania

Rails plugins/engines: Loading files in development mode with "require_dependency"

The situation we discussed in pixel.lt blog post is interesting and some of you may have such situation. The guy introduced view of reusable components - rails engines. The main idea is to develop initial engine and then develop new engines, which extends the initial engine. Then develop more engines, which extends previous engines, then.... extend/change behavior of classes in main application. The issue is that files in plugins are not reloaded on each request in development mode, so the classes will be loaded only from main application after first request. To solve this problem just require the file paths of last engine in main application files with "require_dependency". So if you have model Car in engine A, then Car class is extended in engine B, and then class is extended in RAILS_ROOT/app/models, you need require path to Car file on engine B with "require_dependency" in the RAILS_ROOT/app/models/car.rb.

Published on 22/11/2007 at 22h36 by Saulius Grigaitis

Netbeans 6 - best IDE for Ruby and Rails?

I've tried Netbeans 6 with Ruby support yesterday. I'm impressed. I'm impressed twice:). I used Netbeans for programming tasks at the university few years ago, but Netbeans was just IDE for Java development, I never thought that they achieved so much in Ruby and Rails support nowadays. First of all, read what Infrid writes on his blog lifeonrails.org. There are some videos on netbeans.org too. That's impressive. Is it time to throw out your favorite IDE or editor even if it is textmate? In most cases - Yes.

Unfortunately, there is no Netbeans precompiled binary or port for my favorite operating system FreeBSD, the same situation is with Java Application Server - GlassFish, which is suitable for deploying JRuby on Rails projects. If you do not use Linux, OS X, Solaris or Windows, then building Netbeans and GlassFish from source is probably the only one way to get it working in your box. This way should work for most of Unix derived operating systems, which has JDK 1.5 and other required tools like ANT. The instructions how to built Netbeans from source is available at Netbeans Wiki. Full Netbeans source code checkout and build takes about few hours if your machine and internet downstream are pretty fast, so be patient. The instructions how to build GlassFish ant the JAR archives are available at GlassFish Community. This JAR is suitable if you use FreeBSD. GlassFish is smaller, so the process takes less time than to build Netbeans.

Great! So much new things comes to Ruby and Rails...

Published on 26/09/2007 at 22h35 by Saulius Grigaitis

Rails tekstinė paieška (Full text search)

Tekstinė paieška, tai vienas iš svarbiausių reikalavimų beveik visose tinklo aplikacijose. Taigi, apie tai, kaip tekstinę paiešką realizuoti, Rails tinklo aplikacijoje.

Ne paslaptis, kad didžioji žiniatinklių pyrago dalis naudoja MySQL DBVS, taigi ir šiame straipsnyje į tekstinę paiešką pažvelgsime iš MySQL perspektyvos.

Pasižvalgius po interneto platybes, paaiškėjo, kad patys populiariausi tekstinės paieškos realizavimo metodai Rails aplikacijose yra tokie:

  1. MYSQL, LIKE sintaksė.
  2. MYSQL implementuotas teksto indeksavimas.
  3. Acts_as_ferret pluginas.
  4. Acts_as_sphinx pluginas.

MYSQL, LIKE sintaksė

Dar ir iki šiandien plačiai naudojamas dėl paprastumo. Tiesiog į užklausą įterpiame LIKE('%kazkas%') ir gauname norimus rezultatus. Tačiau šio metodo trūkumai - greitis, galimybės (tarkime norime rezultatų 'Bill+Gates-Windows' ir nepatogus naudojimas - Rails nėra pagalbinių metodų LIKE naudojimui, (tarkim norime teksto ieskoti 15-oje modelio atributų, SQL sakinio dalis atrodytų : "attr1 LIKE('%a%') OR attr2 LIKE('%a%') ir.t.t"). Taigi šis principas - tai iš esmės pavyzdys kaip nereikėtų realizuoti tekstinės paieškos žiniatinkliuose :). Nors, žinoma, niekas to nedraudžia daryti esant mažiems poreikiams.

MySQL teksto indeksavimas.

Tai patobulinta MySQL alternatyva LIKE sintaksei. Implementacija paprasta. Kurdami lentelę (MyISAM) tiesiog nurodome FULLTEXT INDEX(name) ir MySQL automatiškai sukurs indeksų lentelę "name" atributui.

CREATE TABLE test (
  id INT(5) PRIMARY KEY NOT NULL AUTO_INCREMENT,
  data TEXT
  FULLTEXT INDEX(data)
);

Paieška:
SELECT * FROM test WHERE MATCH(data) AGAINST('labas')

Nors ir efektyvesnis būdas nei pirmas, tačiau vistiek turi trūkumų, tarp kurių akivaizdžiausias - naudojimo patogumas. Neišvengiamai tenka visur rašyti SQL (mes juk pripažįstam tik RUBY:), neaiški situacija su Rails migracijomis, ten tenka naudoti šlykštoką 'execute'. Taip pat atsiranda apribojimas, nebegalime indeksuotoje lentelėje naudoti transakcijų, kadangi MySQL indeksavimas reikalauja MyISAM tipo lentelės. Aišku, šis tekstinės paieškos realizavimo būdas efektyvesnis nei pirmas, tačiau toli gražu iki mums įprasto grožio:).

Acts_as_ferret pluginas

Tai pakankamai neseniai atsiradęs ir tikrai vertas dėmesio projektas. Jis naudoja "Ruby" kalba implementuotą "Apache Lucene" indeksuotos paieškos variklį. Diegimas paprastas:

gem install ferret

### įkeliam acts_as_ferret pluginą:

script/plugin install svn://projects.jkraemer.net/acts_as_ferret/tags/stable/acts_as_ferret.

### Modelyje, kuriame norime indeksuoti atributus, nurodome:

acts_as_ferret :fields => [:attr1, :attr2]

Perkrovę aplikacijos serverį turime reikalingą funkcionalumą. Galime daryti paiešką naudodami find_by_contents metodą savo modelyje.

@total, @results = People.find_by_contents('Bill+Gates-Windows')

Acts_as_ferret veikimo principas paremtas tuo, kad nurodytus atributus jis stebi(callbacks) aplikacijos lygmenyje ir po pasikeitimų indeksuoja, jeigu dirbant "development" rėžimu paprastame faile Rails aplikacijos 'index' kataloge, jeigu 'production' - atskirame 'Drb' serveryje, kurio konfigūravimas panašus į paprastos DB konfigūravimą (YAML sintakse). Taigi 'find_by_contents' metodas pagal paieškos užklausą iš indeksų surenka atitikusių įrašų 'id' masyvą, ir iš MySQL lentelės išrenka įrašus pagal tuos 'id'.

Acts_as_ferret galimybės - įspūdingos. Galima paieška pagal tam tikrą žymių ('wildcards') sistemą, rastų atitikmenų paryškinimas (panašiai į Google), protingo rūšiavimo (kai vieni įrašai labiau atitinka paiešką nei kiti) galimybė ir t.t. Įdomus acts_as_ferret su MySQL text search palyginimas : full-text-search-in-ruby-on-rails .

Vienintelis rimtas trūkumas, kaip ir daugelyje Rails pluginų:

documentation.nil? == true.

Todėl rimtesnių informacijos šaltinių nedaug:
acts-as-ferret-tutorial
full-text-search-in-ruby-on-rails-3-ferret

Acts_as_sphinx pluginas.

Naudoti acts_as_sphinx pluginą rekomenduočiau tiems, kurie kaifuoja dėl gero greičio (performance). Pagal atsiliepimus, šis pluginas naudojantis kaimyno ruso programuotojo Andrew Aksyonoff sphinx variklį, greitas kaip velnias. Galybėmis 'sphinx' panašus su 'ferret', tačiau kaip žinia visur kur norisi įspūdingo greičio, reikia visai neįspūdingo krapštymosi po žemo lygmens detales. Kruopščios priežiūros reikalaujantis 'sphinx', ne išimtis. Diegimo ir priežiūros etapai šiek tiek atbaidantys. Keista dar ir plugino kūrėjų pozicija, neindeksuoti įrašų aplikacijos lygmenyje. Taigi tektų su kokiu nors išoriniu 'cron' automatiniu užduočių paleidimo mechanizmu kas tam tikrus laiko intervalus atnaujinti indeksus. Rezultate - arba nuolat per DB lenteles 'vaikštantis' išorinis procesas, arba ne realaus laiko paieška, nes tik įvedę įrašą jo tam tikrą laiką nerasime.

Taigi paminėti pluginai, labiau tiktų norint tikrai profesionalaus sprendimo, 'pigiam' kodui pakanka ir tiesioginio MySQL DB naudojimo.

Published on 04/09/2007 at 22h34 by Mindaugas Kurlavičius

"Rails" Rankinis puslapiavimas (Pagination)

Beveik kievienoje tinklo aplikacijoje yra reikalingas įrašų puslapiavimas. Kas tai yra, labai lengva suprasti apsilankius Google puslapyje. Ten visi paieškos rezultatai suskirstyti po 10 ir pateikiami atskiruose puslapiuose, į kuriuos nuorodos yra tvarkingai sudėliotos rezultatų apačioje.

Programiniame lygmenyje puslapiavimo algoritmas nėra labai sudėtingas, aplikacijai reikalingi tik du parametrai - įrašų skaičius vienam puslapiui (:limit), ir norimas puslapis (:page). Su šiais parametrais galime apskaičiuoti trečią parametrą - (:offset) pagal formulę offset = limit * (page - 1) ir formuoti efektyvias SQL užklausas kiekvienos užklausos metu iš DB pasiimdami tik reikalingą kiekį užrašų einamajam puslapiui. Paprasta, tačiau puslapiavimas vis dėl to yra nemenkas galvos skausmas programuotojams, o "Rails" bendruomenėje ima vyrauti nuomonė, jog pačio paprasčiausio "Rails" puslapiavimo geriau išvis nenaudoti : Things you shoudnt be doing in Rails.

Dokumentacijoje nurodytas metodas:

@person_pages, @people = paginate(
  :people, 
  :order => 'last_name, first_name'
)
atrodo labai patraukliai, tačiau praktikoje iš jo gali būti per mažai naudos. Jo trūkumas yra tas, kad negalima perduoti einamojo puslapio parametro, t.y. jis naudoja params[:page], taigi tokio modelio puslapyje, kur einamasis puslapis visada turi būti fone, nesvarbu, kokį kitokį veiksmą atlikome, prie kiekvienos nuorodos turime "prikabinti" params[:page]. Be, to tradicinis pagalbinis metodas "pagination_links" retai tinka praktiškai.

Patogus būdas (padedantis išvengti galvos skausmo) realizuoti puslapiavimą , tai pasidaryti rankomis pagalbinį metodą (kontrolerio arba bibliotekos) naudojantį žemesnio lygio puslapiavimo metodus.

def my_pagination(user, limit, page)
  @total = Record.count(:conditions => {:user_id => user.id})
  @records = Record.find(
    :all, 
    :conditions =>  {:user_id => user.id },
    :order      =>  'created_at DESC',
    :limit      =>  limit,
    :offset     =>  limit.to_i * (page.to_i - 1)
  )
  @record_pages = Paginator.new(self, @total, limit, page)
end

Tokį metodą naudojant kontrolerio veiksmuose (action), gaunami trys kintamieji iš kurių vėliau nesunku pasidaryti nuorodas į puslapius.

Taip pat, patogu yra params[:page] parametrą "prikabinti" tik prie nuorodų į įrašų puslapius, ir gavus aplikacijoje išsaugoti arba sesijos kintamajame arba kokiuose nors vartotojo nustatymuose, o užklausose, kuriose params[:page] nėra, naudoti išsaugotą parametrą.

Published on 04/09/2007 at 22h32 by Mindaugas Kurlavičius

Mongrel handlers

Yea, it's unusual to read blog post in English here! Because of very low feedback from Lithuanian Rails community (most of posts do not have any comment) we've decided to write in English. Probably Lithuanian Rails community is too small to give us desired feedback, so English language should help increase number of visitors and feedback. Feedback in this case means suggesting better solutions comparing to that one we write about in the post, or even deep discussion about the problem.

More I work with Rails more I understand that Rails fits 80/20 formula. Yes, I think that Rails is beautiful for 80 percents, but thanks to Rails and Ruby, we have freedom in those tricky 20%. One of those tricks is Rails applications deployment. Each Mongrel with Rails eats ~40MB RAM. This is not good if one has small amount of RAM and some actions are processed very long, say action communicates with few slow external WEB services or action is used for bigger files uploads. When such request is processing, mongrel is not available until processing is finished, so if one has only 2-3 mongrels running and action takes few seconds or more, than even low load becomes DOS attack. Mongrel handlers can help in such situations, because if the handler is pretty light, then mongrel eats only ~10 MB of RAM, so it's really good way to deliver such "time costly" operations to mongrel handlers and make available main Rails application to accept regular requests. Mongrel is multi-threaded server, but unfortunately, Ruby implements Green Threads, which is real evil when things turns to blocking system calls, so each such call hangs all threads. But ~40MB is still 4 times more than ~10MB, so instead of 2 Mongrels and Rails you can use 8 Mongrels with light handlers, also Mongrel handlers is really robust. Simple mongrel handler:

require 'rubygems'
require 'mongrel'

class HelloWorldHandler < Mongrel::HttpHandler
  def process(request, response)
    response.start(200) do |head, out|
      puts "request got"

      head["Content-Type"] = "text/html"
       # here should be some real code
       # ...
       out << "Hello World!"
      puts "request finished"
    end
  end
end

config = Mongrel::Configurator.new :host => ARGV[0], :port => ARGV[1] do
  listener do
    uri "/helloworld", :handler => HelloWorldHandler.new
  end
  
  trap("INT") { stop }
  run
  puts 'Started'
end

config.join

Let's start mongrel:

$ruby helloworld.rb 127.0.0.1 8000

Let's make request in the browser http://127.0.0.1:8000/helloworld

Mongrel handlers and Mongrel with Rails combination should work for most Rails projects, so try it and share your experience. Enjoy!

Published on 03/09/2007 at 22h31 by Saulius Grigaitis

WEB servisai - SOAP klientas

Sveiki! Kad ir kaip mėgstame ir žavimės "REST'u" ir "ActiveResource'u", labai daug naudingų WEB servisų naudoją kitus protokolus, pavyzdžiui SOAP. Šįkart trumpai apie tai, kaip lengvai galima pasiekti WEB servisą SOAP protokolu, naudojantis Ruby priemonėmis. Standartinėje Ruby bibliotekoje priemonės SOAP'ui yra gerokai pasenusios, tad verta naudotis "soap4r" "gem'u". Šiuo metu stabili versija yra 1.5.6.

gem install soap4r --source http://dev.ctor.org/download/ --version 1.5.6
Belieka susirasti norimo WEB serviso aprašą WSDL formatu ir pasirašyti paprastą scriptą:
#!/usr/bin/env ruby

require 'soap/rpc/driver'
require 'soap/wsdlDriver'

driver = SOAP::WSDLDriverFactory.new("wsdl_failo_url").create_rpc_driver

#klaidų ieškojimo rėžime(su "-d" raktu ruby interpretoriui) spausdiname visą "dump'ą"

driver.wiredump_dev = STDERR if $DEBUG
                       
#parametrai "hash'e" iš argumentų raktų ir jų reikšmų, informacijos apie tai galima rasti dominančio WEB serviso API dokumentacijoje
@results = driver.lc(:arg1 => "argumentas", :arg2 => "argumentas")
Iki!

Published on 14/07/2007 at 22h30 by Saulius Grigaitis

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

Sveiki. Pagaliau baigėsi bakalauriniai, stojamieji ir visi kiti akademiniai reikalai, tad dažniau rašysiu straipsnius. Šį kartą tęsiu pažintį su RSpec ir kontrolerių testavimu. Kontrolerių testavimo techninė dalis yra analogiška jau aptartoms dalims, bei turi keletą naujų dalykų, tačiau koncepciniai dalykai daug įdomesni. Juos ir aptarsiu šiame straipsnyje.

Visų pirma - RSpec'as propaguoja ne klasikinį testavimą, o "mock'inimą". Klasikiniame testavime visi testai surišti tarpusavyje panaudojant kiek galima tikrų objetų, o netikri objektai("mocks", "stubs" ir kita) yra naudojami tik tada, kai nėra kitos galimybės, pvz. dar neturint priėjimo prie WEB serviso, nes jis dar nesukurtas, bet turint jo API, bei darant prielaidą, kad servisas tikrai veiks korektiškai naudojantis pateiktu API. Tuo tarpu "Mock'istai" naudoja netikrus objektus kur tik gali ir tai turi prasmę. Iš esmės tai yra visiškai skirtingos koncepcijos, turinčios savo privalumų ir trūkumų. Pagrindiniai skirtumai yra testų integracija ir programavimo stilius. Klasikiniai testuotojai rašo testus, kurie surišti tarpusavyje, o "mock'istų" testai dažniausiai visiškai nepriklauso vienas nuo kito, tad klasikinių testuotojų testai yra integraciniai, o "mock'istų" - ne. Pvz. klasikinis testuotojas rašydamas kontrolerio testą nusprendžia, kad jam reikia naujo modelio "Book" klasės metodo "pages", jam nelieka nieko kito, kaip imti ir parašyti "Book" klasės metodo "pages" testą ir tada patį metodą "pages", geriausiu atveju - paprašyti kolegos, kad jis tai padarytų:). Gal viskas tuo gerai baigtųsi, jei nepaaiškėtu, kad "pages" metodui reikia dar kito metodo, kuris dar neparašytas, o kolega jau ir taip užimtas:). Taigi Klasikinis testuotojas norėjo parašyti kontrolerio testą ir patį kontrolerį, bet jam teko įsivelti į modelių metodų rašymą. Dažnai šita grandinė išauga iki įspūdingo ilgio. Tuo tarpu "mock'istas" daro prielaidą, kad modelis turi reikiamą metodą ar jį turės ir pasitiki jo veikimo korektiškumu, nes to metodo autorius jį jau ištestavo. Naudojant RSpec, tai atrodytų taip:

...
@book = mock_model("book")
@book.should_receive(:pages).and_return(100)
...
toks testas praeis sėkmingai net jei "Book" klasė ir neturės metodo "pages", tačiau programuotojas gali susikoncentruoti ir galvoti kaip geriau parašyti testą ir patį kontrolerį, o ne šokinėti prie kitų programos dalių.

Iš vienos pusės "mock'istams" lengviau programuoti, tačiau jie praranda testų integraciją, o klasikinimas testuotojams programuoti žymiai sunkiau, tačiau jie turi didesnę kodo kontrolę, nes integruoti testai geriau indikuoja pažeistas vietas po kodo pakeitimų. Klasikiniams testuotojams yra kur kas sunkiau programuoti didesnėse komandose lygiagrečiai, tuo tarpu "mock'istams" tai nedaro įtakos. Tačiau praktikoje kartais "mock'istams" sunku imituoti sudėtingų objektų elgseną, tad priveliama klaidų į pačius testus. Abi technikos turi savų pliusų ir minusų, jei dar neapsisprendei kuri tau tinkamesnė, paskaityk:

Martin Fowler - Mocks Aren't Stubs

RSpec Documentation - Mocks

Iki!

Published on 06/07/2007 at 22h29 by Saulius Grigaitis

Ruby on Rails hostingas

Sveiki! Kaip sekasi ieškoti tinkamo hostingo tinklapiams, sukurtiems su Ruby on Rais? Mums nelabai, tad kilo idėja įkurti savo hostingą. Iš esmės, yra du variantai:

1) rimtas variantas - kiekvienam vartotojui izoliuotas jail'as su administratoriaus teisėmis jame Unix tipo sistemoje(pvz. OS FreeBSD), tad yra galimybė jame šeimininkauti, bet neturėti jokio poveikio kitiems vartotojams, o kartu nenukentėti pačiam. Būtų galimybė susiinstaliuoti norimus gemsus, reikiamus servisus. Trumpiau kalbant, privatus virtualus serveris su paruoštu steku (Apache 2.2, MySQL/PostgreSQL, Mongrel cluster) Ruby on Rais tinklapiams, valdomas per paruoštą Capistrano skriptą. Minusai - neaišku kiek potencialių vartotojų laisvai jaučiasi Unix tipo operacinėse sistemose, kiek iš jų naudoją Subversion ar kitą Capistrano palaikomą versijų kontrolės sistemą, ar, apskritai, turi noro ir galimybių naudoti Capistrano? Privalumai - bene geriausias sprendimas rinkoje, sunku ir sugalvoti ką nors geresnio.

2) paprastesnis - atsisakyti kai kurio Capistrano funkcionalumo ir automatizuoti pagrindines funkcijas, o likusias palikti administratoriui. Tai gal būt būtų izoliuotas jail'as, o gal ir paprastesnis saugumo sprendimas. Tačiau pagrindinis privalumas yra paprastumas, failai būtų įkeliami pasinaudojant FTP ar kuo nors saugesniu, apribotas priėjimas per SSH, o visas diegimas ir valdymas būtų atliekamas per WEB sąsają. Privalumai - lengva pasinaudoti iš praktiškai bet kokios OS be didelių pastangų. Minusai - apkarpytas automatizuotas funkcionalumas, daug likusio darbo administratoriui, o gerai pagalvojus - visai neprofesionalus sprendimas.

Tikslinė rinka - Lietuva ir šalys aplink ją, o vėliau - globali rinka.

Taigi balsuokite komentaruose už norimą variantą, laukiame ir nuomonių, pasiūlymų, diskusijų, ypač aktyviai dalyvaukite tie, kuriems išties aktuali ši paslauga!

Published on 14/06/2007 at 22h28 by Saulius Grigaitis

Bibliotekose esančių metodų pasiekimas view'suose

Sveiki! Mes niekur nedingom, mes tiesiog pasinėrę į įdomius projektus ir studijas. Kartais susiduriame su paprasta problemėle - pasirašome puikią biblioteką, moduliuose pasirašomę nepakeičiamų metodų, bet jų negalime pasiekti ActionView view'suose. Belieka džiaugtis, kad yra išeitis:
#lib/my_methods
module MyMethods
  protected
    def greatest_method
      "Hello world!"
    end
    #truputi magijos
    def self.included(base)
      base.send :helper_method, :greatest_method
    end
end
Iki!

Published on 04/06/2007 at 22h26 by Saulius Grigaitis

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

Sveiki! Pagaliau laisva valanda dar vienam įrašui apie BDD. Šįkart testuosime view'sus. RSpec suteikia galimybę juos testuoti, tad parodysiu kaip tai padaryti. Visų pirma reikia suprasti kas yra mock'ai ir stub'ai. Labai trumai - tai mechanizmai, kurie geba imituoti objektus, t.y. sukonstruojamas klasės egzempiorius, deklaruojami jo metodai ir tų metodų gražinami objektai. Iš esmės, tai tuščios juodos "dėžės", kurios sugeba priimti "signalus" ir į juos "sureaguoti", t.y. jam galima kviesti metodą ir gauti reikšmę. Rekomenduojama naudoti šį mechanizmą, nes tada visiškai atsirišama nuo kitų programos komponentų, kaip kontrolerių ar modelių. Kartu apsisaugoma nuo modelių pasikeitimo, t.y. naudojant realius modelius , atlikus pakeitimus juose, greičiausiai nugrius ir testas. Be abejo, šis mechanizmas leidžia rašyti view'sų testus dar neturint nei parašytų modelių, nei kontrolerių, iš kitos pusės, būtų visai neblogai matyti kaip modelio pakeitimas paveikė likusią programą, tad realus modelių ir mock'ų/stub'ų naudojimas priklauso nuo konkrečios situacijos. Susigeneruojam kontrolerį RSpec'o pagalba:
$./script/generate rspec_controller notebooks/index
Ir paprastas spec'as:
require File.dirname(__FILE__) + '/../../spec_helper'

describe "/notebooks/index", :behaviour_type => :view do
  before(:each) do
  end
  
  it "should have p tag with text 'My Notebook'" do
    render 'notebooks/index'
    response.should have_tag('p', 'My Notebook')
  end
end

Nepraeinam testo:
$ ruby notebooks/index_view_spec.rb
F

1)
'/notebooks/index should have p tag with text 'My Notebook'' FAILED
Expected at least 1 elements, found 0.
<false> is not true.
notebooks/index_view_spec.rb:10:
notebooks/index_view_spec.rb:3:

Finished in 0.07152 seconds

1 example, 1 failure
taisom view'są:
#app/views/notebooks/index.rhtml
<p>My Notebook</p>
šikart pasisekė kur kas geriau:
$ ruby notebooks/index_view_spec.rb
.

Finished in 0.056177 seconds

1 example, 0 failures
Tvarkoj, dabar kiek sudėtingiau - naudojam mock'us/stub'us:
require File.dirname(__FILE__) + '/../../spec_helper'

describe "/notebooks/index", :behaviour_type => :view do
  before(:each) do
    #susikonstruojam mock'ą
    @notebook = mock_model(Notebook)
    #panaudojam stub mechanizmą tam, kad @notebook galėtų reaguoti į "pages" metodą gražindamas 100
    @notebook.stub!(:pages).and_return(100)
    #naudojam assigns mechanizmą tam, kad galėtume @notebook pasiekti view'se
    assigns[:notebook] = @notebook
  end
  
  it "should have p tag with text 'My Notebook has 100 pages'" do
    @notebook.should_receive(:pages).exactly(1).times.and_return(100)
    render 'notebooks/index'
    response.should have_tag('p', 'My Notebook has 100 pages')
  end
end
Nepavyko:
$ ruby notebooks/index_view_spec.rb
F

1)
'/notebooks/index should have p tag with text 'My Notebook has 100 pages'' FAILED
<"My Notebook has 100 pages"> expected but was
<"My Notebook">.
<false> is not true.
notebooks/index_view_spec.rb:13:
notebooks/index_view_spec.rb:3:

Finished in 0.072341 seconds

1 example, 1 failure
taisom view'są:
<p>My Notebook has <%= @notebook.pages %> pages</p>
jėėė..veikia!
$ ruby notebooks/index_view_spec.rb
.

Finished in 0.059683 seconds

1 example, 0 failures
Laukite tęsinio!

Published on 17/05/2007 at 22h25 by Saulius Grigaitis

Powered by Publify Theme Frederic de Villamil | Photo Glenn