Cześć, w dzisiejszym odcinku chciałbym pokazać wam jak zbudować bardzo prostą aplikacje bez użycia Ruby on Rails.

Do tego celu użyjemy gema sinatra

http://sinatrarb.com/

nie jest to żaden framework a biblioteka która pozwala szybko tworzyć aplikacje internetowe w języku Ruby

na początek utwórzmy sobie nowy plik todo.rb

require 'sinatra'

get '/' do
  'Hello world!'
end

zainstalujemy gem sinatra

gem install sinatra

teraz możemy uruchomić nasz wcześniej utworzony plik

ruby todo.rb

jeżeli wejdziemy na http://localhost:4567 zobaczymy napis hello world który to ustaliliśmy w pierwszym pliku

teoretycznie jeden plik wystarczy by aplikacja działała

ale utwórzmy sobie Gemfile bo za już za chwilę okażę się, że kilka gemów potrzebujemy

source 'https://rubygems.org'

gem 'rack'
gem 'sinatra'

jak widzicie po za sinatrą jest jeszcze rack. Czym jest rack - https://github.com/rack/rack

rack to najprostszy webserwer w języku ruby, dodatkowo większość popularnych rozwiązań również go wykorzystuje.

utwórzmy nowy plik

config.ru

a w nim

require 'rubygems'
require 'bundler'

Bundler.require

require './todo'
run Todo

musimy jeszcze edytować todo.rb

class Todo < Sinatra::Base
	get '/' do
	  'Hello world!'
	end
end

od teraz naszą aplikacje będziemy uruchamiać bundle exec rackup

zmieni się też url na http://localhost:9292/

naszym zadaniem jest utworzenie todolisty tak więc będziemy potzebować:

  • wyświetlić wszystkie elementy
  • utworzyć nowy element
  • oznaczyć element jako ukończony
  • skasować element

wróćmy więc do naszego plikutodo.rb

edytujmy go tak


get '/' do
  #.. show something ..
end

to będzie wyświetlać naszą listę

post '/item' do
 # .. create something ..
end

to będzie dodawać nowy element

post '/done' do
 # .. finish item ..
end

to będzie oznaczać element jako ukończony

delete '/:id' do
  #.. annihilate something ..
end

to będzie usuwać zbędne elementy

w sumie powinniśmy mieć plik wyglądający tak

class Todo < Sinatra::Base
  get '/' do
  # .. show something ..
  end

  post '/item' do
    #.. create something ..
  end

  post '/done' do
    #.. replace something ..
  end

  delete '/:id' do
  # .. annihilate something ..
  end
end

W pierwszym przykładzie widzieliście, że wyświetlało nam Hello World

my potrzebujemy trochę bardziej skomplikowanego widoku, dlatego potrzebujemy obsługę ERB

na szczęście nic więcej nie trzeba dodawać do jego obsługi, sinatra będzie szukać widoków w katalogu views utwórzmy więc taki katalog a w nim dodajmy sobie plik index.html.erb

a w naszym pliku todo.rb

...
get '/' do
	erb :index
end
...

w naszym widoku przydało by się dodać jeszcze jakąś zawartość.

<html>
  <head>
    <title>Todo</title>
  </head>
  <body>
    <h1>Witaj</h1>
  </body>
</html>

bym zapomniał, po wprowadzeniu zmian trzeba zrestartować serwer by zmiany zobaczyć. Na szczęście można łatwo tozmienić

musimy zainstalować odpowiedni gem gem 'sinatra-contrib'

teraz dodajmy kilka zmian w naszej aplikacji (todo.rb)

require "sinatra/reloader"

class Todo < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

	...
end

od teraz gdy wprowadzimy jakieś zmiany wystarczy, że odświeżymy naszą stone.

Następnym krokiem zapewne by była baza danych

w trakcie tego filmu chce zbudować coś naprawdę prostego więc w gruncie rzeczy możemy operować na prostym pliku data.json zamiast całej bazie danych. Unikniemy dodatkowej konfiguracji, instalacji czy migracji

utwórzmy ten plik bez żadnej zawartości data.json

dodatkowo przydała by nam się obsługa jsona w naszej aplikacji więc do Gemfile dodajmy

...
gem 'json'

najłatwiej będzie zacząć od wyświetlania

do naszego data.json dodajmy pierwsze wpisy

[
  {
    "id": "1",
    "name": "test",
    "completed": false
  },
  {
    "id": "2",
    "name": "test test",
    "completed": false
  }
]

teraz możemy zacząć korzystać z tego pliku

zacznijmy od zczytania jego zawartości

file = File.read('./data.json')

teraz JSONa przeróbmy na hasha

