torstai 30. lokakuuta 2014

Edistyminen haittaa edistymistä

Yritän saada mukaan jonkinlaista mittaria edistymiselle ja tavoitetta kohti etenemiselle. Mittari olkoon numero, ajatellaan sitä vaikka prosenttina. Kun tavoite luodaan, numeron pitäisi alkaa automaattisesti nollasta ja käyttäjä saisi päivittää sitä.

Ensimmäinen haasteeni oli saada tavoitteenluontilomakkeeseen piilokenttä välittämään nolla controllerille. Lisäsin lomakkeeseen seuraavan rivin:

<%= hidden_field_tag 'progress', 0 %>

Näin sain aikaan html-koodin

<input id="progress" name="progress" type="hidden" value="0" />

kuten pitikin. Sitten tulikin tenkkapoo. Lomakkeeni kyllä lähetti hienosti progress-muuttujan, mutta controller ei tallentanut sitä tietokantaan.

Started POST "/projects/5/goals" for 127.0.0.1 at 2014-10-30 12:56:05 +0200
Processing by GoalsController#create as HTML
  Parameters: {"utf8"=>"V", "authenticity_token"=>"JFtUcbAFJiAs8y08trBS/y/KqmrlIOsfrkppFAqwabE=", "goal"=>{"title"=>"Tavoite1", "description"=>"Kuvaus1", "celebration"=>"Juhlistus1"}, "progress"=>"0",
 "commit"=>"Luo tavoite", "project_id"=>"5"}
