How does UX transform businesses? The effects of usability testing
It has already become clear that User Experience (UX) is a key factor in shaping how companies build a business. The customer-centric approach has almost completely replaced […]
Kurs Ruby on Rails – Lekcja 2 – 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.
Każdy obiekt w ruby ma swoja klasę. Dostępne są nam typy proste, takie jak:
> 1234.class
=> Integer
> (2.15).class
=> Float
> '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
true.class
=> TrueClass
oraz FalseClass
> false.class
=> FalseClass
> 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, 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."
> :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 if
, elsif
, 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 for
, while
, loop
, until
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
Przeczytaj również o...
It has already become clear that User Experience (UX) is a key factor in shaping how companies build a business. The customer-centric approach has almost completely replaced […]
What if there was one simple method to find opportunities for improved design, uncover UX problems, and learn about your target users’ behavior and preferences? Would you […]