Podstawy

Ecto jest oficjalnym projektem zespołu Elixira, zapewniającym obsługę baz danych wraz z odpowiednim, zintegrowanym językiem. Za pomocą Ecto możemy tworzyć migracje, definiować schematy, wstawiać, aktualizować oraz wyszukiwać rekordy w bazie.

Adaptery

Ecto wspiera różne bazy danych poprzez użycie adapterów. Kilka przykładów spośród nich to:

W tej lekcji skonfigurujemy Ecto tak, by używać go z adapterem PostgreSQL.

Przygotowanie

W trakcie tej lekcji omówimy trzy części Ecto:

Zacznijmy od stworzenia aplikacji z drzewem nadzoru:

$ mix new friends --sup
$ cd friends

Dodajmy biblioteki ecto_sql i postgrex jako zależności w pliku mix.exs:

  defp deps do
    [
      {:ecto_sql, "~> 3.2"},
      {:postgrex, "~> 0.15"}
    ]
  end

A na koniec pobierzmy zależności:

$ mix deps.get

Tworzenie repozytorium

Repozytorium w Ecto zapewnia interfejs do miejsca, w którym trzymamy dane, na przykład do naszej postgresowej bazy. Wszelka komunikacja z bazą danych będzie się odbywać właśnie za pośrednictwem repozytorium.

Stwórzmy je zatem:

$ mix ecto.gen.repo -r Friends.Repo

Powyższa komenda wygeneruje konfigurację w pliku config/config.exs, konieczną do łączenia się z bazą danych. Tak wygląda plik konfiguracyjny dla naszej aplikacji Friends:

config :friends, Friends.Repo,
  database: "friends_repo",
  username: "postgres",
  password: "",
  hostname: "localhost"

W ten sposób definiujemy, w jaki sposób Ecto będzie się łączyć z bazą. Możesz potrzebować zmienić konfigurację swojej bazy lub powyższe dane tak, by nazwa użytkownika bazodanowego i hasło w konfiguracji Twojej aplikacji zgadzały się ze stanem faktycznym w bazie.

Uruchomiona przez nas wcześniej komenda tworzy również moduł Friends.Repo w pliku lib/friends/repo.ex:

defmodule Friends.Repo do
  use Ecto.Repo, 
    otp_app: :friends,
    adapter: Ecto.Adapters.Postgres
end

Będziemy używać modułu Friend.Repo do odpytywania bazy danych. Modułowi temu mówimy również, że informacji konfiguracyjnych powinien szukać w elixirowej aplikacji :friends oraz że wybraliśmy adapter Ecto.Adapters.Postgres.

Następnie ustawmy Friends.Repo jako nadzorcę w drzewie nadzoru naszej aplikacji w pliku lib/friends/application.ex. Dzięki temu proces Ecto będzie startowany podczas uruchamiania naszej aplikacji.

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      Friends.Repo,
    ]

  ...

Teraz powinniśmy dodać następującą linię do naszego pliku config/config.exs:

config :friends, ecto_repos: [Friends.Repo]

Pozwoli to naszej aplikacji uruchamiać polecenia Mixa z poziomu wiersza poleceń.

Skończyliśmy konfigurowanie repozytorium! Teraz możemy stworzyć bazę danych w Postgresie za pomocą następującej komendy:

$ mix ecto.create

Ecto będzie używać informacji z pliku config/config.exs, by ustalić, jak łączyć się z Postgresem i jaką nazwę nadać bazie danych.

W razie wystąpienia jakichkolwiek błędów upewnij się, że dane konfiguracyjne są poprawne i że Twoja instancja Postgresa jest uruchomiona.

Migracje

Abyśmy mogli tworzyć i modyfikować tabele w postgresowej bazie danych, Ecto zapewnia nam odpowiednie narzędzie — migracje. Każda z migracji określa zestaw akcji do wykonania w naszej bazie, takich jak tworzenie i modyfikowanie tabel.

