Novi Sad, 10. decembar 2011.

all

Novi skup je bio u Novom Sadu pod organizacijom Rendered Text-a. Obradovao nas je dobar odziv i to što je skoro polovina ljudi došla iz različitih gradova.

grupa 1

Posle uvodnog ćaskanja prešli smo na dvočasovni dojo gde su 4 grupe radile na zadatku - trebalo je isparsirati poslednje objavljenje gemove sa rubygems.org, ukrstiti one čiji je kod na GitHub-u, i sortirati ih po broju pratilaca koje autor gema ima. Ideja je da su novi gemovi zapaženih developera verovatno vredni pažnje.

grupa 2

Na kraju smo zajedno diskutovali šta je koja grupa uradila. Svi smo imali nešto drugačiji pristup - pisanju specova ili organizaciji koda, što je diskusiju učinilo veoma zanimljivom. Vreme je brzo prošlo tako da nam ostaje samo da uskoro ponovimo nešto slično.

grupa 3

Beograd, 25. jun 2011.

U organizaciji Jablana i Saleta, ovaj sastanak se zbio u komfornim prostorijama firme Vast. Na zadovoljstvo svih, posećenost je bila najveća do sada (13 ljudi), a recimo i da je Dalibor došao čak iz Skoplja.

Verovatno su zbog toga aktivnosti odstupile od predloženog rasporeda: glavne teme uvodnog razgovora su bila iskustva sa upotrebom Rubija na poslu i izazovi sa kojima se susreću ljudi čije se firme zasnivaju na drugim tehnologijama, ili gde ne postoji kultura pisanja testova.

To nas je odvelo u salu za prezentacije, gde je autor prisutnima pokazao primer primene BDD-a na popravljanje baga u real life kodu, što je bilo propraćeno diskusijom o RSpec-u i Cucumber-u, vežbanju, picom i krofnama (što se nastavilo na listi). Puno smo pričali i o uslovima za poslovanje u Srbiji.

Par linkova za kraj (Korijeva sabrana dela):

Ruby.rs sastanak u Bgd

Prošlo je već dosta vremena od poslednjeg ruby.rs sastanka i red je da organizujemo sledeći. Firma vast.com, u kojoj radimo Sale i ja, izašla nam je u susret i obezbediće prostorije i infrastrukturu.

Dakle, očekujemo sve rubiste u subotu, 25. juna u 16 sati u prostorijama Vasta - Trg Nikole Pašića 9, ulaz pored Novosti, žuta vrata.

Program bi bio sličan onom od prošlog sastanka:

  • sat-dva TDD coding dojo u parovima (nadam se da MarkoA nema ništa protiv da nam pripremi neki zadatak poput onog od prošlog puta)
  • sat-dva neobavezne diskusije ili prezentacije (ako ima interesenata: npr. izveštaj sa neke konferencije, predstavljanje softvera itd)

Molio bih samo da nam, ako ste u mogućnosti, unapred najavite prisustvo na mejl ["6a61626c616e40726164696f6e692e6361"].pack('H*') ili na Google mailing listu.

16. april 2011., Novi Sad

16. aprila 2011. naša mala družina se ponovo okupila u Novom Sadu, u kancelariji Rendered Text-a koja deluje kao poprilično fino mesto za rad. U odnosu na prethodni zvanični sastanak (od koga je prošlo dosta vremena), povećao se i broj Ruby programera u Srbiji, no na žalost nisu svi mogli da se pojave na sastanku. Nadamo se da će nas sledeći put biti u većem broju :)

Glavna aktivnost ovog sastanka je bio coding dojo, gde je zadatak koji smo rešavali bio PacMan. Ideja je bila da rešimo problem korišćenjem TDD. Problem smo rešavali tako što smo prvo pisali testove za određene akcije koje Pacman može da radi, a zatim pisali kod koji implementira te akcije. Tu smo se upoznali sa RSpecom. Na žalost ja do sada nisam imao velikog iskustva sa testiranjem (jedino od RSpeca što sam video je u RailsTutorialu), ali mislim da sam u tom nekom kratkom vremenu uz Darka i Marka uspeo da razumem neke nove stvari. TDD je odličan jer čoveka tera da u trenutku misli samo na jednu stvar koju implementira, i da se trudi da tu stvar što elegantnije reši. Ja sam tokom rada imao taj osećaj, jednostavno nisam gledao mnogo napred kao što inače radim, i razmišljao sam samo o malom delu koda koji smo u tom trenutku implementirali. Ako bismo kasnije odlučili da refaktorišemo neki kod, testovi su tu i bićemo sigurni da je sve ok. Naravno, i kada dodajemo nove stvari u kod ovime smo sigurni da se ništa što smo već napisali nije pokvarilo. Može se čoveku učiniti da ovakav razvoj traži više vremena, ali je to što smo sigurni da je sve dobro vredno truda. Pisanjem testova dobijamo praktično i dokumentaciju onoga što pišemo, tako da je to još jedan veliki plus (ko još voli da piše dokumentaciju nakon završenog posla :)).

Takođe smo bili podeljeni u grupe, i menjali se za računarom na određeno vreme, tako da je još jedna od “tehnika” sa kojom sam se ja prvi put ovde susreo i programiranje u paru. Divna tehnika, jer ovako možeš dosta da naučiš od ljudi sa kojima radiš, a i lakše je primetiti greške za vreme pisanja koda (bilo sintaksne ili semantičke prirode). Meni se ovo učinilo kao dobra tehnika da se dobije dobar kod, a i kasnije je neko pomenuo da Pivotal Labs radi isto tako. Poprilično zanimljiv način rada, koji bih voleo da praktikujem u svom svakodnevnom poslu.

Da sumiram svoje utiske: super ekipa (@darkofabijan, @jablanovic, @Lesa_ns, @marijanpovolni, @markoa, @sale87), super tema i poprilično uspešan sastanak. Nadam se da ćemo se uskoro okupiti u Beogradu u još većem broju i nastaviti da upoznajemo nove zanimljive tehnike za razvoj.

И онда орач рече, “Реци нам о раду.”* или .rvmrc

Овај чланак је инспирисан:

Често изађу нове алатке и ако нису приказане на повољан начин, могу лако да мимоиђу потенционалне кориснике. Тако сам чуо за RVM у пролазу и дуго времена нисам обраћао пажњу на њега. Тек касније сам почео да га користим.

Сада бих да опишем врло сажето корисне делове горе поменуте алатке, јер сматрам да сам сајт то не ради довољно добро, али више то радим из љутње што ја нисам почео да користим све могућности овог пројекта. Па да спречим да се то не деси другима.

Увод

Шта је RVM? Само име говори шта ради: Ruby Version Manager, омогућава више верзија рубија да функционишу на једној машини. Али не само то, интересантан део је да уз верзије рубија, омогућава и опстанак “џемова” за сваку верзију. Но може још и нешто да омогући: посебну инсталацију “џемова” за сваки пројекат на вашој машини.

И сада, најбољи део, то све може да се активира аутоматски, за сваки пројекат чим се нађете у самом директоријому пројекта. И за све је заслужан само један фајл: .rvmrc.

Како?

RVM је скуп скрипти написаних у bash-у, које створе .rvm у вашем $HOME директоријому, где стави све жељене верзије рубија и њихове “џемове”.

Ви онда можете да бирате коју верзију рубија желите овако:

$ rvm list
    rvm rubies

   ruby-1.9.2-p136 [ x86_64 ]
=> ruby-1.8.7-p330 [ x86_64 ]

$rvm use 1.9.2

што ће да пређе са верзије 1.8.7 на верзију 1.9.2.

Зимница

То је прилично једноставно, али како шта је са “џемовима”? И они могу да се инсталирају исто као и пре:

$ gem install bundler

али овако је још и лакше пошто ће све бити инсталирано у вашем радном простору, тј. $HOME/.rvm.

То је практично, али посебно интересанто је то што можете да правите посебне скупове “џемова” који одговарају само одређеним пројектима. На следећи начин:

$ rvm gemset create moj-izbor

Пребаците се у тај нови скуп:

$ rvm ruby-1.9.2-p136@moj-izbor

И онда инсталирате пожељне “џемове”.

Информације

Путем rvm info можете да видите више детаља о тренутном стању вашег RVM окружења. На пример, да видите где су тренутно инсталирани “џемови” које користите:

$ rvm info|egrep "GEM_(HOME|PATH)"
    GEM_HOME:     "/home/taliban/.rvm/gems/ruby-1.9.2-p136"
    GEM_PATH:     "/home/taliban/.rvm/gems/ruby-1.9.2-p136:/home/taliban/.rvm/gems/ruby-1.9.2-p136@moj-izbor"
#
# пази ово ------------------------------------------------------------------------------------^^^^^^^^^

Аутомата

Прилично једноставно, али може бити још и лакше, нарочито ако радите на више пројеката или ако тестирате један пројекат на разне начине. Створите фајл .rvmrc у самом директоријуму пројекта са:

 rvm ruby-1.9.2

тако да не морате ручно да мењате ваше окружење, већ ће RVM то радити аутоматски од тада чим уђете у тај пројекат.

* део из књиге “Пророк” од Халила Џибрана

(слика преузета са pressonline.rs)

Блог у синатри, епизода једанаеста

Да објасним мало о овом пројекту, да ставим кратак опис о њему:

emacs README.md

и додам:

Једноставни блог у Синатри за [http://ruby.rs](http://ruby.rs)

Simple blog in Sinatra for [http://ruby.rs](http://ruby.rs)

па то ставим у гит:

$ git add README.md 
$ git commit -m "додајем README.md"
[master 67807a9] додајем README.md
 1 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 README.md

Било би добро да када посетим неку страницу која не постоји, да прикажем грешку, попут: Nothing to see here, move along.

Отворим нови огранак у гиту:

$ git checkout -b 404
Switched to a new branch '404'

У blurgh_spec.rb, додам следећу спецификацију:

describe "error page" do
  it "should display a pre defined 404 message" do
    get '/non-existent-page'
    last_response.body.should match("Nothing to see here")
  end
end    

покренем тест који наравно не пролази:

Failures:

  1) blurgh routes and content error page should display a pre defined 404 message
     Failure/Error: get '/non-existent-page'
     Errno::ENOENT:
       No such file or directory - spec/fixtures/non-existent-page.md
     # ./blurgh.rb:50:in `readlines'
     # ./blurgh.rb:50:in `get_post'
     # ./blurgh.rb:61:in `block in <top (required)>'
     # ./spec/blurgh_spec.rb:67:in `block (4 levels) in <top (required)>'

Да бих то исправио, морам да променим get_post методу из овога:

def get_post(post)
  header, body = File.readlines(Config.options['store'] + "/" + post + ".md", "")
end

у ово:

def get_post(post)
  begin
    header, body = File.readlines(Config.options['store'] + "/" + post + ".md", "")
  rescue Errno::ENOENT
    not_found
  end
end

not_found do
  "Nothing to see here"
end

Да објасним шта се овде изменило: beginrescue омогућава да се изврши File.readlines метода, али ако се види грешка коју се приказала предходно: Errno::ENOENT онда да се покрене not_found метода. not_found метода је специфична, јер је већ унапред дефинисана у самом sinatra пројекту за употребу у оваквим случајевима, када се појављује грешка. И уместо да се види грешка, путем ње, може да се прикаже пожељно упозорење.

Покренем тестирање и све пролази.

Па да испробам и саму апликацију, додам:

store: spec/fixtures

у setup.yaml. И покренем:

$ shotgun blurgh.rb     

Видим два чланка који се налазе под spec/fixtures, као што се видело и пре. И да би испробао нову методу укуцам http://localhost:9393/let2 на којој се види порука: Nothing to see here.

Да то сада додам у гит:

$ git status
# On branch 404
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   setup.yaml
#   modified:   spec/blurgh_spec.rb
#

$ git add blurgh.rb setup.yaml spec/blurgh_spec.rb

$ git commit -m "приказивање грешке за непостојећу страну"
[404 fb7e5c8] приказивање грешке за непостојећу страну
 3 files changed, 19 insertions(+), 2 deletions(-)

И да додам то у главно стабло гита:

$ git checkout master
Switched to branch 'master'

$ git merge 404
Updating 67807a9..fb7e5c8
Fast forward
 blurgh.rb           |   11 ++++++++++-
 setup.yaml          |    3 ++-
 spec/blurgh_spec.rb |    7 +++++++
 3 files changed, 19 insertions(+), 2 deletions(-)

Сада има сисем за блоговање, написан у 75 линија рубија. Који је тестиран. Може још шта да се дода, систем није комплетан. Али има добру основу.

Сав овај рад, окачен је овде.

Блог у синатри, епизода десета

Да би документација тестова била читљивија, преправим

context "the view" do

у

context "the index view" do    

и додам нови тест:

context "the post view" do
  it "should show post content" do
    get '/let'
    last_response.body.should match("25. марта 1923. - Слетањем авиона на линији Париз-Цариград на")
  end
end

Тај тест наравно не пролази, зато што не постоји ништа што би приказало садржај фајла. То могу да омогућим тако што створим методу која ће приказивати индивидалне чланке:

get '/:post' do
  @config, @content = get_post(:post)
  erb :post
end

и додам и post.erb у views/:

<%= @content %>

само да би било што једноставније и да би омогућио да тест прође.

Покренем тест, и он пада са следећом поруком:

1) blurgh routes and content the post view should show post content
     Failure/Error: get '/let'
     TypeError:
       can't convert Symbol into String
     # ./blurgh.rb:46:in `+'
     # ./blurgh.rb:46:in `get_post'
     # ./blurgh.rb:58:in `block in <top (required)>'
     # ./spec/blurgh_spec.rb:44:in `block (4 levels) in <top (required)>'

