Kurs Ruby on Rails

Kurs Ruby on Rails – Lekcja 2 – Ruby

Czego się nauczycie?

  • Korzystać z IRB
  • Zmiennych i typów, w tym:
    • Typów prostych
    • Typów złożonych (Tablice i Hashe)
  • Wyrażeń logicznych
  • Czym są pytajniki i wykrzykniki
  • Jak napisać blok kodu
  • Jak napisać swój pierwszy skrypt w ruby
  • Wyrażeń warunkowych
  • Poznacie pętle dostępne w ruby
  • Jak napisać swoją pierwszą klasę
    • Czym jest konstruktor
    • Pisania metod klasy
  • Dziedziczenia klas

Niezbędnik kursu:

1. Ruby

Otwieramy konsolę irb poprzez wpisanie w terminalu

$ irb

IRB, czyli iteractive ruby to powłoka, w której wykonywać będziemy polecenia rubiowe w tej lekcji.

Podstawowe typy i zmienne

Każdy obiekt w ruby ma swoja klasę. Dostępne są nam typy proste, takie jak:

  • Liczba całkowita
> 1234.class
  =>  Integer
  • Liczba zmiennoprzecinkowa
> (2.15).class
  =>  Float
  • Tekstowy typ danych
> 'some characters'.class
  => String

Prócz tego są jeszcze przedziały Range, wyrażenia regularne Regexp, czy importowane za pomocą require 'BigDecimal' typy zmiennoprzecinkowe BigDecimal, które oferują lepszą precyzję niż typ Float.

Wszystko możemy przypisać do zmiennych poprzez znak równości

> variable = 10
  => 10

Po czym wywołać zmienną, która zwróci nam swoją przypisaną wartość

> variable
  => 10
  • Wartości logiczne TrueClass
true.class
  => TrueClass

oraz FalseClass

> false.class
  => FalseClass
  • Null
> nil
  => nil
> nil.class
  => NilClass

nil czyli obiekt, który ma pustą wartość.

Stringi i symbole

Prócz wywołania metody .class, której używamy wyżej, jest też wiele innych natywnych metod, które możemy wywołać na danym obiekcie, jak np. w przypadku danych typu string.

> 'some chars'.length
  => 10

zwróci długość wartości włącznie ze znakami pustymi

> 'some chars'.reverse
  =>  "srahc emos"

zwróci wartość w odwróconej kolejności

> 'some chars'.upcase
  =>  "SOME CHARS"

zwróci wartość dużymi literami.

Aby poznać więcej metod dostępnych dla danego typu, zachęcam wywołać dla danego obiektu .methods i pozwiedzać, polecam również obszerną dokumentację dostępną pod adresem:

https://ruby-doc.org/core-3.0.2
  • Konkatenacja i interpolacja

Konkatenacja, czyli łączenie ze sobą wyrażeń, w tym wypadku dwóch obiektów typu string, stworzy nam jeden obiekt

> 'two  ' + 'parts'
  =>  "two  parts"

Za pomocą interpolacji, możemy wstawić w obiekt typu string wartość innego obiektu

> lines_count =  10
  => 10
> "This file  has #{lines_count}  lines."
  =>  "This file  has 10  lines."
  • Symbol, czyli obiekt, który posiada tylko nazwę oraz ID
> :counter.class
  =>  Symbol
> "char".object_id
  => 70099361336540
> "char".object_id
  => 70099353185720
> :char.object_id
  => 1276508
> :char.object_id
  => 1276508

Symbol w danym programie posiada stałe ID, kiedy każdy nowo inicjalizowany obiekt generuje kolejne ID. Symbole nie posiadają wartości.

Tablice i Hashe

Tablice są kolekcją różnych obiektów, indeksowanych kolejnymi liczbami całkowitymi (zaczynając od 0). Tablice w Ruby są dynamiczne, nie trzeba definiować im typu ani deklarować rozmiaru tablicy.

> Array.new
  =>  [] #zainicjalizuje nową pustą tablicę
> Array.new(3)
  =>  [nil, nil, nil] #zainicjalizuje nową tablicę z trzema wartościami typu nil
> Array.new(3, 5)
  =>  [5, 5, 5] #zainicjalizuje nową tablicę z trzema wartościami 5

