Уеб сайт с Ruby и Sinatra

Базирано на това ръководство от Piotr Szotkowski (@chastell). Код на ръководството в GitHub.

Съдържание

  1. Какво е това ръководство?
  2. Запознаване с инструментитетекстови документи и редактори
  3. Що е то “конзола”?основни понятия, съвети и упражнения
  4. Основи на HTML и CSS
  5. Блиц-увод в програмиранетоданни и код, променливи, действия (методи/функции), условия, цикли и изрази
  6. Представяне на идеята за приложениетокакво е интернет, какво е уеб приложение, защо Sinatra, инсталиране на Sinatra и документация
  7. Първо работещо приложение! и стартиране на приложението
  8. Добавяне на начална страница с формуляр за гласуване и преместване на HTML-а в отделен файл (view)
  9. Добавяне на страница след успешно гласуване
  10. Показване на резултати
  11. Реално отчитане на гласовете
  12. Опционални задачидобавяне на layout, качване на приложението на Heroku, трайно съхранение на гласовете и други

Какво е това ръководство?

Това ръководство представлява кратък увод в основите на създаването на уеб приложения. Предназначено е за напълно начинаещи или хора с минимален опит. В ръководството се минава през няколко стъпки, като крайният резултат е едно малко уеб приложение за гласуване. Засягат се и се използват технологиите HTML, CSS, Ruby, Sinatra. Работи се в конзола (shell).

Предполага се, че ръководството се прави с помощта на инструктор, който да дообяснява нещата, които не са покрити в текста, да отговаря на въпросите на четящия и да го напътства. Това, разбира се, не пречи да бъде направено и изцяло самостоятелно.

Ръководството по-долу прави предположението, че вече имате работеща среда, в която да изпълнявате примерите. Най-удобно за тази цел е да си направите регистрация в Github и да си създадете нов Codespace, използвайки Blank Template.

Запознаване с инструментите

Преди да започнем да правим каквото и да е, трябва да се запознаем с инструментите, които ще използваме. В нашия случай, това са обикновен текстов редактор (например Notepad) и интегрираната онлайн среда за разработка Github Codespaces.

Всеки майстор има нужда от определен набор инструменти и си има собствена работилница, в която твори. Вместо да ви караме да отделяте време, за да се снабдите с всички необходими инструменти и да си обзаведете собствена работилница специално за това събитие, ние ще използваме една готова такава – услугата Github Codespaces.

Инструктор:

Това е добър момент да влезете с участничките си в техния профил в Github и да създадете нов Codespace, използвайки Blank Template, ако все още липсва такъв. Ползват се основно 3 неща от интерфейса – дървото с файлове в левия панел, текстовия редактор в центъра и Bash конзолата, която е в долния панел, под текстовия редактор. Средите “заспиват” автоматично след 30 минути неактивност. При повторно отваряне, с помощта на бутон се “събуждат”.

Обяснете основните компоненти на интерфейса в Codespaces, без да навлизате в твърде много детайли – къде стоят файловете, къде е този сървър (компютър в “облака”). Обяснете какво всъщност замества тази услуга – текстов редактор, конзола, файлова система, стартиране на процеси. Посочете коя част от интерфейса на кой инструмент отговаря. По-долу ще се разгледат основните инструменти в повече детайли.

Текстови документи и текстови редактори

Програмистите обикновено пишат код в текстови файлове, тъй като “кодът” е най-обикновен текст. Текстовите файлове са просто файлове с чист текст вътре – без никакво специално форматиране. Интересен факт е, че Word документите не са текстови файлове – дори да не сте приложили никакво форматиране на текста. Самият Word файл съдържа специални и нечетими символи вътре, които се ползват като “служебна” информация, когато в Word документа има приложено форматиране. За илюстрация, може да опитате да отворите Word документ с Notepad – ще видите нечетимите символи, които са служебна информация за Word и които той не ви показва.

Името на текстовите документи често завършва на .txt, но е напълно възможно това окончание (наричано още файлово разширение) да бъде друго, например .html, .rb, или пък да няма разширение изобщо – например README. Името на файла няма директно отношение към съдържанието на файла. Един файл може да се казва photo.jpg и вътре да има обикновен текст. Разбира се, ако опитате да отворите файла с програма за обработка на изображения, ще получите грешка, но пък ако го отворите с Notepad, ще видите текстовото му съдържание.

За редакция на файловете с код в това ръководство ще използваме т.нар. “текстови редактори”. Това са програми, които са предназначени за редакция на обикновен текст. Notepad е такава програма в Windows. В онлайн средата Github Codespaces също има вграден такъв текстов редактор. Някои текстови редактори предлагат допълнителни помощни функции като автоматично оцветяване на различните специални команди според типа код вътре, но по същество са си най-обикновени текстови редактори.

Конзола (терминал, Command Prompt)

Нещо, което ще използвате в средата си за разработка, е един вариант на така наречената “конзола” (console). Конзолата обикновено представлява един черен екран с бели или зелени букви текст в нея и нищо друго:

Command Prompt в Windows XP

Като синоними на конзола ще срещате още терминал (terminal), команден ред (command line или Command Prompt) или Shell. Например, Command Prompt е името на конзолата в Windows. Всеки Windows си има вградена такава. Може да стартирате Command Prompt в Windows като натиснете Win + R и в отворилия се прозорец “Run” напишете cmd и натиснете Enter.

В конзолата се пишат команди като текст и резулатът се показва като текст. Реално, конзолата е софтуер, с който може да управлявате компютъра си и “конзола” е нещо като събирателно понятие за този тип софтуер за взаимодействие с компютър.

Интересен факт за конзолите е, че първият интерфейс към компютрите е бил такъв – изцяло текстов, със зелени или бели букви на черен екран (уверете се сами). Логичен въпрос тук е “Защо ме занимават с толкова стар начин за взаимодействие с компютър?” А може би е интересно да се запитаме и защо все още има такива неща като конзола в съвременните компютри?

В интерес на истината, програмистите често използват някаква разновидност на конзола в ежедневната си работа. Причината за това е много проста – конзолата е бърз и ефективен начин да свършим нещо. Защо?