DEBUG params inspect {"utf8"=>"V", "authenticity_token"=>"JFtUcbAFJiAs8y08trBS/y/KqmrlIOsfrkppFAqwabE=", "goal"=>{"title"=>"Tavoite1", "description"=>"Kuvaus1", "celebration"=>"Juhlistus1"}, "progress"=>"0", "commit"=>"Luo tavoite", "action"=>"create", "controller"=>"goals", "project_id"=>"5"}
  ←[1m←[36mProject Load (0.0ms)←[0m  ←[1mSELECT  "projects".* FROM "projects"  WHERE "projects"."id" = ? LIMIT 1←[0m  [["id", 5]]
  ←[1m←[35m (0.0ms)←[0m  begin transaction
  ←[1m←[36mSQL (1.0ms)←[0m  ←[1mINSERT INTO "goals" ("celebration", "created_at", "description", "project_id", "title", "updated_at") VALUES (?, ?, ?, ?, ?, ?)←[0m  [["celebration", "Juhlistus1"], ["created_at", "2014-10-30 10:56:05.533493"], ["description", "Kuvaus1"], ["project_id", 5], ["title", "Tavoite1"], ["updated_at", "2014-10-30 10:56:05.533493"]]
  ←[1m←[35m (108.8ms)←[0m  commit transaction
  ←[1m←[36m (0.0ms)←[0m  ←[1mbegin transaction←[0m
  ←[1m←[35m (0.0ms)←[0m  commit transaction
  ←[1m←[36mProject Load (1.0ms)←[0m  ←[1mSELECT  "projects".* FROM "projects"  WHERE "projects"."id" = ? LIMIT 1←[0m  [["id", 5]]
Redirected to http://localhost:3000/projects/5
Completed 302 Found in 118ms (ActiveRecord: 110.8ms)


Tarkistin sen vielä konsolin kautta hakemalla kaikki tavoitteet tietokannasta.

irb(main):002:0> Goal.all
<snip>
 #<Goal id: 13, title: "Tavoite1", description: "Kuvaus1", project_id: 5, created_at: "2014-10-30 10:56:05", updated_at: "2014-10-30 10:56:05", celebration: "Juhlistus1", progress: nil>]>

Ensin virhe oli ollut siinä, että en ollut sallinut progress-muuttujan tallentamista controllerin turvaosuudessa:

    private
        def goal_params
            params.require(:goal).permit(:title, :description, :celebration, :progress)
        end


Mutta syytä oli vielä jossain muuallakin. Mikä kummempaa, edistysnumeron päivitys onnistui olemassaolevaan tavoitteeseen mutta ei sen asettaminen nollaksi tavoitetta luodessa.

Googlailun jälkeen näytti siltä että en voi käyttää hidden_field_tagia, koska se ei ole jotenkin yhteydessä modeliin eli tietokantaan. Siitä seurasikin seuraava tenkkapoo. Mikä on hidden_fieldin oikea formaatti? Katsokaapa nimittäin tätä keskustelua: http://stackoverflow.com/questions/6636875/rails-hidden-field-undefined-method-merge-error

<%= f.hidden_field :service, "test" %>

<%= f.hidden_field :service, :value => "test" %>

<%= f.hidden_field :service %>

<%= f.hidden_field :service, value: "test" %>

Mitä näistä kuuluu käyttää? Dokumentaatiosta ei ole kovasti apua:

Examples

hidden_field(:signup, :pass_confirm)


<input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />

Mikä ihme tuo härpäke tuossa value-kohdassa on? Jos haluan vain arvoksi vaikka 0, miten saan sen siihen?

Ei auta kuin kokeilla.

<%= f.hidden_field :progress, '0' %>

undefined method `merge' for "0":String

<%= f.hidden_field :progress, :value =>'0' %>

<input id="goal_progress" name="goal[progress]" type="hidden" value="0" />

Jes! Nyt näyttää hyvältä!

keskiviikko 15. lokakuuta 2014

Sekavat näkymät. Errorista eteenpäin

Mainitsin tuossa aiemmin jo controllerit ja modelit, niin kirjoitanpa vielä lisää niistä vieweistä. Näkymän tarkoitus on tosiaan vain tuoda data controllerista ihmisen ihasteltavaksi. Kuten get started -oppaassakin tehtiin, loin itsekin näkymiä sitä mukaa kun selaimelle tuli virhe puuttuvasta näkymästä. Se olikin lempivirheeni, koska tiesin heti mitä sille piti tehdä.

Controllerissa pitää olla kullekin käytetylle toiminnalle oma def-end, esim.

    def show
        @temptask = Temptask.find(params[:id])
        @projects = Project.all
    end    


Sitten niillä pitää olla oma näkymänsä, eli app\views-hakemistossa kaikille controllereille on omat hakemistot ja niiden sisällä esimerkiksi \app\views\temptasks\show.html.erb

Näkymät kirjoitetaan template-kielellä, jossa html-koodin sekaan voi laittaa Railsin tulkkaamia pätkiä.

Esimerkiksi näin

<H1>Olet saanut aikaan tämän!</H1>

<p><%= @temptask.text %> (temptask id <%= @temptask.id %>)</p>


Tuossa sivun H1-otsikko on aina sama, mutta temptaskista riippuen seuraavaan kappaleeseen tulee eri teksti eli annetulla id:llä modelin kautta haetun temptaskin tekstikenttä. Id-kohdan olen laittanut vähän debuggaussyistä näkyviin.

Eniten tuskaa on aiheuttanut se mikä viewn mielestä on tunnettu muuttujan arvo ja mikä ei ja miten sen saa sieltä ulos.

Nämä asiat olen oppinut: 1) View tietää ne arvot, jotka controller on sille hakenut tai laskenut tai muuten muodostanut. Jos jokin ei näy, katse controlleriin. 2) Viewn tietämät arvot löytyvät paramsista. Olen käyttänyt sitä esim. näin:

<p><%= link_to 'Muokkaa', edit_project_goal_path(params[:project_id],@goal.id) %></p>

Sen mitä params pitää sisällään saa näkyviin seuraavan kaltaisella komennolla:

logger.debug "DEBUG params inspect #{params.inspect}"

Logger.debug siis printtaa viestejä serverin komentokehote-ikkunaan sinne kaikkien muiden printtien sekaan. Siksi laitoin selkeästi isoilla kirjaimilla tuon DEBUG rivin alkuun. Params:n varsinainen sisältö tulostuu #-merkistä alkavalla rimpsulla.

Joskus, itselleni vielä epäselvästä syystä, params:ssä on se haluttu arvo hashin sisällä. Eli jos normaalisti params:ssa on jutska1 => arvo1, jutska2 => arvo2, niin joskus se onkin jutska1 => {zydeemi1 => arvo3}. Tämän arvon saa ulos laittamalla nuo jutskat ja zydeemit peräkkäin omissa hakasuluissaan. Eli näin:

goal_id = params[:goal][:id]


Reitit solmussa

Sitten yhteen hankalimmista mutta tärkeimmistä asioista: routes.rb tiedostoon ja reitteihin ylipäänsä.

Ensimmäinen muutos, jonka routes.rb tiedostoon tein, oli

   root 'welcome#index'

Tämä tekee sen, että kun joku tulee nettisivun "juureen", hänet ohjataan welcome-controlleriin ja siellä index-actioniin.

Railsissa on eräänlaisia controllerien vakiotoimintoja. Toimintoja voi ja varmasti myöhemmin pitääkin tehdä myös itse, mutta vakiotoiminnot ovat helppokäyttöisiä ja hoitelevat paljon automaattisesti.

Toiminnot eli actionit ovat index, new, create, show, edit, update ja destroy.

Index on se mitä tehdään, kun käyttäjä haluaa nähdä kaikki tietyn tyyppiset jutskat, vaikka kaikki projektit.

New: luodaan uusi jutska mutta ei vielä tallenneta sitä mihinkään. Tätä tarvitaan vaikkapa siihen, kun näytetään sivu "Luo uusi projekti".

Create: luodaan uusi jutska ja tallennetaan se tietokantaan.

Show: näytetään yksi tietty jutska.

Edit: haetaan tietyn jutskan tiedot selaimelle muokkausta varten.

Update: tallennetaan muutokset tietokantaan.

Destroy: tuhotaan haluttu jutska.

Railsissa oikeaan toimintoon pääsee oikealla urlilla eli sillä mitä selaimen osoiterivillä lukee. Rails tulkitsee siitä mitä käyttäjä tai ohjelmoija haluaa. Nämä urlit saa tietoonsa kirjoittamalla komentoriville rake routes.

Siitä sujahtaa näkyville seuraavanlainen Railsin maagisesti koostama käsittämätön töräys:


Valitettavasti kyseessä on erittäin tärkeä lista, joka ymmärtäminen on ihan keskeistä.

Melko pitkään piti tukkaa nyhtää, mutta nyt tajuan mistä tuossa on kysymys. Otetaan yksi rivi esimerkiksi, vaikka se kaikkein pisin.

edit_project_goal_task GET    /projects/:project_id/goals/:goal_id/tasks/:id/edit(.:format) tasks#edit

edit_project_goal_task on se Railsin apukomento, jota käytetään, kun halutaan päästä task-controllerin edit-toimintoon.

GET on se HTTP-verbi, jota käytetään. En tästä sen enempää vielä tiedä, mutta verbillä on merkitystä,  kun sama url voikin viedä tapauksesta riippuen eri toimintoihin.

Sitten se url-osuus. Kun tuota edit_project_goal_task -apulaista kutsutaan, sen pitää tietää mikä projekti ja mikä tavoite ja mikä tehtävä, että se tietäisi luoda oikean urlin. Sille pitää jotakin kautta antaa ne puuttuvat tiedot eli :project_id, :goal_id ja :id. (Enimmät taisteluni Railsin kanssa olen käynyt juuri noista id:istä.)

Viimeinen sarake kertoo, mistä controllerista ja mistä toiminnosta on kysymys.

Puuhastelua mallien kanssa

Loin myös vastaavat modelit. Halusin niiden riippuvan toisistaan, joten tein taskiin goal_id-kentän ja goaliin project_id-kentän. Niin voisin löytää kuhunkin tavoitteeseen liittyvät tehtävät ja kuhunkin projektiin liittyvät tavoitteet. Loin mallit komentoriviltä, ja minun piti tehdä se muutamaan kertaan.

Lisäsin myös app/model-hakemistossa oleviin tiedostoihin tiedon siitä, millaisissa suhteissa ne ovat toisiinsa. Esim. app\models\task.rb tiedostoni näytti tältä:

class Task < ActiveRecord::Base
    belongs_to :goal
end


ja app\models\project.rb

class Project < ActiveRecord::Base
    has_many :goals
end


Näillä saa ohjeiden mukaan helpotettua esimerkiksi poistoa, kun ohjelma tietää mikä liittyy mihinkin. Onhan se itsellekin selkeyttävää, kun nämä laittaa paikoilleen.

Tietokantataulujen muoto generoituu tiedostoon db\schema.rb. Sieltä näkee minkä nimisiä sarakkeita sitä onkaan tullut luotua.

Taulujen luonnit ja muutokset tehdään migraatioilla. Antaisin ennemminkin nimeksi migreeni. Tämä osa-alue on jäänyt vielä melko hämäräksi. Idea on käsittääkseni se, että kukin muodon muutos tehdään ajamalla migraatiotiedosto, jonka voi myös perua. Menin itse niin solmuun noiden tiedostojen kanssa varsinkin kun menin käpälöimään käsin jotain mitä ilmeisesti ei olisi saanut muuttaa. Tiedostojen nimessä on aikaleima, jonka perusteella migraatiot ajetaan järjestyksessä.

Joka tapauksessa tietokannan hakiessa muotoaan minulle tuli tutuksi komentosarja

rake db:drop db:create db:migrate

joka tyhjentää tietokannat, luo ne uudelleen ja ajaa migraatiot. Joskus vielä ymmärrän tämänkin paremmin ja voin kirjoittaa siitä enemmän.

Valinnan paikka. Mitä ohjelmani tekee

Halusin siis tehdä oman ohjelman. Pieni ongelma oli ensin ratkaistavana - minun piti ensin keksiä mitä ohjelma tekisi.

Pienen pohdinnan jälkeen keksin loistavan idean, jossa pääsisin hyvin alkuun edellämainitulla Get Started -ohjeella. Tekisin ohjelman, jossa tietokantaan voisi lisätä projekteja, joiden alle voisi lisätä tavoitteita, joiden alle voisi lisätä tehtäviä. Tehtävät syötettäisiin irrallisina, ja ne liitettäisiin johonkin olemassaolevaan tavoitteeseen.

Siinäpä tavoitetta! Sain kuitenkin vähän vihiä siitä, millaisia controllereita tarvitsisin. Ohjeiden mukaan loin controllerit Project, Goal ja Task sekä Temptask. (Viimeksimainitun kanssa minun piti muuttaa nimeä, koska temp_task sekä TempTask aiheuttivat minulle ongelmia Railsin omatoimisen tiedoston- ja muun tauhkan luomisen kanssa.) Controllerit tallentuvat app/controllers -hakemistoon.

Rohkaistuminen. Koodin kopiointi on tylsää

Nyt siis oli edessä ensimmäisen applikaation tekeminen. Rails on siitä hyvä systeemi, että se tekee ohjelmoijan puolesta paljon rutiinihommia ja rohkaisee hyvään ohjelmointityyliin (tai näi ainakin mainoslause sanoo). Joka tapauksessa se luo paljon hakemistoja ja tiedostoja, joiden käyttötarkoituksesta ei ole alussa hajuakaan.

Näin kävi myös kun lähdin seuraamaan http://guides.rubyonrails.org/getting_started.html ohjeita ja loin ensimmäisen controllerini. En vielä siinä vaiheessa tiennyt yhtään, mikä controller on.

Mieheni, joka on ohjelmoinut jonkin verran Djangolla, joka on samantapainen alusta kuin Rails, selitti minulle miten asia menee, ja alkoihan se jostain muustakin yhteydestä muistua mieleen.

Applikaatiossa on siis kolme kerrosta: view, controller ja model. (Ja sitten se varsinainen tietokanta pohjalla). Ne ovat toistensa kanssa tekemisissä sillä tavalla, että view näyttää nätisti tiedon, jonka controller on jollain logiikalla kursinut kokoon ja hakenut modelin kautta tietokannasta. Model on tietokannan helppokäyttöinen ohjaamo. Minulla ei ole aavistustakaan mitä nämä termit ovat suomeksi, mutta ehkä jossain vaiheessa sen opin.

Joka tapauksessa se ohjelman varsinainen äly on controllerissa. Käyttäjä pyytää ohjelmaa tekemään jotain, ja pyyntö menee selaimen osoiterivin kautta controlleriin, joka tekee jotain ja palauttaa käyttäjälle tuloksen.

Kyllästyin ohjeiden seuraamiseen ja koodin kopioimiseen varsin äkkiä ja halusin tehdä ihan oman ohjelman.

Liikkeelle mars. Ympäristön asentaminen

Aivan ensin asensin tietokoneelleni Ruby on Rails -kehitysympäristön.

Seurasin RailsGirls.com-sivuston ohjeita Windowsille:
http://guides.railsgirls.com/install/#setup-for-windows

Latasin ja asensin ensin RailsInstallerin, sitten päivitin sen annetulla komennolla ja serveri lähti kiltisti käyntiin. Ei mitään ongelmia tässä vaiheessa. Tekstieditorina minulla oli jo Notepad++, joten en asentanut uutta.

En ihan ensin saanut serverin tuottamaa nettisivua auki, koska kikkailin jonkun ohjeen antamilla kummallisilla IP-osoitteilla. http://localhost:3000 on se, jota täytyy käyttää.

Käytin asentaessa default-asetuksia ja ohjelma meni C:/Sites hakemistoon. Eri projektit menevät sitten Sites-hakemiston sisälle.

Rubiiniturbiini pyörähtää käyntiin

Sain tässä yhtenä päivänä idean elvyttää vanha Ruby on Rails -opiskeluharrastukseni. Kokeilin joskus muinoin tehdä RailsGirls-sivuilta jonkin pienen applikaation ohjeiden mukaan, mutta kun yritin vähän laajentaa sitä, en tajunnutkaan enää yhtään mitään. Into latistui ja homma jäi.

Nyt olen uusin voimin asialla. Kirjaan opiskellessani oppimani asiat tähän blogiin, niin niistä voi olla muillekin hyötyä. Tässä jo ensimmäisten askelieni aikana olen nimittäin ehtinyt huomata, että RoR-dokumentaatio ei ole ihan parasta ja selkeintä laatua. Ehkä joku toinenkin tarvitsee keskimääräistä enemmän rautalankaa.

Minulla on käsitys siitä mihin ohjelmani on menossa, mutta en halua paljastaa liikaa. Voihan olla että siitä tulee kamalan hyvä ja saan siitä paljon rahaa. Hah hah! Jos nyt ainakin omaksuisin sen avulla tärkeimpiä Ruby on Rails -asioita.