Bloki w języku Ruby

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


Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *