Kurs Ruby on Rails

Kurs Ruby on Rails – Lekcja 3 – Struktura aplikacji

Czego się nauczycie?

  • Tworzyć nową aplikację Ruby on Rails
  • Jaką strukturę ma aplikacja
    • MVC (Model-View-Controller)
    • Drzewko aplikacji
  • Poleceń w terminalu w tym:
    • Jak tworzyć modele
    • Jak tworzyć migracje
  • Łączyć modele asocjacjami
  • Walidować dane
  • Tworzyć zakresy

Niezbędnik kursu:

1. Tworzymy nową aplikację Ruby on Rails

Na początek pojęcia, jakimi się posługujemy: Ruby on Rails to framework, z którym będziemy pracować, dostarczony jest przez zainstalowanie gemu railsGem jest więc rozszerzeniem do języka Ruby. Gemy instaluje inny gem – bundler, czyli rozszerzenie do instalowania innych wtyczek. Do kursu potrzebny będzie dowolny edytor tekstowy, my używać będzie edytora atom.

$ cd katalog-projektu
$ gem install rails:6.1.4.4 bundler
$ rails	-h #pokaże nam dostępne	opcje podczas tworzenia nowej aplikacji
$ rails new .
$ atom .

Instalujemy gemy i sprawdzamy, czy działa

$ bundle

zainstaluje nam gemy, po wpisujemy

$ rails	server

albo

$ bundle exec rails server

aby wystartować serwer na porcie :3000

Convention over configuration

Jednym z filarów Ruby on Rails jest convention over configuration. Oznacza to tyle, że część konfiguracji jest zautomatyzowana, aby przyśpieszyć rozwój aplikacji oraz zmniejszyć ilość redundantnych decyzji związanych ze strukturą aplikacji. Ruby on Rails jest natomiast elastyczne w kwestii niepodążania za konwencjami.

2. Struktura aplikacji

W otwartym edytorze widać drzewko aplikacji, jednak zanim przejdziemy przez najistotniejsze dla tego kursu foldery, krótko objaśnimy czym jest MVC.

MVC (Model-View-Controller)

MVC, czyli model architektoniczny, gdzie:

M – model – reprezentacja obiektu, logika aplikacji

V – view – wyświetlanie GUI dla modeli

C – controller – sterowanie przepływem danych

Rola modelu

  • Reprezentacja danych
  • Reprezentacja asocjacji
  • Walidacja poprawności danych
  • Operacje na bazie danych

Rola kontrolera

  • Obsługuje żądanie generując odpowiedź
  • Pośredniczy pomiędzy widokami i modelami

Rola widoku

  • Reprezentacja danych

Drzewko aplikacji

app/assets - tu znajdują się pliki css oraz obrazki
app/channels - zawiera kanały dla WebSocketów
app/controllers	- kontrolery (mvc)
app/helpers - metody używane w widokach
app/javascript - pliki .js
app/jobs - logika do procesów asynchronicznych
app/mailers - klasy do wysyłki maili
app/models - modele (mvc)
app/views - widoki i layouty (mvc)
/config - pliki	konfiguracyjne
/config/routes - ścieżki w aplikacji
/db - migracje, seedy
Gemfile - plik z gemami
Gemfile.lock - autogenerowany plik z wersjami zainstalowanych gemów

2. Modele

Generowanie nowego modelu

Model, a tabela

  • Model dziedziczy z klasy ApplicationRecord, dzięki której może czerpać swoje atrybuty z tabeli w bazie danych
  • Nazywamy tabelę w bazie danych wg konwencji, czyli powinna mieć nazwę, która jest liczbą mnogą nazwy modelu (User -> users). Nie jest wymagane łączenie klasy z tabelą, dzieje się to automatycznie.
  • Strukturą bazy danych zarządzamy przez migracje

Przede wszystkim, musimy stworzyć naszą bazę danych poprzez

$ rails db:create

Do tworzenia struktury aplikacji można używać tzw. scaffoldingu, czyli komend, które opisują strukturę w sposób zrozumiały dla generatora, który to wygeneruje za nas odpowiednie pliki w drzewku aplikacji.

Struktura takiej komendy wygląda tak:

$ rails	generate model	NazwaModelu nazwapola:typ nazwapola:typ

a więc, generując model User używamy:

$ rails generate model User name:string

Powyższa komenda wygeneruje migracje dla tabeli users, model User oraz pliki do testów

  invoke active_record
  create db/migrate/20210726055700_create_users.rb
  create app/models/user.rb
  invoke test_unit
  create test/models/user_test.rb
  create test/fixtures/users.yml

Po takiej komendzie zmieniamy stan naszej bazy poprzez:

$ rails db:migrate

Oczywiście scaffoldingu nie trzeba używać, jak widać generuje on kilka plików, które niekoniecznie muszą być w przyszłości potrzebne. Nic nie stoi na przeszkodzie, aby samemu stworzyć nowy plik w modelach i zainicjalizować w nim klasę

class User < ApplicationRecord
end

Następnie tworzymy samodzielną migrację poprzez

rails generate migration createUsers

która to wygeneruje nam pustą migrację:

class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
    end
  end
end

Aby osiągnąć ten sam efekt co przy wywołaniu pierwszej komendy, uzupełniamy migrację poprzez dodanie kilku linijek kodu do ciała metody change

class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :name

      t.timestamps # doda nam pola created_at oraz updated_at które automatycznie się będą wypełniać
    end
  end
end

Każda tabela automatycznie tworzy sobie również pole ID. Po migracji danych, czyli

rails db:migrate

możemy spojrzeć w db/schema.rb, aby zobaczyć jak aktualnie wygląda nasza baza danych.

3. ActiveRecord

Manipulowanie obiektami poprzez ORM

Podobnie jak w przypadku irb z poprzedniej lekcji, mamy do dyspozycji konsolę, którą włączamy poprzez

$ rails console

lub

$ bundle exec rails console

Otworzy nam to konsolę, ale również ładuje całą aplikację, dając dostęp do wszystkich klas.

irb(main):001:0> user = User.new(name: 'Jan Kowalski') # inicjalizuje obiekt
irb(main):002:0> user.save # zapisuje obiekt do bazy
irb(main):003:0> User.create(name: 'Jan Kowalski') # inicjalizuje i jednocześnie próbuje zapisać obiekt do bazy
irb(main):004:0> user = User.create(name: 'Janina Kowalska')
irb(main):005:0> user.update(name: 'Katarzyna Kowalska') # aktualizuje obiekt i stan w bazie
irb(main):006:0> user.destroy # usuwa obiekt z bazy danych

Najczęstsze operacje do odczytywania obiektów z bazy

irb(main):001:0> User.find(1) # znajdzie w bazie obiekt User, który ma ID 1
irb(main):002:0> User.find_by(name: 'Jan Kowalski') # znajdzie w bazie obiekt User, który posiada podany name
irb(main):003:0> User.last # odczyta z bazy ostatnio stworzony obiekt User
irb(main):004:0> User.last(5) # odczyta z bazy 5 ostatnio stworzonych obiektów User
irb(main):005:0> User.first # odczyta z bazy pierwszy stworzony obiekt User
irb(main):006:0> User.all # odczyta z bazy wszystkie stworzone obiekty User
irb(main):007:0> User.count # odczyta z bazy wszystkie stworzone obiekty User i zwróci ich liczbę
irb(main):008:0> User.where(name: 'Jan Kowalski') # odczyta z bazy wszystkie stworzone obiekty User, które posiadają podany name

4. Asocjacje (relacje)

Modele mogą się łączyć za pomocą relacji, które odzwierciedlają zależności logiki biznesowej

Relacje jakie mamy do dyspozycji to:

  • belongs_to
  • has_one
  • has_many
  • has_many :through
  • has_one :through
  • has_and_belongs_to_many

Chcąc powiązać User z Post relacją jeden do wielu, wpisujemy

$ rails	generate model Post user:belongs_to title:string body:string

Jak poprzednio, stworzy nam to model Post oraz migrację, w której przypisze obiektom Post ID Usera. W modelu Post zobaczymy też wspomnianą relację.