Конзолата има много прост принцип на действие – има поле, в което пишем команди под формата на текст, натискаме Enter, командата се изпълнява и виждаме резултата от изпълнението ѝ също като текст. Ако сме написали невалидна (несъществуваща) команда, или сме объркали нещо друго, ще ни се изпише съобщение за грешка. След изпълнението на командата, отново ни се показва познатото поле за писане на команда.

Някои възможности на конзолата:

И много други. Това са някои от причините да виждате и днес програмисти и системни администратори масово да пишат текст в тяхната разновидност на конзола или терминал. В днешно време можем да си нагласяме цвета на фона (например бял, полупрозрачен или изображение), цвета, размера и шрифта на текста и дори някои команди ни показват резултат в цветен текст, но принципът на действие си е същият. Вижте пример за по-шарена конзола.

Хайде да запретнем ръкави и да станем бързо майстори на конзолата!

Основни понятия за конзолите

Практически съвети за конзолите

Примери и упражнения

Windows: Създаване на папка и файл през конзолата, показване съдържанието на файла в конзолата и отваряне на файла с текстов редактор:

dir
mkdir ConsoleTest
cd ConsoleTest
dir
echo "Hello world!" > hello.txt
dir
type hello.txt
notepad hello.txt
cd ..

Други полезни команди и вариации на такива в Windows (помолете инструктора си за обяснение какво прави всяка една от тях):

D:
cd "C:\Documents and Settings\Radostina"
cd ..
cd ..\..
explorer .
explorer http://facebook.com
echo Current folder: %cd%
echo Hello world!
dir
type path\to\file.txt

Mac OS X и Linux: Създаване на папка и файл през конзолата, показване съдържанието на файла в конзолата и отваряне на файла с текстов редактор (в Linux отварянето на файл с текстов редактор трябва да стане по различен начин, например с gedit hello.txt):

ls
mkdir ConsoleTest
cd ConsoleTest
ls
echo "Hello world!" > hello.txt
ls
cat hello.txt
open hello.txt
cd ..

Други полезни и често срещани команди под Mac OS X и Linux (помолете инструктора си за обяснение какво прави всяка една от тях):

pwd
cd /home/radostina
cd ~
cd ..
cd ../..
touch hello.txt
ls -al

Честито! Вече сте майстори на конзолата! За награда, може да се насладите на този комикс.

Инструктор:

Упражнете командите по-горе и техни вариации, като направите това в Command Prompt и/или в Shell-а в Github Codespaces и разяснете нещата, които не са ясни.

In HTML we trust - статичен сайт

HTML е основният градивен блок на сайтовете. Преди да се занимаем с това приложение, ще покрием основни понятия в HTML и CSS с помощта на вашия инструктор.

В това упражнение ще създадете една малка страничка, която илюстрира основни принципи в HTML. Може да видите примерния очакван резултат тук. Знаете ли, че можете да разгледате HTML и CSS кода на всеки сайт? Питайте вашия инструктор как.

Алтернативно, ако ви е по-интересно, може да създадете и една семпла страничка, която да играе ролята на ваша визитка. Пример за такава може да видите тук.

Незадължително, ако ви остане време тук, или в края на събитието. Направете си безплатен профил в GitHub и използвайте това ръководство, за да публикувате току-що създадената страничка онлайн, на безплатния хостинг на GitHub за статични страници. Ако потребителското ви име е radostina, то сайтът ви ще се намира на адрес https://radostina.github.io.

Инструктор:

Примерите по-долу могат да се тестват лесно в Github Codespaces, като вляво, от таба Extensions се инсталира Preview. След това вляво се създаде и отвори за редакция един something.html файл там и с десен бутон се избере “Open Preview”. Това preview ще се отвори в панел в Codespaces. За да се отразят промените, които се направили по файла, изберете отново “Open Preview”.

Друг вариант да показвате примерите по-долу, е с Notepad или Notepad2 на лаптопите на участничките, без да се занимавате с Github Codespaces. За целта, може да изтеглите Notepad2 (директен линк, x86), за да е цветен HTML-ът, който ще показвате тук. Notepad2 е добър вариант за Windows, тъй като е много лек и прост, прилича на вградения Notepad и няма излишни неща в интерфейса.

Забележка: На Windows е добре да изключите опцията, която крие разширенията на файловете - инструкции тук.