Przypisujemy array do zmiennej, jak widać, jego elementy nie muszą być tego samego typu, mimo że mieszanie ich jest złą praktyką.

> arr = [ 1 ,  5 ,  "cat",  :counter]
  =>  [ 1 ,  5 ,  "cat",  :counter]

Z zapisanej tablicy możemy odczytywać wartości elementów na konkretnych indeksach

> arr[ 0 ]
  => 1
> arr[ 2 ]
  =>  "cat"

Możemy również do tablicy dokładać kolejne elementy poprzez użycie metody

> arr.push("last  element")
  =>  [ 1 ,  5 ,  "cat",  :counter, "last element"]

lub przez użycie operatora

> arr <<  "now Im last"
  =>  [ 1 ,  5 ,  "cat",  :counter, "last element", "now Im last"]

ale możemy też usuwać

> arr.pop
  =>  "now Im last"
> arr
  =>  [ 1 ,  5 ,  "cat",  :counter, "last element"]

Hash to nieco inna kolekcja, indeksowana dowolnym obiektem, składająca się z unikalnych kluczy oraz przypisanych do nich wartości. Jest też nazywany tablicą asocjacyjną. Tak jak w przypadku tablic, kolejne wartości oddzielone są przecinkami.

> {}
  => {}
> Hash.new
  => {}
> age_of_people = { bob: 20, jane: 18 }
  => { :bob=> 20 , :jane => 18 }

Stworzyliśmy hash z dwoma elementami, możemy poznać klucze oraz wartości tych elementów kolejno poprzez

age_of_people.keys
=> [:bob, :jane]

age_of_people.values
=> [20, 18]

Tak jak w przypadku tablic, możemy odczytać konkretną wartość, tylko w tym przypadku podając jej klucz

> age_of_people[:jane]
  => 18

Aby dodać wartości do hasha używamy:

> age_of_people[:mark] = 30
  => 30
> age_of_people
=>  {:bob=> 20, :jane=> 18, :mark=> 30 }

Wyrażenia logiczne

Mamy dwie możliwości zapisywania wyrażeń AND i OR

false and true => false false && true => false false or true => true false || true => true

Pytajniki i wykrzykniki

Pomagają nam zrozumieć kod. Metoda zakończona wykrzyknikiem oznacza potencjalne niebezpieczeństwo. Najczęściej oznacza to, że metoda może zwracać wyjątki, albo zmienia obiekt, na którym jest wywoływana. Metoda zakończona znakiem zapytania zwraca wartość boolean (true lub false).

arr = [1, 10, 7, 3] => [1, 10, 7, 3] arr.sort => [1, 3, 7, 10] #sortuje, ale nie zmienia obiektu arr => [1, 10, 7, 3] arr.sort! => [1, 3, 7, 10] #sortuje i zmienia obiekt arr => [1, 3, 7, 10] arr.empty? => false age_of_people = { bob: 20, jane: 18 } => { :bob=> 20, :jane=> 18 } age_of_people.has_key?(:bob) => true age_of_people.has_value?( 19 ) => false

Bloki

Bloki to anonimowe funkcje, które są ograniczone albo słowami kluczowymi do oraz end lub klamrami {}. Dobrym przykładem wykorzystania bloków w Ruby jest iteracja po kolekcji.

[1, 3, 7, 10].each { |a| puts a }

Przyjęto, że jednolinijkowe instrukcje zapisuje się wewnątrz nawiasów klamrowych, natomiast w przypadku większej ilości instrukcji, używa się składni do end

[1, 3, 7, 10].each do |a| b = a + 10 puts b end

Blokiem można nazwać również instrukcje zawarte pomiędzy begin oraz end. Zdefiniuje to kod, który wykona się we własnym kontekście, przykładem jest chociażby deklaracja metody, która omija słowo kluczowe begin, ale działa na takiej samej zasadzie. O metodach powiemy w drugiej części lekcji.

def method
...
end

W Ruby domyślnie zwracana jest wartość ostatniej wykonanej linijki, bez potrzeby używania return

begin a = 1 + 2 b = a + 3 end => 6 # zwracana jest ostatnia instrukcja w bloku

Skrypt w Ruby

Kolejne przykłady nie są już jednolinijkowymi instrukcjami składni, dlatego łatwiej będzie pokazać je pisząc skrypt. Skrypt, czyli instrukcje zapisane w pliku, który to plik możemy następnie wykonać poprzez polecenie Ruby.