Jako że nasza baza nie ma jeszcze żadnych tabel, będziemy musieli stworzyć migrację, by taką tabelę dodać. Konwencja Ecto mówi, że tabele powinny mieć nazwy w liczbie mnogiej, zatem stwórzmy tabelę people (ludzie) — i tu zacznijmy naszą pracę z migracjami.

Najlepszym sposobem na utworzenie migracji jest użycie zadania Mixa ecto.gen.migration <name>, więc w tym przypadku powinniśmy uruchomić następujące polecenie:

$ mix ecto.gen.migration create_people

Wygeneruje ono nowy plik w folderze priv/repo/migrations, zawierający w nazwie datę i czas. Jeśli przejdziemy do wymienionego wyżej katalogu i otworzymy plik z migracją, zobaczymy mniej więcej coś takiego:

defmodule Friends.Repo.Migrations.CreatePeople do
  use Ecto.Migration

  def change do

  end
end

Zacznijmy od zmodyfikowania funkcji change/0, aby stworzyć nową tabelę people z kolumnami name (imię) i age (wiek):

defmodule Friends.Repo.Migrations.CreatePeople do
  use Ecto.Migration

  def change do
    create table(:people) do
      add :name, :string, null: false
      add :age, :integer, default: 0
    end
  end
end

Jak możesz zauważyć, zdefiniowaliśmy także typy danych dla tworzonych przez nas kolumn. Dodaliśmy ponadto null: false i default: 0 jako opcje.

Wróćmy do wiersza poleceń i uruchommy naszą migrację:

$ mix ecto.migrate

Schematy

Teraz, gdy stworzyliśmy naszą pierwszą tabelę, musimy powiedzieć Ecto nieco więcej na jej temat, co częściowo zrobimy poprzez schematy (ang. schema). Schemat jest modułem definiującym mapowanie do pól w bazie danych.

Podczas gdy w nazwach tabel Ecto faworyzuje liczbę mnogą, moduł nazywany jest zwykle w liczbie pojedynczej, zatem utwórzmy schemat Person (osoba), który będzie towarzyszył naszej tabeli.

Stwórzmy nowy schemat w pliku lib/friends/person.ex:

defmodule Friends.Person do
  use Ecto.Schema

  schema "people" do
    field :name, :string
    field :age, :integer, default: 0
  end
end

Możemy tu zauważyć, że moduł Friends.Person mówi Ecto, że ten schemat odnosi się do tabeli people, która zawiera dwie kolumny: name — z typem danych string, a także age — liczbę całkowitą z zerem jako wartością domyślną.

Rzućmy okiem na nasz schemat, otwierając iex -S mix i tworząc nową osobę:

iex> %Friends.Person{}
%Friends.Person{age: 0, name: nil}

Zgodnie z oczekiwaniami, jako wynik otrzymaliśmy nową strukturę Person z wartością domyślną w polu age. Teraz stwórzmy „prawdziwą” osobę:

iex> person = %Friends.Person{name: "Tom", age: 11}
%Friends.Person{age: 11, name: "Tom"}

Ponieważ schematy są po prostu strukturami, możemy wchodzić z nimi w interakcje tak, jak do tego przywykliśmy:

iex> person.name
"Tom"
iex> Map.get(person, :name)
"Tom"
iex> %{name: name} = person
%Friends.Person{age: 11, name: "Tom"}
iex> name
"Tom"

Możemy również zmieniać nasze schematy dokładnie tak, jak w dowolnej mapie czy strukturze w Elixirze:

iex> %{person | age: 18}
%Friends.Person{age: 18, name: "Tom"}
iex> Map.put(person, :name, "Jerry")
%Friends.Person{age: 18, name: "Jerry"}

W naszej kolejnej lekcji omówimy zestawy zmian — changesety — i zobaczymy, w jaki sposób możemy walidować zmiany w danych i wreszcie jak zapisać je w naszej bazie.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!