Примерен план за обяснения:

  1. HTML са командите, с които караме браузърите да рисуват неща по екрана. Суха дефиниция. След малко ще я илюстрираме. Тук е добър момент да се каже каква е ролята на HTML в интернет, защо показваме HTML и защо започваме с него – HTML е технология (не точно език за програмиране), която е градивното блокче на интернет и на която стъпва визуализацията на всички сайтове - това е, което се точи по жицата до нашия компютър.
  2. HTML е просто текст. Всеки текст е и HTML. Започва се с обикновен текстов файл, с разширение .html. Може да ползвате Notepad, или Notepad2, инсталиран в предишната стъпка, като направите уточнението, че и с Notepad става, просто с Notepad2 е цветно. Плюсът на Notepad е, че може да се демонстрира как само с вградените в операционната система инструменти, човек може да твори HTML. Бонус е когато впоследствие HTML файлът бъде отворен с по-умен редактор и когато се покаже оцветяването и нещата, които редакторът дава. Във файла участничките пишат “Къде да обядваме?” на първия ред, оставят един празен ред и допълват с няколко имена на заведение, като всяко е на отделен ред. Ако правят визитка, вместо това може да си напишат името на първия ред, оставят празен ред и да напишат едно-две изречения за себе си, пак на отделни редове. Файлът се отваря с браузър. Всичко се вижда, но е на един ред. Изводи - какво значи whitespace-а за HTML (а и какво е “whitespace”).
  3. HTML таговете са начални и крайни маркери. Задават структура. Имат сурова визия по подразбиране. Нека оградят първия ред с <p> таг. Може да го напишете на хартия, за да видят отварящия и затварящия таг и с какви скоби са. Може да им отнеме малко време да намерят символите на клавиатурата. Добре е те да си напишат тага. Запазете файла и презаредете браузъра. Обсъдете резултата - името е с големи букви и е на отделен ред. Вкарайте <h1> след това. Споменете за различните видове h1, h2, …
  4. Може да покажете таговете за линк, картинка и нов ред. Обърнете внимание, че таговете <img> и <br> са едни от малкото, които нямат съответен затварящ им таг. Като покажете нов таг, дайте мнемоника, с която да го запомнят (img = image, br = (line) break, p = paragraph, h* = heading, a = anchor, ul = unordered list, li = list item и т.н.)
  5. Браузърите са толерантни към невалиден HTML – това, което сме създали, формално погледнато, е само парченце от цялостен HTML документ и е невалиден такъв документ, според формалната дефиниция. Но работи. Кажете какво друго има обикновено в един валиден HTML документ – <html>, <head>, <body>.
  6. Обяснете защо има нужда от <body> и <head> – в body слагаме видимото съдържание, а в <head> неща, които са по-скоро допълнителна информация за документа – например, инструкции към търсещи машини като Google, допълнителни инструкции за браузърите и други. Може да дадете пример и с тага <title>, който реално задава заглавието на таба в браузъра.
  7. CSS е за визия, оформление, шарено – HTML е създаден 1993 г., а CSS - 1996 г. Първоначално е имало само HTML. Добавете следния ред код в примерния HTML, някъде в <head>...</head> секцията, за да приложите примерно стилизиране (CSS-ът е направен за примерния HTML по-долу):

     <link rel="stylesheet" type="text/css" href="voter.css">
    

    Това ще демонстрира как се променя един и същ HTML след прилагане на стилове. Стиловете може да се видят тук. Определено покажете CSS Zen Garden - идеята на този сайт е, че HTML-ът е един и същ, но има различни CSS стилове, които коренно променят визията на сайта. Има линкове в самия сайт към алтернативните визии.

    Друг много полезен и интересен пример, който може да се покаже, е този сайт, който прогресивно добавя стилизиране над собственото си съдържание.

    Финално, cssicon.space демонстрира как могат да се създават доста богати визуални елементи с помощта на CSS и минимален HTML.

  8. При наличие на време и желание, може да покажете как изглежда един CSS документ (например показанията в предната стъпка voter.css) и да обясните двете основни неща вътре: селектори (selectors) и правила (properties). Правилата задават конкретен визуален стил – цвят, шрифт, фон, размери и прочее. Селекторите пък указват за кои парчета от HTML документа да важи този конкретен визуален стил.

Примерна структура на HTML документ (този пример го има и тук без CSS, тук със CSS, а страничката-визитка тук):

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Машина за гласуване</title>
    </head>
    <body>
        <h1>Добре дошли в машината за гласуване!</h1>
        <p>Къде да обядваме?</p>

        <ul>
            <li>Happy</li>
            <li>Кривото</li>
            <li>Мистър Пица</li>
            <li>Слънце луна</li>
        </ul>

        <form>
            <p>
                Вашият избор:
                <input type="text" name="vote">
                <button type="submit">Гласувай!</button>
            </p>
        </form>

        <p>Или <a href="https://foursquare.com/">потърсете още места</a>.</p>

        <p>
            Добър апетит!
        </p>

        <a href="http://www.nipponham.co.jp/files/topics/18678_ext_06_0.jpg" target="_blank" title="Bento Lunch">
            <img src="http://www.nipponham.co.jp/files/topics/18678_ext_06_0.jpg" width="300" alt="A photo of a Bento lunch">
        </a>
    </body>
</html>

Основи на програмирането с Ruby

Инструктор:

Презентация, покриваща основните концепции от тази секция, може да бъде видяна тук. Не е нужно да минавате през нея с участничките.

Ще изпробваме някои неща с Ruby, като използваме инструмента irb (съкратено от “Interactive Ruby”).

irb е името на програма, която идва инсталирана заедно с Ruby. Тя е вид конзола, наречена REPL (read-eval-print loop). Може да я стартирате директно от конзолата (Shell) в Codespaces. irb наподобява конзола (command line) – пишете Ruby изрази, натискате Enter и irb ги оценява веднага и ви показва резултата (или грешката, ако има такава). Обърнете внимание, че irb е различен тип конзола от стандартната ви – командите, които irb разбира, не са за движение по папките ви, за стартиране на команди или за отваряне и манипулация на файлове – irb разбира само езика за програмиране Ruby.

За да стартирате irb, напишете следната команда в конзолата на Codespaces и натиснете Enter:

irb

Обърнете внимание, че prompt-ът (символите отляво на курсора) ще се сменят с такива от irb. За да ви е по-удобно, може да увеличите размера на панела с конзолата.

От irb се излиза с Ctrl + D или като напишете exit и натиснете Enter.

Инструктор:

Припомнете накратко какво е конзола (синоними: терминал, command line, shell) и разяснете разликата между конзолата и irb, тъй като двете си приличат и това може да е много объркващо в началото.

Внимание: ако в irb има синтактична грешка от типа на незатворена кавичка или скоба и се натисне Enter, irb няма да изпълни израза и да покаже грешка, а ще чака да “завършите” израза, т.е. да затворите кавичката или скобата. Prompt-ът на irb също ще се промени на символа, който се очаква в момента. Ето пример:

$ irb
irb(main):001:0> puts "Hello, world!
irb(main):002:0"
irb(main):003:0"

В такива случаи, може да се натисне Ctrl + D, Ctrl + C, или просто да се въведе липсващият символ.

Данни и код

Програмите боравят с данни. Тези данни трябва да се вземат отнякъде. Или ги зареждате по някакъв начин от външния свят (прочитате от файл, сваляте от интернет, потребител ги попълва), или вие като програмист ги слагате директно в кода.

Данните биват различни типове:

Причината за разграничаването на типовете на данните е, че с всеки тип данни могат да се правят различни операции – например, числата могат да се умножават, текстът – не.

Инструктор:

Упражнете основните типове данни и някои операции с тях в irb. Съставете списък, намерете му дължината, обходете елементите и ги отпечатайте на екрана. Обърнете внимание на разделението между данни и код.

Имена (променливи)

Ако определени данни ни трябват на повече от едно място в програмата (което е по-честият случай), не е практично да повтаряме вмъкването им всеки път.

Затова в Ruby (и другите езици за програмиране) имаме възможност да дадем име на определени данни. Това име се нарича “променлива” и как се казва променливата си избираме ние. Добре е името да описва ясно смисъла и целта на данните, към които сочи. Насочваме името към данни, като използваме конструкцията име = данни. Например:

participant_name = "Весела Радостинова"

Името participant_name става налично веднага след този ред код (но не и преди това). След това, правим манипулации над самото име, като реално ще работим с данните, към които името сочи. Например:

puts participant_name

Тук puts е вградена в Ruby ключова дума, която извежда на екрана подадените ѝ данни.

Обърнете внимание, че имената в Ruby са просто имена. Може да насочите повече от едно име към едни и същи данни и да ги променяте от което и да е от имената. Можете и да сменяте накъде сочи дадено име. Например:

participant_name = "Весела Радостинова"
puts participant_name

participant_name = 42
puts participant_name

Друг интересен момент е, че данните от дясната страна на равенството може да се получат в следствие на динамично изчисление, което, логично, може да включва и други, вече дефинирани променливи:

instructors_count = 50
accepted_participants_count = instructors_count * 2

Инструктор:

Упражнете работа с имена и основни типове данни в irb. Покажете как работи повече от едно име към един и същи обект. Може да използвате метод, който променя състоянието на данните in-place, например String#upcase!. Променливите нямат тип и са просто имена/етикети, закачени към обекти.

Тук е добър момент да се обърне внимание на разликата между “извеждане на екрана” (например с puts) и показване на резултата от даден израз, което irb прави автоматично след всеки израз. Добре е да се спомене и че когато Ruby програмата се изпълнява от файл (ruby program.rb), а не в irb, оценките на изразите няма да са видими, а ще се виждат само неща, които са изрично показани на екрана с puts.

Действия

Както споменахме по-горе, всеки тип данни поддържа определен набор от действия върху него. Може да разглеждаме всеки тип данни в една Ruby програма като обект, над който можем да изпълняваме този набор действия. Общият начин за изпълнение на действие над определени данни е данни.име_на_действие(опции). Например:

"Радостина".length
6 * 7
Date.today

Това са все действия, дори да не виждате кръгли скоби навсякъде.Поставянето на кръгли скоби след името на действието не е задължително в Ruby.

“Каноничният” формат на записване на горния пример е такъв:

"Радостина".length()
6.*(7)
Date.today()

Вероятно така по-лесно може да разпознаете шаблона на извикване на действие. Изпускането на кръглите скоби на места прави израза по-лесно разбираем за четящия кода. Това е една от причините да е позволено в Ruby да ги пропуснете. При изпълнение на действия над числа е позволено да се изпусне и точката, защото е по-естествено да напишем 2 + 2, отколкото 2.+(2), въпреки, че първото реално се обръща вътрешно до второто.

Важно е да се отбележи, че след изпълнението на всяко действие има “резултат” под формата на нови данни. Тоест, всеки израз, който включва изпълнение на действие, си има резултат, който резултат представлява потенциално нови, променени данни. Тези нови данни, на свой ред, също си имат набор от действия, които може да изпълните над тях. Това е и причината да може да “навързвате” действия едно след друго:

"Hello World".sub("World", "Sofia").upcase()
"Hello Ruby".chars().first(5)

Или:

Date.today.to_s

Обърнете внимание, че горният пример е еквивалентен на Date.today().to_s().

Възможно е да наричаме данните в Ruby “обекти”. Двете ще използваме като синоними – всички данни в Ruby са обекти и всички данни (обекти) си имат определен набор от валидни действия.

Инструктор:

Упражнете работа с действия върху обекти в irb.

Забележка: За да ползвате Date.today, трябва да изпълните някъде require 'date'.

Условия

Условията в езиците за програмиране ни дават възможност да напишем “умни” програми, които могат да взимат решения за промяна в логиката си по време на работа на програмата.

Това е сложен начин да опитаме да обясним следния пример:

if условие
  изпълни това, ако условието е истина
else
  иначе изпълни тази част
end

Например, нека имаме този ред код:

your_age = gets.to_i

В Ruby, gets е ключова дума, която пита потребителя за вход. При изпълнение на кода по-горе, Ruby програмата ви ще паузира работата си и ще очаква да въведете данни. Като ги въведете, ще ви ги върне в прогамата като текст. Ако въведете число, това число ще дойде като текст. Например, ако въведете 42, в програмата си ще го получите като текста "42". В случая, искаме да вземем годините на потребителя като число, за да можем да използваме действия, присъщи на числа. Действието to_i ще извлече числото от текста и ще ни го даде в подходящия тип: "42".to_i ще ни върне 42.

След този ред код, можем да съставим условие, което ще промени поведението на програмата в зависимост от входа на потребителя:

if your_age < 13
  puts "What do you want to become when you grow up?"
else
  puts "Want to play some more?"
end

Условията имат и други форми. Например, else клонът не е задължителен. Освен това, имаме възможност да добавим и един или повече elsif клонове, които да позволяват разклонение на пътя на програмата на повече от две (забележете, че не е elseif, а е elsif).

Важно: Когато сравнявате в условия за равенство, го правите с два, а не с един символ за равенство: ==. Помнете, че един символ = се ползва за присвояване на стойност на променлива, а две равенства == – за сравнение. За сравнение за различие, се използва != (четете като “различно”, или “не-равно”).

Инструктор:

Упражнете работа с различни видове условия.

Повторение (цикли)

За какво щеше да ни е да програмираме, ако нямахме начин да накараме компютъра да прави това, в което е най-добър – да повтори едно действие милиарди пъти за част от секундата?

Във всеки език за програмиране има начини да накараме програмата да изпълни част от логиката си определен брой пъти. Това може да е фиксиран брой (например, 10), или неограничен брой пъти, до изпълнение на дадено условие (например, повтаряй това, докато потребителят не въведе текста "exit").

Повторенията се наричат още цикли.

В Ruby, както и в други езици за програмиране, има повече от един начин да се стартира един цикъл. Програмистът избира кой начин да използва според ситуацията.