Wyjdźmy z irb i stwórzmy więc plik poprzez

$ touch script.rb

otwieramy plik ulubionym edytorem tekstu i umieszczamy w nim

begin
   a =  10
   b =  12
   puts a + b # wyświetli wynik w konsoli
   b
end

w terminalu wywołujemy skrypt poprzez wpisanie

$ ruby  script.rb

W efekcie, zobaczymy wyświetlony w konsoli wynik metody puts, nie należy natomiast mylić tego z wynikiem bloku, który jak już wiemy, powinien zwrócić wartość ostatniej linijki kodu czyli 12 Od teraz, kod wpisywać będziemy do tego pliku i wywoływać go w taki sam sposób.

Wyrażenia warunkowe

Bardzo często potrzebujemy, aby kod wykonał inne instrukcje w przypadku spełnienia lub niespełnienia danego zestawu warunków. Z pomocą przychodzi nam wyrażenie if

if 2 + 2 == 4
   puts 'math is working'
elsif true
   puts 'should not go here'
else
   puts 'something is wrong'
end

W powyższym kodzie, po wyrażeniach ifelsif, pojawiają się warunki oraz pod nimi kod, który ma być wykonany gdy warunek jest spełniony. Wykonany zostanie kod dla pierwszego w kolejności spełnionego warunku. Ilość warunków oczywiście możemy zwiększyć poprzez dodanie kolejnych elsif

else sygnalizuje, że ten kod będzie wykonany w przypadku niespełnienia żadnego z poprzednio ewaluowanych warunków.

Jeśli potrzebujemy sprawdzić zanegowany warunek, możemy użyć if !(warunek), lub if not

if not 2 + 2 == 4
   puts 'math is not working'
elsif true
   puts 'This time should go here'
else
   puts 'something is wrong'
end

Ternary operator

Czyli operator warunkowy, pozwala nam osiągnąć część powyższej funkcjonalności znacznie skracając ilość linijek kodu. Istotną różnicą jest, że poprzedni przykład mógł posiadać wiele warunków, w tym wypadku warunek mamy jeden oraz dwie drogi w wypadku, gdy warunek jest spełniony lub nie. A więc:

if 12 > 14
  puts('12 is greater than 14')
else
  puts('12 is not greater than 14')
end

możemy zapisać jako

12 > 14 ? puts('12 is greater than 14') : puts('12 is not greater than 14')

Zaraz po warunku występuje wolnostojący ?, który jest elementem składni; sygnalizuje on operator warunkowy. Następnie mamy kod, który zostaje wykonany w przypadku spełnienia warunku, : który również jest elementem składni oraz sprawia, że następny element będzie kodem wykonanym w przypadku niespełnienia warunku.

Mamy za sobą pytajniki, możemy ich użyć również w takim przypadku

[1,2,3].empty? ? puts 'empty' : puts 'full'

modyfikator

if razem z warunkiem może występować również po instrukcji, jeśli ma ona być wykonana tylko w określonej sytuacji

puts "2" if [].empty?

to samo dotyczy operatora unless, który jest negacją if

puts "2" unless [1,2].empty?

switch statement – ruby case

W niektórych przypadkach, zamiast pisać wiele instrukcji elsif, lepiej jest użyć operatora case, który składa się ze słowa kluczowego case, po którym występuje zmienna, która będzie ewaluowana. Warunki wpisujemy każdorazowo po słowie when. Słowo else jest opcjonalne.

color = 'blue'

case color
when 'blue'
  puts 'The color is blue'
when 'red'
  puts 'The color is red'
when 'purple'
  puts 'The color is purple'
else
  puts 'The color is not blue, red, or purple'
end

Każdy kolejny warunek rozpatrywany będzie na zasadzie condition === color, gdzie === jest metodą porównującą implementowaną przez obiekt. W naszym przypadku, obiekt typu String

> 'blue' === 'blue'
  => true

Pętle

Ruby oferuje standardowe pętle forwhile , loopuntil oraz iteracje po kolekcjach. Pobieżnie przyjrzymy się każdej z nich.

for wywołuje blok tak długo, jak enumerator jest w zakresie

for counter in 1..10
  puts counter
