Omówienie bloków w języku ruby.
Bloki to krótkie fragmenty kodu, które można przekazać do metody – kod w takim fragmencie jest wywoływany w kontekście metody do której został przekazany blok.
Właściwie można by je porównać do funkcji anonimowych w javascript albo lambd w pythonie, jest jednak kilka istotnych różnic o których warto wspomnieć.
O ile w działaniu bloki nie różnią się zbyt od wspomnianych już konstrukcji w językach python lub javascript to sposób przekazywania do metody jest już wart uwagi.
Blok kodu jest częścią wywołania metody, czyli jej składni – nie jest on argumentem metody – co bywa mylące.
Składnia wywołania metody.
Każde wywołanie metody w ruby ma następującą składnię:
obiekt.nazwa_metody(argumenty_metody) {blok_przekazywany_do_metody}
- – Obiekt lub zmienna odbiorcy – domyślnie jest to obiekt self.
- – Znak kropki, wymagany w przypadku określenia jawnego odbiorcy.
- -nazwa metody.
- – Lista argumentów metody (opcjonalne).
- – blok kodu, (opcjonalne).
należy zwrócić uwagę, że blok kodu i lista argumentów to dwie osobne rzeczy. Aby to zademonstrować kilka przykładów poniżej:
przykłady pochodzą z ruby irb.
$ irb --simple-prompt
>>
my_method() {# to jest blok kodu}
my_method do |arg|
#to jest też blok kodu
puts arg
end
my_method 1, "somethink" {|arg1, arg2| puts arg1 * arg2}
Ostatnie dwa przykłady demonstrują też przekazywanie parametrów do bloku, instrukcja
|param1, param2|
oznacza przekazanie do bloku dwóch parametrów – odpowiednio param1 i param2.
przykłady metod powyżej, mimo że różnią się sposobem zapisu, są oczywiście poprawne – co ładnie pokazuje elastyczność składni języka ruby.
Jako, że nie da się zdefiniować czegoś takiego jak blok domyślny, to czy metoda otrzyma blok czy nie, decyduje o jej rezultacie.
poniżej zachowanie metody each z blokiem i bez.
>> [1, 2, 3].each {|i| puts i * i }
1
4
9
=> [1, 2, 3]
>>
>> [1, 2, 3].each
=> #
>>
jak widać powyżej metoda each bez bloku zwróciła sam obiekt enumerator.
Jak to wygląda od środka.
Do wywoływania bloku wewnątrz metody służy metoda yield.
def my_method
yield
end
:my_method
>> my_method { puts "hurra! zostalem wywolany" }
hurra! zostalem wywolany
=> nil
>>
w przypadku kiedy spróbujemy wywołać zdefiniowaną wcześniej metode bez przekazywania do niej bloku otrzymamy następujący błąd:
>> my_method
(irb):4:in `my_method': no block given (yield) (LocalJumpError)
from (irb):7:in `'
(...)
należało by sprawdzić, czy blok został w ogóle przekazany – do czego służy metoda block_given?
def my_method
if block_given?
puts "blok zostal przekazany"
yield
end
puts "dalsza czesc metody"
end
wynik:
>> my_method {puts "komunikat z bloku" }
blok zostal przekazany
komunikat z bloku
dalsza czesc metody
=> nil
>> my_method
dalsza czesc metody
=> nil
>>
parametry bloków
blok tworzy własny zasięg lokalny dla zmiennych, obejmuje również zmienne zdefiniowane wewnątrz metody.
W przypadku konfliktu nazwy zmiennej metody i parametru bloku, blok zawsze będzie brał pod uwagę tylko parametr wewnątrz bloku.
przykład:
def my_method
x = 100
yield x if block_given?
puts "wartosc zdefiniowanej w metodzie zmiennej x to: #{x}"
end
wynik:
?> my_method do |x|
?> x = x * x
?> puts "zmienna z bloku x to: #{x}"
>> end
zmienna z bloku x to: 10000
wartosc zdefiniowanej w metodzie zmiennej x to: 100
=> nil
>>
W Ruby bloki kodu można przekazywać do metod na dwa sposoby: przy użyciu nawiasów klamrowych {} oraz słów kluczowych do i end. Oba sposoby mają swoje zastosowania i różnią się przede wszystkim kontekstem użycia i stylem kodu. Oto różnice i wskazówki, kiedy używać której składni:
Nawiasy klamrowe {}
Zalecane do krótkich bloków: Klamry {} są zazwyczaj używane dla krótkich, jedno-liniowych bloków kodu.
Wyższy priorytet: Nawiasy klamrowe mają wyższy priorytet składniowy, co oznacza, że mogą być używane bez dodatkowych nawiasów wokół argumentów metody.
Przykład:
[1, 2, 3].map { |n| n * 2 }
Słowa kluczowe do i end
Zalecane do dłuższych bloków: do i end są zazwyczaj używane dla dłuższych bloków kodu, często zawierających wiele linii.
Niższy priorytet: do i end mają niższy priorytet składniowy, co oznacza, że w przypadku wywoływania metod z dodatkowymi argumentami trzeba uważać na ich poprawne zamknięcie w nawiasach.
Przykład:
[1, 2, 3].each do |n|
puts n
puts n * 2
end
Zastosowanie w praktyce:
Krótki kod w jednej linii:
Użyj {}:
10.times { |i| puts i }
Dłuższy kod w wielu liniach:
Użyj do … end:
10.times do |i|
puts i
puts i * 2
end
Priorytety składniowe:
Z {} możesz mieć problemy przy bardziej skomplikowanych wywołaniach metod, więc warto uważać na kontekst.
Przykład z metodą mającą dodatkowe argumenty:
def my_method(arg1, arg2)
yield if block_given?
end
my_method(1, 2) { puts "Blok z nawiasami klamrowymi" }
my_method(1, 2) do
puts "Blok z do ... end"
end
Obiekty proc i lambdy
W ruby obiekty typu proc i lambda są to specjalne obiekty dzięki którym możemy przechowywać bloki w zmiennych, są one podobne do bloków, ale mają kilka istotnych różnic – zwłaszcza w obsłudze argumentów i zwracaniu wartości.
Proc.
Proc to skrót od procedure, obiekty tego typu enkapsulują blok kodu i mogą być wywoływane wielokrotnie, przy tym są bardziej elastyczne w kwestii argumentów – w czym są bardzo podobny do bloków.
przykład:
>> pr = Proc.new {|x| puts x}
=> #
>> pr.call(123)
123
=> nil
>>
Lambda
Lambdy w ruby w przeciwieństwie do obiektów proc podchodzą bardziej rygorystycznie do przekazywanych argumentów – o ile do obiektu proc można przekazać dowolną ilość argumentów, nawet jeżeli potrzebuje tylko dwóch, to w lambda sprawdza argumenty, a w przypadku braku – zgłasza błąd.
Przykład:
>> l = lambda {|x| puts x*x }
=> #
>> l.call
(irb):55:in `block in ': wrong number of arguments (given 0, exp
ected 1) (ArgumentError)
from (irb):56:in `'
(...)
>> l.call(10)
100
=> nil
>>
lambdy mają też alternatywną składnie definicji – bardziej podobną do funkcji anonimowych z javascript
>> l = -> (x) {puts x + x * 2}
=> #
>>
>> l.call(12)
36
Warto zwrócić uwagę, że w bloku dla lambdy nie podajemy już parametrów otoczonych znakami || – zamiast tego parametry są w nawiasach poprzedzających blok.
kluczowe różnice między proc a lambda
proc nie kontroluje przekazywanych argumentów, te które nie zostały przekazane traktuje jako nil, a nadmiarowe pomija.
lambda kontroluje przekazywanie argumentów, a w przypadku braku, lub nadmiaru zgłasza błąd.
zwracanie wartości przez return
w proc return kończy za równo proc, jak i metodę z której został wywołany.
lambda zwraca wartość do kontekstu z którego została wywołana.
przekazywanie proc i lambda jako argument w metodzie.
Jeżeli chcemy przekazać do metody obiekt typu proc – co obejmuje również lambdy, musimy poprzedzić nazwę argumentu znakiem &
należy go również użyć podczas przekazywania obiektu typu proc do zmiennej
przykład:
def my_method(&arg)
arg.call("jestem argumentem")
end
>> lb = -> (x) {puts x}
=> #
>> my_method(&lb)
jestem argumentem
=> nil
>>
przechwytywanie bloku do argumentu metody
Okazuje się, że jeżeli zdefiniujemy argument jako typ proc, oznaczając go znakiem & ruby automatycznie przypisze do tego argumentu przekazany do metody blok – czyniąc z niego obiekt proc.
powyższy przykład można skrócić do następującej postaci:
def my_method(&arg)
arg.call("jestem argumentem")
end
>> my_method {|x| puts x }
jestem argumentem
=> nil
>>
możemy również wywołać konkretną metodę powiązaną z argumentem który jest przekazywany do obiektu proc przez metodę
w tym celu należy podać nazwę metody jako symbol, poprzedzając wszystko znakiem &
w przykładzie wyświetlimy klasę argumentu przekazanego do obiektu proc
def my_method(&arg)
arg.call("jestem argumentem")
end
>> my_method(&:class)
=> String
>>
szczególnie przydaje się, kiedy chcemy dla każdego elementu danego zbioru wywołać jego jedną metodę
w przykładzie poniżej chcemy sprawdzić czy zbiór składa się z liczb nieparzystych:
>> [1, 2, 3, 4, 5].all?(&:odd?)
=> false
>>
zamiast
>> [1, 2, 3, 4, 5].all? {|x| x.odd?}
=> false
>>
Oczywiście która wersja jest bardziej czytelna pozostawiam już czytelnikowi do oceny – w ruby mamy wybór.
Podsumowanie
Bloki w ruby są jedną z ciekawszych konstrukcji jakie zapewnia nam ten język programowania.
Mając doświadczenia z innych języków programowania mogą się okazać na początku cokolwiek problematyczne, szybko jednak ich używanie staje się intuicyjne .
Niezbędnym jest wręcz dobre poznanie tegoż zagadnienia, biorąc pod uwagę jak powszechnie jest wykorzystywane w kodzie napisanym w ruby.
Na koniec kilka ciekawych przykładów z wykorzystaniem bloków
znajdowanie liczb pierwszych:
require 'prime'
(0..Float::INFINITY).lazy.select {|x| Prime.prime? x}.first(30).each {|i| puts i}
>
[2,
3,
5,
7,
11,
13,
17,
19,
23,
29,
31,
37,
41,
43,
47,
53,
59,
61,
67,
71,
73,
79,
konfiguracja routingu w ruby on rails
Rails.application.routes.draw do
# Strona główna
root 'home#index'
# Prosta trasa dla zasobu
resources :articles do
collection do
get 'search'
get 'archived'
end
member do
get 'preview'
post 'publish'
end
# Zagnieżdżone zasoby
resources :comments, only: [:create, :destroy] do
member do
patch 'approve'
patch 'reject'
end
collection do
get 'recent'
end
end
end
# Routing dla użytkowników
resources :users do
member do
get 'profile'
get 'dashboard'
end
# Zagnieżdżone zasoby dla ustawień użytkownika
resources :settings, only: [:index, :update] do
collection do
get 'privacy'
get 'notifications'
end
end
end
# Routing dla administratora z użyciem namespace
namespace :admin do
root 'dashboard#index'
resources :users do
member do
patch 'promote'
patch 'demote'
end
end
resources :articles do
collection do
get 'pending'
end
end
resources :comments, only: [:index, :destroy] do
collection do
get 'flagged'
end
end
end
# Routing dla API z wersjonowaniem
namespace :api do
namespace :v1 do
resources :articles, only: [:index, :show, :create, :update, :destroy] do
resources :comments, only: [:index, :create, :destroy]
end
resources :users, only: [:index, :show] do
member do
get 'profile'
end
end
end
end
# Routing dla webhooków
namespace :webhooks do
post 'payment', to: 'payments#receive'
post 'notifications', to: 'notifications#receive'
end
# Routing dla statycznych stron
get 'about', to: 'static#about'
get 'contact', to: 'static#contact'
get 'terms', to: 'static#terms'
get 'privacy', to: 'static#privacy'
# Nazwane ścieżki
get 'signup', to: 'users#new', as: 'signup'
get 'login', to: 'sessions#new', as: 'login'
post 'login', to: 'sessions#create'
delete 'logout', to: 'sessions#destroy', as: 'logout'
# przechwytywanie pozostałych tras jako 404 nie znaleziono
match '*path', to: 'application#not_found', via: :all
end