class Post < ApplicationRecord
  belongs_to :user
end

Należy pamiętać żeby uzupełnić relacje w istniejącym już modelu User:

class User < ApplicationRecord
  has_many :posts
end

Teraz możemy dodać posty naszemu użytkownikowi. Jeśli poprzednio go usunęliśmy, to tworzymy go znów

user = User.create(name: 'Jan Kowalski')

jednocześnie przypisaliśmy go do zmiennej user.

Post.create(user_id: user.id, title: 'Witam!', body: 'Nowy Post Jan Kowalski')

Po czym możemy odczytać z bazy posty, które są przypisane do naszego użytkownika przez:

user.reload.posts

5. Walidacje

Aby czuwać nad spójnością danych w bazie, używamy walidacji, które sprawdzają stan obiektu zanim zostanie wysłany do bazy danych. Najprostszą i najszybszą drogą są walidacje ActiveRecord’owe. W modelu User dodajemy kolejne linijki kodu, które będą sprawdzać, czy jest podany name oraz, czy jest w odpowiednim formacie

class User < ActiveRecord::Base
  has_many: :posts

  validates :name, presence: true
  validates :name, format: /[A-Z][a-z]*/
end

Dla modelu Post dodajemy

class Post < ActiveRecord::Base
  belongs_to: :user

  validates :title, :body, presence: true
end

Przy tworzeniu postu sprawdzimy, czy podany jest tytuł, treść postu oraz, czy jest podany user_id, co jest walidowane automatycznie przez relację belongs_to.

6. Zakresy

W klasie modelu możemy tworzyć zakresy, które wywołane zwrócą odpowiednie rekordy z bazy; jest to dobra praktyka jeśli często korzystamy z konkretnego zapytania bazodanowego. Do klasy User dodajemy

scope :created_today, -> { where("created_at > ?", Date.today.beginning_of_day) }

sprawdź w konsoli, jak to się zachowa

User.created_today

Możemy też tworzyć zakresy przyjmujące parametry, czy nawet łączyć je w “methods chain”, czyli wywołać jedną po drugiej. Jeśli dodamy kolejny zakres

scope :with_name, ->(name) { where("name LIKE ?", "#{name}%") }

możemy użyć:

User.created_today.with_name("Jan")

7. Praca własna

1. Stworzyć w konsoli 5 obiektów Post z różnymi wartościami

2. Stworzyć model Comment z polem content (string), timestampami, który będzie miał relację do Post (post has_many :comments) i relację do User

3. Dodać walidację na pola content (presence) oraz walidację na ilość znaków (max. 140 w obu)

4. Napisać pętlę, która przeiteruje się przez Comment i wyrzuci na konsolę ich autorów. (można użyć puts)

5. Napisać query, które wyciągnie z bazy wszystkie obiekty Post użytkownika o podanym name.

6. (trudne) Napisać query, które wyciągnie z bazy wszystkie obiekty Comment użytkownika o podanym name. (tip: przeczytać o active_record_querying, w szczególności joins w linkach poniżej)

6. (trudne) Napisać query, które wyciągnie z bazy wszystkie obiekty Comment użytkownika o podanym name i podanym tytule posta.

7. (trudne) Stworzyć jeszcze jeden obiekt Comment, którego pole created_at jest ustawione na datę jutrzejszą. Napisać query, które wyciągnie z bazy wszystkie wiadomości, które zostały stworzone wcześniej niż 5 minut temu i mają więcej niż 100 znaków. (tip: sprawdź jak działa Date.today + 120.minutes i dostosuj do zadania)

Przydatne linki

http://guides.rubyonrails.org/ 
http://api.rubyonrails.org/ 
http://guides.rubyonrails.org/migrations.html 
https://guides.rubyonrails.org/active_record_validations.html 
https://guides.rubyonrails.org/association_basics.html 
https://guides.rubyonrails.org/active_record_validations.
html https://guides.rubyonrails.org/active_record_basics.html 
https://guides.rubyonrails.org/active_record_querying.html

Przeczytaj również o...

Dołącz do naszego zespołu!

Zobacz oferty pracy