end

while wywołuje blok tak długo, jak warunek jest spełniony

counter = 0
while counter < 10
  puts counter
  counter += 1
end

until, podobnie jak while, będzie działał do momentu spełnienia warunku

counter = 0
until counter > 9
  puts counter
  counter += 1
end

Różnica jest taka, że ta pętla przestanie się wykonywać zaraz po zewaluowaniu warunku, kiedy while zakończy się po wykonaniu instrukcji, dla której warunek jeszcze był spełniony.

loop będzie się wykonywał do momentu przerwania poprzez break

counter=0
loop do
  break if counter > 10
  puts counter
  counter+=1
end

Metoda times wywołuje blok określoną liczbę razy

10.times  do |index|
   puts  index
end

Pętle wykonywane na kolekcjach

collection = [ 1 , :two, 'three']
for element in collection
   puts  element
end

Metoda each iteruje po kolekcji, wykonuje dany blok kodu dla każdego elementu i zwraca pierwotną kolekcję.

collection.each  do |member|
   puts  member
end

Metoda map iteruje po kolekcji, wykonuje dany blok kodu dla każdego elementu i zwraca tablicę wyników operacji.

collection  = [ 1 ,  2 ,   3 ]
collection.map do |member|
   member ^ 2
end

Blok możemy zapisać również w jednej linijce, używając klamer zamiast do/end.

collection.map  { |member|  member ^ 2 }

Klasy i atrybuty

Napiszmy nową klasę

class Post
  attr_accessor :body, :author
end

Klasa będzie posiadała dwa atrybuty :body oraz :author

Inicjalizujemy nowo napisaną klasę przez

post = Post.new
post.author = 'John'

jednocześnie przypisaliśmy wartość do atrybutu :author, więc kiedy odwołamy się do niego, otrzymamy zapisaną wartość

puts  post.author

Konstruktor i metody

Tym razem napiszmy klasę wraz z konstruktorem, czyli metodą, która jest automatycznie wywoływana przy zainicjalizowaniu obiektu klasy.

class Posts
  attr_accessor :body,  :author

  def initialize (body, author)
    @body = body
    @author  = author
  end
end

W tym wypadku, kiedy inicjalizujemy klasę przez .new, musimy podać jej dwa parametry, które przyjmuje metoda initialize. W wypadku nie podania parametrów, otrzymamy błąd.

post1 = Post.new('This is first body', 'John')
post2 = Post.new('This is second body', 'Adam')
puts  post1.body
puts  post2.author

prócz konstruktora, możemy dodać inne metody, które w odróżnieniu od niego nie będą wywoływane automatycznie, a jawnie przez nas

class Post
  attr_accessor :body,  :name, :surname

  def initialize (body, name, surname)
    @body = body
    @name = name
    @surname = surname
  end

  def author_name
    puts "#{@name} #{@surname}"
  end
end
  post = Post.new("This is my post!", "John", "Doe")
  post.author_name

Dziedziczenie

Klasy mogą dziedziczyć z innych klas poprzez użycie < w deklaracji klasy. Klasa dziedzicząca przejmuje atrybuty oraz metody klasy, z której dziedziczy.

class Message < Post
  def author_name
    puts "Hello Im #{@name}"
  end
end

możemy natomiast nadpisywać metody tak, jak w przypadku author_name

post = Post.new('This is my post!', "John", "Doe" )
message = Message.new('This is my message!', "John", "Doe")
post.author_name
message.author_name

Praca własna

  1. Stwórz nowy skrypt
  2. W skrypcie napisz klasę User z konstruktorem, niech klasa ma atrybuty takie jak: name, surname, email, phone number.
  3. Zainicjalizuj trzy obiekty klasy User i przypisz je do zmiennych
  4. Napisz nową klasę Admin, która dziedziczy z klasy User
  5. Zainicjalizuj 2 obiekty klasy Admin
  6. Stwórz tablicę, w której umieścisz stworzone obiekty oraz przeiteruj się po niej tak, aby zwróciła numery telefonów.
  7. (trudne) Zmodyfikuj pętle z punktu 6 tak, aby zwracała numer telefonu tylko, jeśli suma jego cyfr jest parzysta

Przeczytaj również o...

Dołącz do naszego zespołu!

Zobacz oferty pracy