што значи да се get_post метода не добија одговарајући аргумент. Зато преправим:

@config, @content = get_post(:post)

у

@config, @content = get_post(params[:post])  

покренем тестирање поново и све прође.

Но нисам баш задовољан предходним тестом, он само проверава прву линију чланка

last_response.body.should match("25. марта 1923. - Слетањем авиона на линији Париз-Цариград на")

Било би боље када би учитавао сав фајл. Зато изменим тест у:

it "should show post content" do
  get '/let'
  article = File.readlines("spec/fixtures/let.md", "")[1]
  last_response.body.should match(article)
end

јер ми изгледа читљивије. Па покренем тест… који прође.

Додам напредак у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   spec/blurgh_spec.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   views/post.erb
no changes added to commit (use "git add" and/or "git commit -a")

Овде се види да је views/post.erb нови фајл који још није унет у гит. Зато требам да га додам са осталим фајловима где је направљена промена:

$ git add blurgh.rb spec/blurgh_spec.rb views/post.erb 

$ git commit -m "приказивање чланака"
[posts 5540485] приказивање чланака
 3 files changed, 20 insertions(+), 5 deletions(-)
 create mode 100644 views/post.erb

Било би добро да додам да може да се види наслов када се прикаже чланак.

it "should show post title" do
  get '/let'
  config = File.readlines("spec/fixtures/let.md", "")[0]
  post_options = YAML.load(config)
  last_response.body.should match(post_options['title'])
end

Покренем тест, и он не пролази, зато што немам начин да прикажем наслов на страници која приказује чланке.

Зато преуредим get ‘/:post’ у:

get '/:post' do
  @config, @content = get_post(params[:post])
  @title = YAML.load(@config)['title']
  erb :post
end

подесим post.erb:

<h2>
  <%= @title %>
<h2>

<%= @content %>

покренем тест и он прође. Зато да учитам у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   setup.yaml
#   modified:   spec/blurgh_spec.rb
#   modified:   views/post.erb

$ git add blurgh.rb spec/blurgh_spec.rb views/post.erb 
$ git commit -m "приказивање наслова чланака"

Сада да додам датум за чланке. Прво да додам тест:

it "should show post date" do
  get '/let'
  config = File.readlines("spec/fixtures/let.md", "")[0]
  post_options = YAML.load(config)
  last_response.body.should match("20110325")
end

Па да се уверим да тест пада:

$ bundle exec rake

И онда да преправим get ‘/:post’:

get '/:post' do
  @config, @content = get_post(params[:post])
  post_options = YAML.load(@config)
  @title = post_options['title']
  @date =  post_options['date']
  erb :post
end

и да променим post.erb:

<h2>
  <%= @title %>
</h2>

<div>
  <%= @date %>
</div>

<%= @content %>

Покренем тест и он прође. Па да додам напредак у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   spec/blurgh_spec.rb
#   modified:   views/post.erb
#

$ git add blurgh.rb spec/blurgh_spec.rb views/post.erb

$ git commit -m "приказивање датума чланака"
[posts d005862] приказивање датума чланака
 3 files changed, 14 insertions(+), 1 deletions(-)

Па да додам овај огранак у главни, тако што се прво пребацим у master огранак:

$ git checkout master
Switched to branch 'master'

и сада да додам posts огранак у master:

$ git merge posts
Auto-merging blurgh.rb
CONFLICT (add/add): Merge conflict in blurgh.rb
Auto-merging spec/blurgh_spec.rb
CONFLICT (add/add): Merge conflict in spec/blurgh_spec.rb
Auto-merging spec/config_spec.rb
CONFLICT (add/add): Merge conflict in spec/config_spec.rb
Auto-merging views/index.erb
CONFLICT (add/add): Merge conflict in views/index.erb
Automatic merge failed; fix conflicts and then commit the result.     

Ово је ново. Порука ми говори да измене које сам направио се не подударају апсолутно са садржајем master огранку.

Да би то решио, морам да направим измене ручно. Отворим прво blurgh.rb и видим следеће:

module Config
<<<<<<< HEAD:blurgh.rb
  def self.title
    options["title"]
  end

=======

Да би то поправио, морам сам да коригујем у едитору. Бацим се на посао, фајл по фајл… То су тестови да ми помогну да ништа није пропуштено.

Али ако у међу времену покушам да додам огранак posts поново, добићу следећу грешку:

$ git merge posts
fatal: You have not concluded your merge. (MERGE_HEAD exists)

то је зато што гит није могао аутоматски да дода нови огранак, па је направио нове фајлове, спајањем постојећих фајлова у master огранку са фајловима из posts огранка.

Да би то решио, морам прво да средим све фајлове и да сви тестови пролазе. Затим да видим статус гита, и тек онда да додам фајлове:

$ git status
blurgh.rb: needs merge
spec/blurgh_spec.rb: needs merge
spec/config_spec.rb: needs merge
views/index.erb: needs merge
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   spec/fixtures/let.md
#   new file:   spec/fixtures/o-kapadokiji.md
#   new file:   views/post.erb
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   unmerged:   blurgh.rb
#   unmerged:   spec/blurgh_spec.rb
#   unmerged:   spec/config_spec.rb
#   unmerged:   views/index.erb
#

$ git add blurgh.rb spec/config_spec.rb spec/blurgh_spec.rb views/index.erb

али требам да додам и нове фајлове:

$ git add spec/fixtures/o-kapadokiji.md spec/fixtures/let.md views/post.erb    

На крају учитам све промене у гит:

$ git commit -m "додајем рад на чланцима"    

Ако погледам лог гита, видим да су то и поруке из posts огранка:

$ git log --oneline | head -5
88f2449 додајем рад на чланцима
d005862 приказивање датума чланака
4a40483 приказивање наслова чланака
5540485 приказивање чланака
a79f8c8 смршао blurgh_spec.rb    

Наставља се …

Блог у синатри, епизода девета

Резултат тестирања изгледа овако:

$ bundle exec rake

Config
  .title
    should return value
  when the title is blank
    should return nothing
  when the title is missing
    should return nil
  .store
    should return value
  .all
    should return all the values

blurgh
  routes and content
    should respond to /
    should have a title
    the view
      should have a body html elements
      the posts URLs should be listed
  get_posts
    should return posts
    on the index page
      the posts URLs should be listed
      the posts titles should be shown
  get_post
    should return a post in two parts

Finished in 0.02477 seconds
13 examples, 0 failures

То је зато што сам одредио да формат резултата теста буде: —format doc у .rspec фајлу.

Но проблем је што сам ставио:

on the index page
  the posts URLs should be listed
  the posts titles should be shown

да је унутар get_posts. То би приуредим и да наместим да буде читљивије.

Тренутно blurgh_spec.rb изгледа овако:

# -*- coding: utf-8 -*-
require 'spec_helper'

describe "blurgh" do

  def app
    @app ||= Sinatra::Application
  end

  context "routes and content" do
    before :each do
      YAML.should_receive(:load_file)\
       .with("setup.yaml").and_return({"title" => "Naslov", "store" => "posts"})
    end

    it "should respond to /" do
      get '/'
      last_response.should be_ok
    end

    it "should have a title" do
      get '/'
      last_response.body.should match("Naslov")
    end

    context "the view" do
      it "should have a body html elements" do
        get '/'
        last_response.body.should match("<body>")
      end

      it "the posts URLs should be listed" do
        get '/'
        # last_response.body.should match("let")
        last_response.body.should match('<a href="let">Авионски лет</a>')
      end
    end
  end

  describe "get_posts" do

    before do
      YAML.should_receive(:load_file).with("setup.yaml")\
        .and_return({"title" => "Naslov", "store" => "spec/fixtures"})
    end

    it "should return posts" do
      first_article = File.readlines("spec/fixtures/o-kapadokiji.md", "")[1]
      second_article = File.readlines("spec/fixtures/let.md", "")[1]
      store = Config.all['store']

      get_posts(store).should == \
      [[20110325,  {"url" => "let", "title" => "Авионски лет", "body" => "#{second_article}"}],\
       [20110324, {"url" => "o-kapadokiji", "title" => "Кападокија", "body" => "#{first_article}"}]]
    end

    context "on the index page" do
      it "the posts URLs should be listed" do
        get '/'
        last_response.body.should match("let")
        last_response.body.should match("o-kapadokiji")
      end

      it "the posts titles should be shown" do
        get '/'
        last_response.body.should match('<a href="let">Авионски лет</a>')
        last_response.body.should match('<a href="o-kapadokiji">Кападокија</a>')
      end

    end

  end

  describe "get_post" do

    before do
      YAML.should_receive(:load_file).with("setup.yaml")\
        .and_return({"title" => "Naslov", "store" => "spec/fixtures"})
    end

    it "should return a post in two parts" do
      get_post("o-kapadokiji").should have(2).parts
    end

  end

end

Да би то променио, променим прво:

context "routes and content" do
  before :each do
    YAML.should_receive(:load_file)\
     .with("setup.yaml").and_return({"title" => "Naslov", "store" => "posts"})
  end

у

context "routes and content" do
  before :each do
    YAML.should_receive(:load_file)\
     .and_return({"title" => "Naslov", "store" => "spec/fixtures"})
  end      

Покренем тестирање и оно прође.

Али сада се то понавља три пута, што да не ставим то пре почетка свих тестова:

before :each do
  YAML.should_receive(:load_file)\
    .and_return({"title" => "Naslov", "store" => "spec/fixtures"})
end

а остале избришем. Покренем тест и пролази без жалби.

Вратим се предходној жељи где би да групишем сродне тестове, тако да преуредим фајл да изгледа овако:

# -*- coding: utf-8 -*-
require 'spec_helper'

describe "blurgh" do

  before :each do
    YAML.should_receive(:load_file)\
      .and_return({"title" => "Naslov", "store" => "spec/fixtures"})
  end

  def app
    @app ||= Sinatra::Application
  end

  context "routes and content" do

    it "should respond to /" do
      get '/'
      last_response.should be_ok
    end

    context "the view" do

      it "should have a title" do
        get '/'
        last_response.body.should match("Naslov")
      end

      it "should have a body html elements" do
        get '/'
        last_response.body.should match("<body>")
      end

      it "the posts titles should be shown" do
        get '/'
        last_response.body.should match('<a href="let">Авионски лет</a>')
        last_response.body.should match('<a href="o-kapadokiji">Кападокија</a>')
      end

    end
  end

  describe "get_posts" do

    it "should return posts" do
      first_article = File.readlines("spec/fixtures/o-kapadokiji.md", "")[1]
      second_article = File.readlines("spec/fixtures/let.md", "")[1]
      store = Config.all['store']

      get_posts(store).should == \
      [[20110325,  {"url" => "let", "title" => "Авионски лет", "body" => "#{second_article}"}],\
       [20110324, {"url" => "o-kapadokiji", "title" => "Кападокија", "body" => "#{first_article}"}]]
    end

  end

  describe "get_post" do

    it "should return a post in two parts" do
      get_post("o-kapadokiji").should have(2).parts
    end

  end

end

Што је у мом мишљењу, читљивије и логичније. Покренем тест и он прође. Да додам у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   spec/blurgh_spec.rb
#

$ git add spec/blurgh_spec.rb 
$ git commit -m "смршао blurgh_spec.rb"
[posts 0e3722c] смршао blurgh_spec.rb
 1 files changed, 18 insertions(+), 34 deletions(-)

Блог у синатри, епизода осма

Сада да претворим фајлове у HTML линкове.

Прво направим тест под describe “get_posts”:

it "the posts titles should be shown" do
  get '/'
  last_response.body.should match('<a href="let">Авионски лет</a>')
  last_response.body.should match('<a href="o-kapadokiji">Кападокија</a>')
end

Онда да подесим index.erb:

<h1><%= @title %></h1>

<% @posts.each do |post| %>
   <a href="<%= post[1]['url'] %>"><%= post[1]['title'] %></a>
<% end %>

покренем тест, и он успешно прође. Да додам напредак у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   spec/blurgh_spec.rb
#   modified:   views/index.erb
#

$ git add spec/blurgh_spec.rb views/index.erb

$ git commit -m "омогућено претварање наслова чланака у URL на почетној страници"
[posts b1ad84c] омогућено претварање наслова чланака у URL на почетној страници
 2 files changed, 8 insertions(+), 1 deletions(-)

Било би добро да сада могу да видим и саме чланке. Пошто су они сами фајлови, треба ми нека метода која ће да их чита. Да кренем са тестом:

describe "get_post" do
  pending
end

покренем тестирање:

$ bundle exec rspec spec/blurgh_spec.rb 

и оно пада са:

Pending:
  blurgh get_post 
    # Not Yet Implemented
    # ./spec/blurgh_spec.rb:69

Када размислим мало боље о овоме, како ће се фајлови читати, то ће бити у суштини овако:

/име-фајла

Но како они треба да изгледају кад су прочитани. Слично као и када их get_posts чита, сачињени од два дела. Први део је YAML конфигурација а други, сам садржај.

Тако да пишем следећи тест:

describe "get_post" do
  it "should return a post in two parts" do
    get_post("o-kapadokiji").should have(2).parts
  end
end

Покренем тест, који пада, зато пишем следећу методу:

def get_post(post)
  header, body = File.readlines(Config.options['store'] + "/" + post + ".md", "")
end

покренем тест и он пада:

1) blurgh get_post should return a post in two parts
   Failure/Error: get_post("o-kapadokiji").should have(2).parts
   NoMethodError:
     undefined method `+' for nil:NilClass
   # ./blurgh.rb:46:in `get_post'
   # ./spec/blurgh_spec.rb:76:in `block (3 levels) in <top (required)>'

46 линија је средина предходне методе и она очекује конфигурацију за Config. Зато преправим тест да изгледа овако:

describe "get_post" do

  before do
    YAML.should_receive(:load_file).with("setup.yaml")\
      .and_return({"title" => "Naslov", "store" => "spec/fixtures"})
  end

  it "should return a post in two parts" do
    get_post("o-kapadokiji").should have(2).parts
  end

end

Покренем тест, који успешно прође.

Па додам то у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   spec/blurgh_spec.rb
no changes added to commit (use "git add" and/or "git commit -a")

$ git add blurgh.rb spec/blurgh_spec.rb

$ git commit -m "учитавање чланка"
[posts b1290dd] учитавање чланка
 2 files changed, 17 insertions(+), 0 deletions(-)

Овде би било добро да обратим пажњу на:

get_post("o-kapadokiji").should have(2).parts     

специфично на have(2).parts део. parts је само реч која је ту додана да би тест био читљивији. Она може бити замењена са неком другом и тест ће и даље да прође. На пример:

get_post("o-kapadokiji").should have(2).сарма

пролази као важећи тест.

Ако заменим сарма са class, добијам следећу грешку:

1) blurgh get_post should return a post in two parts
     Failure/Error: get_post("o-kapadokiji").should have(2).class
     NoMethodError:
       undefined method `matches?' for RSpec::Matchers::Have:Class
     # ./spec/blurgh_spec.rb:76:in `block (3 levels) in <top (required)>'    

што ми указује на то да је have(2) сачињена од Have класе. Ако погледам https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/have.rb видим следеће под примерима:

#   should have(number).named_collection__or__sugar

# If the receiver IS the collection, you can use any name
# you like for <tt>named_collection</tt>. We'd recommend using
# either "elements", "members", or "items" as these are all
# standard ways of describing the things IN a collection.

па примери:

# Passes if [1,2,3].length == 3
# [1,2,3].should have(3).items #"items" is pure sugar

Наставак следи…

Блог у синатри, епизода седма

Било би пожељно да овај систем може да учита чланке и да их прикаже. Идеја је да чита чланке из одређеног директоријума који је наведен у конфигурационом фајлу setup.yaml на следећи начин:

store: posts

За то да направим нови огранак у гиту:

$ git checkout -b posts

Прво желим да Config чита и .store врати име директоријума. То означим у тесту овако:

describe ".store" do
  it "should return value" do
    YAML.should_receive(:load_file).with("setup.yaml").and_return({"store" => "posts"}) 
    Config.store.should match("posts")
  end
end

покренем тест и он не прође:

1) Config.store should return value
   Failure/Error: Config.store.should match("posts")
   NoMethodError:
     undefined method `match' for nil:NilClass

Онда у Config додам нову методу:

def self.store
  options["store"]
end

Поново покренем тест, и овај пут прође. Додам тај рад у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   spec/config_spec.rb
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git add blurgh.rb spec/config_spec.rb
$ git commit -m "додана метода Config.store"
[posts b243446] додана метода Config.store
 2 files changed, 12 insertions(+), 0 deletions(-)

Сада имам тачно одређено место где ће да се држе чланци, али још немам ништа да их учитам. То желим да ми уради метода get_posts која ће да врати све фајлове директоријума на који указује store. Тест за ту методу изгледа овако:

describe "get_posts" do
  it "should return posts" do
    Dir.should_receive(:entries).with("posts").and_return(["sample_post.md", "another_sample_post.md"])
    get_posts.should == ["sample_post.md", "another_sample_post.md"]
  end
end

И кад покренем тестове, добијем следеће:

 1) blurgh get_posts should return posts
Failure/Error: get_posts.should == ["sample_post.md", "another_sample_post.md"]
NameError:
  undefined local variable or method `get_posts' for #<RSpec::Core::ExampleGroup::Nested_2::Nested_2:0x00000001f12da8>

Напишем следећу методу у blurgh.rb:

def get_posts
  Dir.entries(Config.store)
end

покренем тестирање поново и тест прође.

Но то ми није довољно, јер како ћу да организујем чланке, овако су враћени у “хрпи”. Било би боље када би они могли да буду организовани по датуму.

И како ћу за наслов ?

То би могло да се реши овако: сваки чланак да буде сачињен делимично у YAML формату, то јест, први параграф, а остатак да буде сам чланак. На пример да садржај фајла изгледа овако:

title: Кападокија
date: 20110324

Премало се зна о Кападокији пре него што је постала персијска
сатрапија. Зна се само да је Кападокија била у време Хета веома
значајна земља и да се управо у њој налазила хетска престоница
Хатуша. Иако сатрапија у персијско време, Кападокија је сачувала
известан степен самосталности.

То би било у реду за један чланак, али шта са get_posts методом ? У каквом формату да врати чланке ?

Било би добро да буду враћени у следећем формату:

[[["20110324"], {"title" => "Кападокија", "body" => "Премало се зна о Кападокији..."}]]

На тај начин могу лако да сортирам чланке.

И шта још би могао да ту додам? Адресу, тј. URL сваког чланка. Намерно бих да title буде наслов у самој страници а да име фајла буде URL. Тако да ако предходни пример чланак назовем: o-kapadokji.md, формат који би get_posts метода враћала би био:

[["20110324", {"url" => "o-kapadokiji", "title" => "Кападокија", "body" => "Премало се зна о Кападокији..."}]]

Да то направим да буде направићу два фајла која ћу да користим за тестирање:

$ mkdir spec/fixtures
$ emacs spec/fixtures/o-kapadokiji.md 

И ту ставим горе наведени текст. Онда отворим други фајл:

$ emacs spec/fixtures/let.md

Ставим следећи текст:

title: Авионски лет
date: 20110325

25. марта 1923. - Слетањем авиона на линији Париз-Цариград на аеродром
у Панчеву, Краљевина СХС постала део тада малобројне породице држава
повезаних ваздушним саобраћајем.

Сада да преправим тест:

describe "get_posts" do
  it "should return posts" do
    first_article = File.readlines("spec/fixtures/o-kapadokiji.md", "")[1]
    second_article = File.readlines("spec/fixtures/let.md", "")[1]
    YAML.should_receive(:load_file).with("setup.yaml").and_return({"title" => "Naslov", "store" => "spec/fixtures"}) 

    get_posts.should == \
    [[20110325,  {"url" => "let", "title" => "Авионски лет", "body" => "#{second_article}"}],\
     [20110324, {"url" => "o-kapadokiji", "title" => "Кападокија", "body" => "#{first_article}"}]]

  end
end

Да објасним овде да first_article и second_article су само фајлови учитани, да не бих писао цео њихов садржај. И store сада чита фајлове из spec/fixtures.

Покренм тестирање:

$ bundle exec rspec spec/blurgh_spec.rb

али тест не прође:

1) blurgh get_posts should return posts
     Failure/Error: get_posts.should == \
     Errno::ENOENT:
       No such file or directory - posts

То је зато што на самом почетку теста имам:

before :each do
  YAML.should_receive(:load_file).with("setup.yaml").and_return({"title" => "Naslov", "store" => "posts"}) 
end

који попуњава store са posts. Да би то спречио одвојићу прва неколико теста у једну целину на следећи начин:

 context "routes and content" do
   before :each do
     YAML.should_receive(:load_file).with("setup.yaml").and_return({"title" => "Naslov", "store" => "posts"}) 
   end

   it "should respond to /" do
     get '/'
     last_response.should be_ok
   end

   it "should have a title" do
     get '/'
     last_response.body.should match("Naslov")
   end

   context "the view" do
     it "should have a body html elements" do
       get '/'
       last_response.body.should match("<body>")
     end
   end
 end

док је други тест сам за себе.

Покренем тест и он опет не пролази:

1) blurgh get_posts should return posts
   Failure/Error: [["20110325"], {"url" => "let", "title" => "Авионски лет", "body" => "#{second_article}"}]]
     expected: [[["20110324"], {"url"=>"o-kapadokiji", "title"=>"Кападокија",
     ...

што је и пожељан резултат. Јер ако се анлизира грешка, драгачија је од предходне: не жали се о томе како не може да прочита неки фајл, већ да резултат методе није идентичан очекиваном.

Сада да прилагодим методу, да би прошао тест:

def get_posts

  all_posts = Hash.new {
    |h,k| h[k] = Hash.new(&h.default_proc) 
  }

  post_dir = File.join(Config.store + "/" + "*.md")

  Dir.glob(post_dir).each do |post|
    header, body = File.readlines(post, "")
    data = YAML.load(header)
    all_posts[data['date']]['url']   = post.gsub("\.md", "").gsub(Config.store + "/", "")
    all_posts[data['date']]['title'] = data['title']
    all_posts[data['date']]['body']  = body
  end

  all_posts.sort.reverse

end

all_posts je “хаш хашева” (следи објашњење!). Dir.glob чита фајлове из Config.store и попуњава all_posts.

Покренем тест и … он падне:

1) blurgh get_posts should return posts
   Failure/Error: YAML.should_receive(:load_file).with("setup.yaml").and_return({"title" => "Naslov", "store" => "spec/fixtures"})
     (Syck).load_file("setup.yaml")
         expected: 1 time
         received: 3 times

Ово ми говори да сам добио жељени резултат, но само је метода позивала YAML.load_file више него што је означено у тесту. Овде ми тест не помаже да програм буде коректан, већ и ефикаснији.

Преправим методу у:

def get_posts

  all_posts = Hash.new {
    |h,k| h[k] = Hash.new(&h.default_proc) 
  }

  store = Config.store
  post_dir = File.join(store + "/" + "*.md")

  Dir.glob(post_dir).each do |post|
    header, body = File.readlines(post, "")
    data = YAML.load(header)
    all_posts[data['date']]['url']   = post.gsub("\.md", "").gsub(store + "/", "")
    all_posts[data['date']]['title'] = data['title']
    all_posts[data['date']]['body']  = body
  end

  all_posts.sort.reverse

end

покренем тест и сада прође. Што значи да треба да се убаци у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   spec/blurgh_spec.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   spec/fixtures/
no changes added to commit (use "git add" and/or "git commit -a")
$ git add blurgh.rb spec/blurgh_spec.rb spec/fixtures/
$ git status
# On branch posts
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   blurgh.rb
#   modified:   spec/blurgh_spec.rb
#   new file:   spec/fixtures/let.md
#   new file:   spec/fixtures/o-kapadokiji.md
#
$ git commit -m "урађена метода get_posts"
[posts fba900e] урађена метода get_posts
 4 files changed, 81 insertions(+), 31 deletions(-)
 rewrite spec/blurgh_spec.rb (73%)
 create mode 100644 spec/fixtures/let.md
 create mode 100644 spec/fixtures/o-kapadokiji.md

Наставља се… (у следећој епизоди RSpec пије пиво и осваја свет)

Да видим како би то сада могао да организујем да се листа чланака прикаже на почетној страни.

Додам следећи тест:

context "on the index page" do
  it "the posts URLs should be listed" do
    get '/'
    last_response.body.should match("let")
    last_response.body.should match("o-kapadokiji")
  end
end

под **describe “get_posts” **, покренем тестирање и оно пада:

1) blurgh get_posts on the index page the posts URLs should be listed
   Failure/Error: last_response.body.should match("let")
     expected "<html>\n  <body>\n    <h1>Пази сад</h1>\n\n  </body>\n</html>\n" to match "let"
   # ./spec/blurgh_spec.rb:47:in `block (4 levels) in <top (required)>'

што ми указује да мој тест чита прави setup.yaml, што треба исправити тако што наместим да се YAML објекат учита пре самог теста. То урадим на следећи начин, тако што омогућим у before блоку:

describe "get_posts" do

  before do
    YAML.should_receive(:load_file).with("setup.yaml")\
      .and_return({"title" => "Naslov", "store" => "spec/fixtures"})
  end

  it "should return posts" do
    first_article = File.readlines("spec/fixtures/o-kapadokiji.md", "")[1]
    second_article = File.readlines("spec/fixtures/let.md", "")[1]

    get_posts.should == \
    [[20110325,  {"url" => "let", "title" => "Авионски лет", "body" => "#{second_article}"}],\
     [20110324, {"url" => "o-kapadokiji", "title" => "Кападокија", "body" => "#{first_article}"}]]
  end

  context "on the index page" do
    it "the posts URLs should be listed" do
      get '/'
      last_response.body.should match("let")
      last_response.body.should match("o-kapadokiji")
    end
  end

end

Тест опет пада, али са садржајем који сам ја дефинисао у тесту:

1) blurgh get_posts on the index page the posts URLs should be listed
   Failure/Error: last_response.body.should match("let")
     expected "<html>\n  <body>\n    <h1>Naslov</h1>\n\n  </body>\n</html>\n" to match "let"
   # ./spec/blurgh_spec.rb:53:in `block (4 levels) in <top (required)>'    

Сада да наместим да index.html добије линкове чланака, преправим оригинални get ‘/’ у:

get '/' do
  @title = Config.title
  @posts = get_posts
  erb :index 
end

и у index.erb додам:

<% @posts.each do |post| %>
   <%= post[1]['url'] %>
<% end %>    

покренем тест, али овај пут само одређени, задњи тест на којем радим:

bundle exec rspec -e "blurgh get_posts on the index page" spec/blurgh_spec.rb         

резултат је:

Failures:

    1) blurgh get_posts on the index page the posts URLs should be listed
       Failure/Error: YAML.should_receive(:load_file).with("setup.yaml")\
         (Syck).load_file("setup.yaml")
             expected: 1 time
             received: 2 times
       # ./spec/blurgh_spec.rb:36:in `block (3 levels) in <top (required)>'

што ми говори да очекивани резултат теста је тачан, али да се конфигурациони фајл чита два пута. Једном се чита:

@title = Config.title

а други пут:

@posts = get_posts

Могао бих да преправим Config модул да чита резултате само једном и да их све врати. Нешто овако:

self.all
  options["title"], options["store"]
end

Али што се тиче овог чланка, овако је једноставније за сада, ма да можда није изванредно ефикасно. Но да не заборавим, ставићу белешку да се овоме вратим и погледам поново:

module Config
  # TODO: moze biti efikasnije

Сада да подесим тест:

YAML.should_receive(:load_file).exactly(2).times.with("setup.yaml")\
  .and_return({"title" => "Naslov", "store" => "spec/fixtures"})

Додао сам: .exactly(2).times што ће омогућити да тест прође:

$ bundle exec rspec -e "blurgh get_posts on the index page" spec/blurgh_spec.rb
Run filtered using {:full_description=>/(?-mix:blurgh get_posts on the index page)/}

blurgh
  routes and content
    the view
  get_posts
    on the index page
      the posts URLs should be listed

Finished in 0.02366 seconds
1 example, 0 failures

Покренем тест за цео програм и добијам следећу грешку за четири случаја:

blurgh
  routes and content
    should respond to / (FAILED - 1)
    should have a title (FAILED - 2)
    the view
      should have a body html elements (FAILED - 3)
  get_posts
    should return posts (FAILED - 4)

 ...    

 Failure/Error: YAML.should_receive(:load_file).with("setup.yaml").and_return({"title" => "Naslov", "store" => "posts"})
   (Syck).load_file("setup.yaml")
       expected: 1 time
       received: 2 times    

И то исто могу да исправим тако што учиним исту измену и за те тестове, али опет добијам следећу грешку:

1) blurgh get_posts should return posts
   Failure/Error: YAML.should_receive(:load_file).exactly(2).times.with("setup.yaml")\
     (Syck).load_file("setup.yaml")
         expected: 2 times
         received: 1 time
   # ./spec/blurgh_spec.rb:37:in `block (3 levels) in <top (required)>'

Што ми указује да ћу више труда потрошити на измени тестова, него да применим нову методу all у Config модулу. Зато вратим ове две задње измене и додам нови тест:

describe ".all" do
  it "should return all the values" do
    YAML.should_receive(:load_file).with("setup.yaml")\
      .and_return({"title" => "Naslov", "store" => "posts"}) 
    Config.all.should == {"title" => "Naslov", "store" => "posts"}
  end
end

са методом:

def self.all
  options
end

Покренм тестове за Config:

$ bundle exec rspec spec/config_spec.rb     

сви прођу.

Сада да преправим тестове за blurgh:

it "should return posts" do
  first_article = File.readlines("spec/fixtures/o-kapadokiji.md", "")[1]
  second_article = File.readlines("spec/fixtures/let.md", "")[1]
  store = Config.all['store']

  get_posts(store).should == \
  [[20110325,  {"url" => "let", "title" => "Авионски лет", "body" => "#{second_article}"}],\
   [20110324, {"url" => "o-kapadokiji", "title" => "Кападокија", "body" => "#{first_article}"}]]
end

тест у овом случају неће да прође јер get_posts метода очекује аргумент. Њу преправим у:

def get_posts(store)

  all_posts = Hash.new {
    |h,k| h[k] = Hash.new(&h.default_proc) 
  }

  post_dir = File.join(store + "/" + "*.md")

  Dir.glob(post_dir).each do |post|
    header, body = File.readlines(post, "")
    data = YAML.load(header)
    all_posts[data['date']]['url']   = post.gsub("\.md", "").gsub(store + "/", "")
    all_posts[data['date']]['title'] = data['title']
    all_posts[data['date']]['body']  = body
  end

  all_posts.sort.reverse

end

Покренем тест, али он пада:

1) blurgh routes and content should respond to /
   Failure/Error: get '/'
   ArgumentError:
     wrong number of arguments (0 for 1)
   ...

Порука је да у get ‘/’ користим нешто са погрешним бројем аргумената. Изменим блок да сада користи аргумент у позиву у get_posts методи:

get '/' do
  blurgh_conf = Config.all
  @title = blurgh_conf['title']
  @posts = get_posts(blurgh_conf['store'])
  erb :index 
end

Покренем тестирање и све прође:

$ bundle exec rake

И пре него што убацим то у гит, било би добро да погледам како то заправо изгледа.

Отворим setup.yaml и додам:

store: spec/fixtures

покренем апликацију:

$ shotgun blurgh.rb

И погледам http://localhost:9393/ и видим имена фајлова.

Вратим измену у setup.yaml и додам рад у гит:

$ git status
# On branch posts
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   blurgh.rb
#   modified:   spec/blurgh_spec.rb
#   modified:   spec/config_spec.rb
#   modified:   views/index.erb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   .bundle/
no changes added to commit (use "git add" and/or "git commit -a")
$ git add blurgh.rb spec/ views/
$ git status
# On branch posts
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   blurgh.rb
#   modified:   spec/blurgh_spec.rb
#   modified:   spec/config_spec.rb
#   modified:   views/index.erb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   .bundle/
$ git commit -m "приказује листу чланака на index.html"
[posts 21d9938] приказује листу чланака на index.html
 4 files changed, 45 insertions(+), 13 deletions(-)

Једна напомена овде, кад сам урадио git status приказана су четири фајла. Али ја сам уписао само три аргумента кад сам радио git add:

$ git add blurgh.rb spec/ views/

То је зато што када се гиту дају аргументи са завршном /, он учита фајлове из тог директоријума.

У следећој епизоди: претварање фајлова у линкове и можда још нешто…