Ето пример за цикъл, който ще се изпълни фиксиран брой пъти:

30.times do
  puts "Hello, Ruby! I am your master now!"
end

Обърнете внимание, че в този пример 30 е число (данни) и всъщност 30.times е изпълнение на действие върху тези данни. Това означава, че 30 може да се замени и с променлива и нещата пак ще работят.

Ето и пример за цикъл, който ще се повтаря неограничен брой пъти, до възникването на определено условие:

correct_password = "very-secret-password-that-no-one-else-knows"
password_entered = ""

while password_entered != correct_password
  if password_entered == ''
    puts "Please enter your password."
  else
    puts "You entered " + password_entered + " but that is not the correct password. Please try again."
  end

  password_entered = gets.strip
end

Чувствайте се свободни да смените стойността на correct_password в примера по-горе.

Инструктор:

Обърнете внимание на конкатенацията на низове с + и на комбинацията между if и while. Обходете кода стъпка по стъпка. Упражнете цикли, в комбинация с наученото до момента.

Изрази

Всяка Ruby програма, дори най-кратката, може да се разглежда като съвкупност от изрази.

Всеки израз е просто валидно парче Ruby код, което може да бъде изпълнено самостоятелно без това да доведе до грешка. След изпълнението на всеки валиден израз се получава някакава стойност. Това винаги е така. Стойността е просто някакви данни. Наричаме я оценка на израза.

Всички примери за Ruby код, които разгледахме до момента – всички до един – са валидни изрази. Дори if-else-end конструкциите. Може да проверите това, като ги paste-нете в irb и натиснете Enter. irb ще ги изпълни (оцени) и ако няма грешка, ще ви покаже оценката им (данните, които са резултат от изпълнението на израза).

Това е важен принцип, който трябва да имате предвид. Едно много важно и ценно следствие от него е следното твърдение:

Навсякъде, където работите с данни, можете да ги замените с произволно сложен и валиден Ruby израз.

Например, там, където имате данни.действие(други, данни), всъщност може да имате израз.действие(друг_израз, трети_израз). Това е много мощен инструмент, който ви дава възможност да комбинирате по-прости изрази с по-сложни и с който може да сте страшно гъвкави при писането на код.

Инструктор:

Упражнете наученото до момента – работа с речници, променливи, низове, изрази и комбинацията между тях. Отговорете на възникналите въпроси, но не влизайте в прекалено много детайли.

Динамичен сайт - за гласуване

След като сме се позабавлявали малко с HTML и CSS, е време да направим следващата важна стъпка – да напишем софтуер, който да създава HTML код вместо нас – и динамично.

Ще създадем малко приложение за гласуване.

Представете си, че планирате хапване с група приятели или колеги. Понякога изборът на място, от което максимално много хора да са доволни, е трудна задача. Програмистите обичаме да си създаваме инструменти, които да ни помагат с решаването на трудни задачи. Това ще е целта и на нашето приложение за гласуване.

Ще го напишем от нулата с помощта на помощна библиотека за правене на уеб сайтове с Ruby, която се казва Sinatra. Sinatra решава вместо нас набор от проблеми, които се срещат често при изработката на един уебсайт. Бихме могли и да не използваме Sinatra, но ще трябва да напишем доста повече Ruby код. Също така, Sinatra е просто един от възможните инструменти, които бихме могли да приложим. Има и други, разбира се (например библиотеката Ruby on Rails). Sinatra е сравнително компактна и семпла библиотека, която въпреки това има много възможности.

Един готов и работещ вариант на примерно приложение може да бъде изпробван тук. Кодът на това примерно приложение, в неговата цялост, може да бъде разгледан тук.

Но какво е всъщност интернет?

За някои от нас, интернет е мястото, чрез което поддържаме връзка с приятелите си, следим последните новини, пазаруваме и играем игри. За други, интернет може да означава техния локален доставчик, скритите под земята мрежи и оптични кабели, които пренасят информация напред и назад през градове и океани. Тогава, кой е прав?

Нека започнем от самото начало - 1974 г. Това е годината, в която няколко компютърни специалисти изобретили нещо, наречено Пакет за интернет протокол (Internet Protocol Suite) или TCP/IP за по-кратко. TCP/IP създали множество от правила, които позволявали на компютрите да си “говорят” помежду си. С други думи, TCP/IP осигуряват правила за комуникация, които гарантират, че свързаните устройства се “разбират” и могат да си изпращат информация. Групата от свързани устройства се увеличила от просто една стая до много стаи, след това до много сгради, а накрая до цели градове и държави и така се родил интернет.

А какво е уеб приложение?

Ако играете онлайн игри, използвате онлайн програма за редактиране на снимки или разчитате на уеб-базирани услуги като Google Maps, Twitter, YouTube или Facebook, то тогава вие сте активен гражданин в света на уеб приложенията.

На английски, web app е съкратено от web application (уеб приложение). Приложенията се наричат още програми или софтуер. Първоначално, те са били създадени, за да извършват големи и обширни задачи като счетоводни услуги или задачи, свързани с обработването на текст. В света на уеб браузърите и смартфоните, приложенията обикновено са бързи програми, фокусирани върху една единствена задача. Уеб приложенията конкретно изпълняват тези задачи в уеб браузъра и често предоставят богато и интерактивно преживяване. Google Maps е добър пример за уеб приложение. То е фокусирано върху една задача - осигуряването на полезни функции за работа с карта директно през уеб браузъра.

Sinatra и Ruby on Rails

След като се отклонихме малко, за да разясним още няколко термина, да се залавяме с истинската работа. Както казахме, ще сглобим нашето уеб приложение с помощта на Sinatra.

Интересен факт е, че името на Rails Girls събитията идва от друга Ruby библиотека за правене на уеб приложения, наречена Ruby on Rails. В първите издания на Rails Girls Sofia използвахме Ruby on Rails в това ръководство, но впоследствие преминахме на Sinatra, която е далеч по-проста и изчистена и е много по-добър вариант за навлизане в света не уеб програмирането.

Инструктор:

Обяснете накратко какво е Sinatra, какво е “библиотека”/”framework” и защо съществуват софтуерните библиотеки изобщо.

Инсталиране на Sinatra

Първо се налага да инсталираме библиотеката Sinatra, която ще използваме. Изпълнете следната команда в конзолата/комадния ред на Codespaces:

gem install sinatra --no-document

Инструктор:

Припомнете и допълнете неща за работа с command line, ако е нужно.

Опцията --no-document пропуска генерирането на конзолна документация за Sinatra и значително ускорява процеса по инсталация.

Помощ (документация) за Sinatra

След като сме си инсталирали библиотеката, идва моментът да я използваме. По-долу ще ви даваме необходимия код, който да изпълните стъпка по стъпка, но ако някъде искате да се отклоните от примерите, или ударите на проблем, ще ви е необходима консултация с ръководството (документацията) на библиотеката Sinatra.

Тази документация се намира на адрес: http://www.sinatrarb.com/intro.html

Умението да се намира правилната документация и да се чете и извлича необходимата информация от нея е много важно и често подценявано. Ако търсите нещо там, четете внимателно, осмисляйте и питайте инструктора за това, което не ви е ясно. Не сканирайте диагонално, за да не изпуснете това, което ви трябва.

Създайте своето първо Sinatra приложение

За да започнем нашето приложение, ще създадем един празен текстов файл в папката на нашия Codespaces проект. Преди това, обаче, е добре да изтрием всички други примерни файлове, които са създадени там. Нито един от тях няма да ни трябва, само ще ни пречат. Можете да ги изтриете един по един от дървото с файлове вляво, или да изпълните следната команда в прозореца с конзолата на Codespaces: rm -r *. Попитайте инструктора си за обяснение на тази команда. Може да използвате и този пищов с помощни команди.

Когато сме почистили излишното от папката на проекта, можем да пристъпим към създаването на празния текстов файл. Може да го кръстим voter.rb. Създаването може да стане или от терминала, с командата touch voter.rb, или от графичния интерфейс на Codespaces, с дясно копче върху дървото с файлове. След като го създадете, го отворете в редактора (с двоен клик върху дървото с файлове) и сложете вътре следното съдържание:

require 'sinatra'

get '/' do
  'Здравей, гласоподавателю!'
end

В този Ruby файл ще се намира кодът (логиката) на вашето приложение. Можете да кръстите Ruby файла с каквото име желаете. voter.rb е просто примерно такова.

Стартиране на приложението

От директорията, съдържаща създадения по-горе файл, изпълнете следната команда:

ruby voter.rb

Ще видите да се появяват съобщения на екрана. След няколко секунди, приложението ви би трябвало да е заредило напълно и да е готово да обслужва потребители.

В долния десен ъгъл на екрана ще се появи прозорец, който съдържа URL адреса на вашето приложение. Ако кликнете върху линка Open in Browser, ще се отвори нов раздел в браузъра ви, в който трябва да пише “Здравей, гласоподавателю!”.

Github Codespaces Preview URL

Инструктор:

Обяснете за това как се стартират Ruby програми и припомнете, че ruby е програма-интерпретатор, посредник между написания в текстовия файл Ruby код и операционната система/компютъра, които не разбират директно Ruby. Говорете за команди и терминал, ако е необходимо. Обърнете внимание, че терминалът е “зает” с изпълнението на процес и това ще остане така, докато не спрем процеса ние, ръчно.

Панелите в Codespaces могат да се разместват и да им се променя големината. Могат да се отварят и повече от един таб с конзола, както в долната част на екрана, така и в големия прозорец с редактора, или в панел до него.

Ако правим промени по кода на приложението, ще се налага да го спираме и пускаме, за да се отразят тези промени. Трябва да затваряме и отваряме наново съответния таб в браузъра.

За да спрете приложението си, натиснете клавишната комбинация Ctrl + C, когато фокусът ви е в терминала.

Инструктор:

Обяснете накратко, че кодът около get се грижи да посрещне заявките до началната страница на нашето приложение (/) и да върне отговор и че това, което пише вътре в низа (внимавайте с термините; низ = текст между кавички), е това, което ще се покаже в браузъра на потребителя.

Ако прецените, че е удачно на този етап, може да споменете накратко за HTTP и как информацията пътува между браузъра и сървъра (request → response). В това ръководство се използва само HTTP GET за простота.

Добавяне на начална HTML страница

В примерната “страница” по-горе сложихме едно изречение, но без нито грам HTML. Нека променим това.

Заменете текста 'Здравей, гласоподавателю!' между единичните кавички със съдържанието на някой от HTML файловете, които сте създали на предните стъпки. Бихте могли да копирате целия файл и да го вмъкнете между единичните кавички и нещата би трябвало да работят.