@items = JSON.parse(file)

teraz możemy z niego skorzystać w naszym widoku

dodajmy

<ul class="list-group">
      <% @items.each do |item| %>
        <li class="list-group-item"><%= item["name"] %></li>
      <% end %>
    </ul>

musicie pamiętać, że nasze dane to hash, nie obiekt active recorda jak w railsach więc zamiast item. używamy item["nazwa_klucza"]

spróbujmy teraz dodawać nowy element do naszej listy

<form method="POST" action="/item">
      <p>Nazwa: <input type="text" name="name"></p>
      <input type="submit" value='Dodaj'>
    </form>

takim prostym formularzem będziemy dodawać element

samo zapisywanie będzie trochę trudniejsze niż w przypadku active recorda

sam formularz przekazuje nam tylko

{"name"=>"to co wpiszemy w formularz"}

nie ma tam id elementu i nie mamy stanu

utwórzmy coś takiego

new_tem = {
      id: SecureRandom.uuid,
      name:  params["name"],
      completed: false,
    }

teraz musimy to dopisać do naszego pliku

otwórzmy go

file = File.read('./data.json')

zróbmy z niego hash

data_hash = JSON.parse(file)

dodać nasz nowy task

data_hash << new_tem

i na koniec zapisać

File.write('./data.json', JSON.dump(data_hash))

jeszcze jedna rzecz na koniec

trzeba wrócić na naszą stronę główną

redirect '/'

możemy wejść na http://localhost:9292/ i zobaczyć czy działa

oczywiście taki rodzaj zapisu jest bardzo nie efektywny, po za budowaniem jakiegoś proof of concept nie polecam użycia czegoś takiego. Ale na potrzeby tego video jest to dobre rozwiązanie.

Gdy mamy już dowanie nowych elementów, może jako następny krok zrobimy ich usuwanie

Jako, że formularze w HTMLu nie wspierają metody delete zmienimy ją na post. Oczywiście możemy się wesprzeć javascriptem, ale nie będziemy teraz tego robić

post '/delete' do
  # .. annihilate something ..
end
<form method="POST" action="/delete">
	<input type="hidden" name="delete_id" value="<%= item['id'] %>">
	<button name="delete_data">Usuń</button>
</form>

w naszej metodzie usuwania ponownie musimy otworzyć nasz plik

file = File.read('./data.json')
data_hash = JSON.parse(file)

a następnie wyszukać i skasować nasz element. Po to właśnie dodajemy id do naszego elementu

data_hash.delete_if { |element| element["id"] == params["id"] }

musimy też pamiętać by dane zapisać

File.write('./data.json', JSON.dump(data_hash))

na koniec musimy jeszcze wrócić na naszą stronę główną

redirect '/'

Na koniec została nam tylko jedna akcja

oznaczanie jako wykonane

formularz będzie bardzo podobny do poprzedniego

<form method="POST" action="/done">
	<input type="hidden" name="id" value="<%= item['id'] %>">
	<input type="submit" value='Ukończone'>
</form>

podobnie jak w poprzedniej metodzie musimy otworzyć nasz plik

file = File.read('./data.json')
data_hash = JSON.parse(file)

i wyszukać element który nas interesuje

element = data_hash.find{ |e| e['id'] == params['id'] }

edytujemy to co nas interesuje

element["completed"] = true

i zapiszmy plik

i oczywiście musim pamiętać o powrocie na stronę główną

redirect '/'

teraz jeszcze tylko trzeba nasz widok zmienić tak by widać było który element jest ukończony

<% if item["completed"] %>
            <p><s><%= item["name"] %></s></p>
          <% else %>
            <p><%= item["name"] %></p>
            <form method="POST" action="/done">
              <input type="hidden" name="id" value="<%= item['id'] %>">
              <input type="submit" value='Ukończone'>
            </form>
          <% end %>

z takim ifem, jeżeli mamy ukończony element, tekst będzie przekreślony a przycisk nie będzie nam się wyświetlał

Dodam jeszcze jedną rzecz na koniec

sortowanie. Niech wszystkie elementy do wykonania będą na górze

jest tylko jeden mały problem

nie możemy zrobić

data_hash.sort_by { |item| item['completed'] } ponieważ nie możemy posortować true false

zwróciło by to nam błąd

** ArgumentError Exception: comparison of FalseClass with true failed

ale zamiast tego możemy spróbować zrobić tak:

@items = data_hash.sort_by! { |item| item['completed']? 1 : 0 }

w ten sposób będziemy mieli nie ukończone elementy na górze, a ukończone na dole

to tyle na dzisiaj

dziękuje za oglądanie

Miłego kodowania! Kod aplikacji