Възможно е да имате проблем само ако във файла с HTML-а имате единични кавички ('). Ако това е така, за да помогнете на Ruby да разбере откъде започва и къде свършва вашият HTML, може да използвате %q( вместо “отваряща кавичка” и ) вместо затваряща. Например така:

require 'sinatra'

get '/' do
  '<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Машина за гласуване</title>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body class="container">
    <p>Къде да обядваме?</p>
    <form action="/cast">
      <ul class="unstyled">
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Happy">
            Happy
          </label>
        </li>
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Кривото">
            Кривото
          </label>
        </li>
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Мистър Пица">
            Мистър Пица
          </label>
        </li>
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Слънце Луна">
            Слънце Луна
          </label>
        </li>
      </ul>
      <button type="submit" class="btn btn-primary">Гласувай!</button>
    </form>
  </body>
</html>'
end

Инструктор:

Обяснете какво се случва тук – последният текстов низ в get блока в Sinatra реално е това, което Sinatra ще изпрати на браузъра.

По-долу този подход се изоставя и се преминава към отделни view файлове, които вече използват ERB, за да има правилно оцветяване на кода и да е по-удобна редакцията – да не трябва да се рестартира приложението при всяка промяна на inline-натия HTML. Въпреки това, ако прецените, че е още рано за ERB и отделни изгледи, може да останете на текущия подход. В този случай, може да ви се наложи да иползвате стрингова интерполация, за да вмъквате Ruby код в текста. Това може да стане като замените единичните кавички около HTML кода с %Q( и ) (еквивалент на низ с двойни кавички) и използвате синтаксиса за интерполация #{ruby code here}, например така: %Q(The answer is: #{40 + 2}).

Преместване на HTML-а в отделен файл

Подходът по-горе, при който вмъкваме HTML-а директно в Ruby файла на нашето приложение (voter.rb) работи и е удобен в началото, но има няколко проблема:

Можем да решим и трите проблема, ако изведем HTML кода на страниците в отделни, самостоятелни файлове. Така нещата ще са ясни и подредени. Всеки файл ще съдържа само HTML, който ще е отделен от Ruby кода на приложението ни и ще си има правилното оцветяване на HTML таговете.

В контекста на едно уеб приложение, такива отделни файлове с HTML код се наричат още “шаблони за изгледа” (“view templates”, или само “views” накратко). Защо шаблони ще разберете скоро.

За целта, трябва да направим папка в нашия проект, в която ще слагаме тези HTML файлове. Папката трябва да се казва views (логично, нали?)

Сложете следния код във файл, който се казва index.erb и се намира в папката views (т.е. views/index.erb):

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Машина за гласуване</title>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body class="container">
    <p>Къде да обядваме?</p>
    <form action="/cast">
      <ul class="unstyled">
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Happy">
            Happy
          </label>
        </li>
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Кривото">
            Кривото
          </label>
        </li>
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Мистър Пица">
            Мистър Пица
          </label>
        </li>
        <li>
          <label class="radio">
            <input type="radio" name="vote" value="Слънце Луна">
            Слънце Луна
          </label>
        </li>
      </ul>
      <button type="submit" class="btn btn-primary">Гласувай!</button>
    </form>
  </body>
</html>

Променете секцията с get действието във файла voter.rb така, премахвайки HTML кода от там:

get '/' do
  erb :index
end

Проверете дали всичко е наред, като спрете и пуснете отново вашето приложение с ruby voter.rb. Знаете ли, че в терминала имате история на последно изпълнените команди? Като натискате стрелките нагоре и надолу на клавиатурата, ще се движите по тази история, когато в момента няма друга текущо работеща команда.

Очакваният резултат е списък с места за обяд, под формата на радио бутони и бутон “Гласувай!”.

Инструктор:

Обяснете защо HTML-ът, който сложихме в index.erb, се генерира от приложението ни (конвенция, приета от Sinatra, която търси файл с това име във views/; ако е нужно, покажете документацията на Sinatra, секцията за изгледи).

Обяснете защо избираме точно името “index”.

Предаване на променливи към изгледите

Променете index.erb файла в папката views, за да добавите <h1>…</h1> тагове:

  <body class="container">
    <h1><%= @title %></h1>
    <p>Къде да обядваме?</p>

Променете и get действието по следния начин:

get '/' do
  @title = 'Добре дошли в машината за гласуване!'
  erb :index
end

Инструктор:

Обяснете концепцията за шаблони (templates), още наричани “изгледи”. Защо има нужда от това? Обяснете какво е ERB (embedded Ruby) и как става вмъкването на логика (Ruby) в изгледа и как HTML-ът се “динамизира”. Опционално, може да отбележите и че в Ruby има и други начини за генериране на HTML от шаблони (например, Slim, HAML и че ERB е просто една от възможностите.

Споменете накратко, че @title е определен вид променлива (в случая, инстанционна) и че това е начин да се предават стойности (променливи) от “действието” (get) към шаблона на изгледа (index.erb). За информация на инструктора – причината това да работи е, че в Sinatra и действието get, и изгледите се оценяват в контекста на един и същи Ruby обект. Инстанционните променливи в Ruby могат да се създават динамично и не е нужно да са предварително декларирани. Достъп до недефинирана инстанционна променлива връща nil.

Обърнете внимание върху ERB таговете <%= %>, които не са HTML код, не се виждат в браузъра и служат за изпълнение на Ruby код на сървъра и за вмъкване на резултата в HTML-а, който браузърът ще види.

Проверете в браузъра как изглежда вече генерираният HTML, за да се види разликата между шаблона index.erb и резултата, който браузърът вижда.

Добавяне на възможност за изпращане на резултатите

Инструктор:

Какво става, когато се гласува в момента? Обърнете внимание на 404 страницата по подразбиране на Sinatra и на информацията на нея. Защо имаме грешка 404?

Добавете следното във voter.rb:

get '/cast' do
  @title = 'Благодарим за вашия глас!'
  @vote  = params['vote']
  erb :cast
end

Създайте нов файл в папката с изгледи views, който се казва cast.erb. В него сложете следния HTML и ERB код:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Машина за гласуване</title>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body class="container">
    <h1><%= @title %></h1>
    <p>Вашият глас: <%= @vote %></p>
    <p><a href="/results">Вижте резултатите!</a></p>
  </body>
</html>

Инструктор:

Обяснете, че нещата в params идват от Sinatra и че params е друг вид променлива от тип Ruby речник. Разкажете как се съпоставят полетата от формуляра с нещата в params и как можем да ги използваме.

Бонус:

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

Показване на резултатите

Добавете следния код във вашето приложение, във voter.rb:

get '/results' do
  @title = 'Резултати'
  @votes = {'Happy' => 7, 'Слънце Луна' => 5}
  erb :results
end

Създайте и нов шаблон за изглед в папката views, който кръстете results.erb:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Машина за гласуване</title>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet" />
  </head>
  <body class="container">
    <h1><%= @title %></h1>
    <table class="table table-hover table-striped">
      <tr>
        <th>Happy</th>
        <td><%= @votes.fetch('Happy', 0) %></td>
        <td><%= '#' * (@votes.fetch('Happy', 0)) %></td>
      </tr>
      <tr>
        <th>Кривото</th>
        <td><%= @votes.fetch('Кривото', 0) %></td>
        <td><%= '#' * (@votes.fetch('Кривото', 0)) %></td>
      </tr>
      <tr>
        <th>Мистър Пица</th>
        <td><%= @votes.fetch('Мистър Пица', 0) %></td>
        <td><%= '#' * (@votes.fetch('Мистър Пица', 0)) %></td>
      </tr>
      <tr>
        <th>Слънце Луна</th>
        <td><%= @votes.fetch('Слънце Луна', 0) %></td>
        <td><%= '#' * (@votes.fetch('Слънце Луна', 0)) %></td>
      </tr>
    </table>
    <p><a href="/">Гласувайте още</a></p>
  </body>
</html>

Стартирайте отново приложението с ruby voter.rb и проверете дали ще може да видите резултатите.

Инструктор:

Обяснете как работят таблиците в HTML. Обяснете как липсващите стойности от речника се броят като нула (за справка - документацията на fetch). Обяснете как така умножаваме текстов низ по число и какъв е резултатът.

Припомнете концепцията на типа данни “речник” (хеш). При необходимост, пуснете още един терминал в Codespaces и в него стартирайте irb, където демонстрирайте работа с хешове (речници) – създаване, извличане на стойност, добавяне и промяна на елементи, обхождане. Използвайте puts където е нужно. Оставете участничките да си поиграят и да осмислят концепциите за променливи. Експериментирайте с fetch.

Помислете заедно защо в момента гласовете не се отразяват изобщо. Съберете идеи какво би трябвало да се промени.

Отчитане на гласовете

Добавете следния код в началото на файла voter.rb (преди всички действия get):

votes = {}

След което променете кода в действията cast и results в същия файл така:

get '/cast' do
  @title = 'Благодарим за вашия глас!'
  @vote  = params['vote']

  votes[@vote] = votes.fetch(@vote, 0) + 1

  erb :cast
end

get '/results' do
  @title = 'Резултати'
  @votes = votes
  erb :results
end

Спрете и стартирайте отново приложението. Уверете се, че гласовете вече се отчитат правилно. Може да дадете адреса до вашето приложение и на някой друг, за да гласува и той.

Поздравления! Първата бета версия на вашето уеб приложение за гласуване е готова!

Инструктор:

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

Обяснете промените. Защо имаме нужда от този fetch в cast? Каква е ролята на променливата votes?

(Блоковете в Ruby виждат локалните променливи, дефинирани в closure-a над тях, а действията get в Sinatra реално са Ruby блокове. Докато Sinatra работи и уеб приложението е живо, локалната променлива се пази между request-и, като in-memory DB. Инстанционните (тези, започващи с @) променливи в действията – не се пазят между request-и.)

Обърнете внимание на факта, че като се спре приложението, данните се губят.

Опционални и допълнителни задачи

Стъпките по-долу не са задължителни и спокойно можете да спрете с ръководството до тук. Все пак, ако ви е останало време и ви е интересно, можете да си изберете което и да е от нещата по-долу и да пробвате да го реализирате с напътствия от инструктора ви.

Опционално – изнесете общия код в layout

Ако искате да промените текста в <title> тага, на колко места ще трябва да направите промяната? Ами ако искате да добавите други стилове? А ако искате да вмъкнете header или footer, който да се появява на всяка страница?

За да се избегне повторението на код, което възниква между различните изгледи, обикновено е практика общата част да се изнесе в отделен шаблон – така нареченият “layout”.

Създайте файл layout.erb в папката с изгледите views. Сложете там следния код:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Машина за гласуване</title>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet" />
  </head>
  <body class="container">
    <h1><%= @title %></h1>
    <%= yield %>
  </body>
</html>

След това махнете вече дублиращите се общи части с HTML от другите два шаблона (index.erb, cast.erb и results.erb).

Инструктор:

Обяснете защо се прави тази операция. Разяснете, че другите изгледи се вмъкват на мястото на yield и не е нужно да носят информация за общия layout.

Обяснете защо кръстихме файла layout и защо нещата автоматично сработиха, без да декларираме някъде, че това е нашият layout (Sinatra спазва принципа “convention over configuration” и ако следваме определени правила за кръщаване на файлове и папки, нещата ще работят автоматично, без да има нужда от конфигурация).

Опционално – качете приложението на Heroku

Ако желаете, може да си направите безплатна регистрация в Heroku и да качите приложението, което сте направили до момента, за да може да го ползва всеки, който пожелае.

За целта, може да следвате това ръководство и да помолите инструктора си за съдействие.

Опционално – трайно съхранение на гласовете с YAML::Store

Време е за нещо вълнуващо! Трайно съхранение на данни (persistence). Обикновено това става в СУБД (системи за управление на бази данни), накратко наричани “бази данни”. Примери за някои популярни СУБД са PostgreSQL, MySQL, MS SQL, Oracle и други.

Ние няма да използваме цяла СУБД, а ще пазим данните от гласуването в обикновен текстов файл, който ще се обновява автоматично от нашето приложение. За целта ще ни послужи друга помощна библиотека, която е вградена в езика Ruby, но която трябва да заредим изрично.

Добавете следното в началото на voter.rb:

require 'yaml/store'

Пак във voter.rb, заменете действията get '/cast' и get '/results' със следните версии:

get '/cast' do
  @title = 'Благодарим за вашия глас!'
  @vote  = params['vote']

  @store = YAML::Store.new 'votes.yml'
  @store.transaction do
    if @store['votes'] == nil
      @store['votes'] = {}
    end

    @store['votes'][@vote] = @store['votes'].fetch(@vote, 0) + 1
  end

  erb :cast
end

get '/results' do
  @title = 'Results so far:'

  @store = YAML::Store.new 'votes.yml'
  @votes = @store.transaction { @store['votes'] }

  erb :results
end

На този етап може да изтриете и реда votes = {} от voter.rb, който добавихме на предната стъпка.

Спрете и пуснете отново приложението. Гласувайте и наблюдавайте резултатите. Вижте дали се запазват, ако спрете и пуснете приложението отново. Разгледайте новопоявилия се и автоматично създаден и попълван файл votes.yml. Би трябвало да се намира в същата папка, където е voter.rb.

Инструктор:

Обяснете какво е YAML и как се грижи за обновяване на votes.yml файла и защо са ни if-овете в кода по-горе.

Още опционални задачи

Опитайте да промените неща в приложението, за да видите какви ще са резултатите: