Skip to content

Commit

Permalink
[RU] Add Russian translation to /specifics/ecto.md
Browse files Browse the repository at this point in the history
  • Loading branch information
Tetiana Hogg committed Apr 18, 2016
1 parent 2a08843 commit 763c0c4
Show file tree
Hide file tree
Showing 2 changed files with 345 additions and 0 deletions.
3 changes: 3 additions & 0 deletions GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ Class names and proper nouns do not get translated.
- function clauses - объявления функций
- guard - ограничитель
- guard operator - ограничивающий оператор
- changeset - набор изменений
- keyword list - список с ключами
- match operator - оператор сопоставления
- pin operator - фиксирующий оператор
- pipe operator - оператор конвейера
- sigil - строковая метка
- schema - структура
- tuple - кортеж

#### <a name="russian-cs-terms"></a> CS terms
Expand Down Expand Up @@ -59,6 +61,7 @@ Class names and proper nouns do not get translated.
- signature - сигнатура (функции)
- standard library - стандартная библиотека
- subtraction - вычитание
- supervision tree - дерево надзора
- supervisor - супервизор
- tail - хвост
- worker - рабочий процесс
Expand Down
342 changes: 342 additions & 0 deletions ru/lessons/specifics/ecto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
---
layout: page
title: Ecto
category: specifics
order: 2
lang: ru
---

Ecto - официальный проект от создателей Elixir. Это оболочка, которая предоставляет возможность коммуникации с базой данных. Ecto позволяет создавать миграции, объявлять модели, вносить и обновлять данные, а также посылать запросы к ним.

## Содержание

- [Установка](#section-1)
- [Репозиторий](#section-2)
- [Супервизор](#section-3)
- [Настройка](#section-4)
- [Mix задачи](#mix-)
- [Миграции](#section-5)
- [Модели](#section-6)
- [Запросы](#section-7)
- [Основы](#section-8)
- [Количество](#section-9)
- [Группировка](#section-10)
- [Сортировка](#section-11)
- [Объединение](#section-12)
- [Фрагменты](#section-13)
- [Набор изменений](#section-14)

## Установка

Для начала необходимо добавить Ecto и адаптер для базы данных в файл `mix.exs`, который находится в нашем проекте. Список поддерживаемых адаптеров для баз данных можно найти в [Usage](https://github.com/elixir-lang/ecto/blob/master/README.md#usage) секции README для Ecto. В примере мы используем PostgreSQL:

```elixir
defp deps do
[{:ecto, "~> 1.0"},
{:postgrex, ">= 0.0.0"}]
end
```

После этого добавим Ecto и адаптер в список приложений.

```elixir
def application do
[applications: [:ecto, :postgrex]]
end
```

### Репозиторий

Создадим репозиторий для проекта - оболочку для базы данных. Для этого выполним команду `mix ecto.gen.repo`. Репозиторий находится в `lib/<project name>/repo.ex`.

```elixir
defmodule ExampleApp.Repo do
use Ecto.Repo,
otp_app: :example_app
end
```

### Супервизор

После создания репозитория, необходимо создать дерево надзора. Файл находится в `lib/<project name>.ex`.

Важно, чтоб мы настроили репозиторий, как супервизор с помощью `supervisor/3` метода, а не `worker/3`. Если создать приложение с флагом `--sup`, большинство нужного кода для нас уже будет сгенерировано.

```elixir
defmodule ExampleApp.App do
use Application

def start(_type, _args) do
import Supervisor.Spec

children = [
supervisor(ExampleApp.Repo, [])
]

opts = [strategy: :one_for_one, name: ExampleApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
Чтоб узнать больше о супервизорах советуем посетить урок [Cупервизоры](/lessons/advanced/otp-supervisors).

### Настройка

Чтоб настроить Ecto, необходимо добавить конфигурацию в файл `config/config.exs`. В конфигурации необходимо указать репозиторий, адаптер, базу данных и информацию об аккаунте.

```elixir
config :example_app, ExampleApp.Repo,
adapter: Ecto.Adapters.Postgres,
database: "example_app",
username: "postgres",
password: "postgres",
hostname: "localhost"
```

## Mix задачи

Ecto включает в себя `mix` задачи для работы с базой данных:

```shell
mix ecto.create # Создать хранилище для репозитория.
mix ecto.drop # Удалить хранилище для репозитория.
mix ecto.gen.migration # Сгенерировать новую миграцию для репозитория.
mix ecto.gen.repo # Сгенерировать новый репозиторий.
mix ecto.migrate # Запустить миграции для репозитория.
mix ecto.rollback # Откатить миграции из репозитория.
```

## Миграции

Лучший способ создать миграцию - использовать команду mix ecto.gen.migration <название_миграции>. Это похоже на создание миграций в ActiveRecord.

Давайте рассмотрим, как выглядит миграция для таблицы `users`:

```elixir
defmodule ExampleApp.Repo.Migrations.CreateUser do
use Ecto.Migration

def change do
create table(:users) do
add :username, :string, unique: true
add :encrypted_password, :string, null: false
add :email, :string
add :confirmed, :boolean, default: false

timestamps
end

create unique_index(:users, [:username], name: :unique_usernames)
end
end
```

Ecto по умолчанию создает первичный автоинкрементируемый ключ под названием id. Также по умолчанию используется `change/0` callback, но для большего контроля можно использовать `up/0` и `down/0`.

Вы уже могли догадаться, что при добавлении `timestamps` к миграции у Вас появляется возможность создать и управлять полями `created_at` и `updated_at`.

Следующим шагом будет запуск только что созданной миграции с помощью команды `mix ecto.migrate`.

Дополнительную информацию о миграциях Вы можете найти в главе [Ecto Миграции](http:https://hexdocs.pm/ecto/Ecto.Migration.html#content).

## Модели

Теперь, имея миграцию, можно двигаться дальше, к модели. Модели определяют структуру(schema), вспомогательныe методы(helper methods) и набор изменений. Набор изменений объяснен в следующей главе.

Рассмотрим как выглядит модель для нашей миграции:

```elixir
defmodule ExampleApp.User do
use Ecto.Schema
import Ecto.Changeset

schema "users" do
field :username, :string
field :encrypted_password, :string
field :email, :string
field :confirmed, :boolean, default: false
field :password, :string, virtual: true
field :password_confirmation, :string, virtual: true

timestamps
end

@required_fields ~w(username encrypted_password email)
@optional_fields ~w()

def changeset(user, params \\ :empty) do
user
|> cast(params, @required_fields, @optional_fields)
|> unique_constraint(:username)
end
end
```
Структура, заданная в нашей модели, очень похожа на код в самой миграции. Дополнительно к полям базы данных мы также добавили 2 виртуальных поля. Виртуальные поля не сохраняются в базу данных, но могут быть полезными для валидаций. Примеры виртуальных полей можно найти здесь [Набор изменений](#section-15).

## Запросы

Перед тем как посылать запросы к репозиторию, необходимо импортировать Query API. Для начала нам понадобится только один метод из библиотеки `from/2`:

```elixir
import Ecto.Query, only: [from: 2]
```

Официальную документацию можно найти перейдя по ссылке [Ecto запросы](http:https://hexdocs.pm/ecto/Ecto.Query.html).

### Основы

Ecto предоставляет мощный язык запросов. Запрос для поиска всех пользователей, аккаунты которых приняты, будет выглядеть так:

```elixir
alias ExampleApp.{Repo,User}

query = from u in User,
where: u.confirmed == true,
select: u.username

Repo.all(query)
```

Кроме `all/2`, Repo предоставляет различные callback-функции, такие как `one/2`, `get/3`, `insert/2`, и `delete/2`. Список всех функций можно найти в официальной документации [Ecto.Repo#callbacks](http:https://hexdocs.pm/ecto/Ecto.Repo.html#callbacks).

### Количество

```elixir
query = from u in User,
where: u.confirmed == true,
select: count(u.id)
```

### Группировка

Для того, чтобы сгруппировать всех пользователей по статусу, можно добавить опцию `group_by`:

```elixir
query = from u in User,
group_by: u.confirmed,
select: [u.confirmed, count(u.id)]

Repo.all(query)
```

### Сортировка

Сортировка пользователей по дате создания записи в базе данных:

```elixir
query = from u in User,
order_by: u.inserted_at,
select: [u.username, u.inserted_at]

Repo.all(query)
```

Сортировка по убыванию(`DESC`):

```elixir
query = from u in User,
order_by: [desc: u.inserted_at],
select: [u.username, u.inserted_at]
```

### Объединение

Допустим, каждый пользователь имеет свой профайл. Давайте найдем все профайлы утвержденных пользователей:

```elixir
query = from p in Profile,
join: u in assoc(profile, :user),
where: u.confirmed == true
```

### Фрагменты

Иногда, когда нам необходимы специфические функции базы данных, Query API недостаточно. В таких случаях следует использовать функцию `fragment/1`:

```elixir
query = from u in User,
where: fragment("downcase(?)", u.username) == ^username
select: u
```

Примеры построения различных запросов можно найти здесь [Ecto.Query.API](http:https://hexdocs.pm/ecto/Ecto.Query.API.html).

## Набор изменений

В предыдущем параграфе мы увидели как извлечь необходимую информацию из базы данных, а что насчет добавления и обновления записей? Для этого нам необходим набор изменений (Changesets).

Набор изменений выполняет функции фильтрации, валидации, и поддержки ограничений при изменении модели.

Давайте используем пример набора изменений для создания аккаунта для пользователя. Для начала необходимо обновить модель:

```elixir
defmodule ExampleApp.User do
use Ecto.Schema
import Ecto.Changeset
import Comeonin.Bcrypt, only: [hashpwsalt: 1]

schema "users" do
field :username, :string
field :encrypted_password, :string
field :email, :string
field :confirmed, :boolean, default: false
field :password, :string, virtual: true
field :password_confirmation, :string, virtual: true

timestamps
end

@required_fields ~w(username email password password_confirmation)
@optional_fields ~w()

def changeset(user, params \\ :empty) do
user
|> cast(params, @required_fields, @optional_fields)
|> validate_length(:password, min: 8)
|> validate_password_confirmation()
|> unique_constraint(:username, name: :email)
|> put_change(:encrypted_password, hashpwsalt(params[:password]))
end

defp validate_password_confirmation(changeset) do
case get_change(changeset, :password_confirmation) do
nil ->
password_mismatch_error(changeset)
confirmation ->
password = get_field(changeset, :password)
if confirmation == password, do: changeset, else: password_incorrect_error(changeset)
end
end

defp password_mismatch_error(changeset) do
add_error(changeset, :password_confirmation, "Passwords does not match")
end

defp password_incorrect_error(changeset) do
add_error(changeset, :password, "is not valid")
end
end
```

Таким образом, мы улучшили функцию `changeset/2`, а также добавили три новые вспомогательные функции: `validate_password_confirmation/1`, `password_mismatch_error/1` и `password_incorrect_error/1`.

По названию можно догадаться, что функция `changeset/2` создает новый набор изменений. В теле функции используем функцию `cast/4`, которая трансформирует данные параметры в набор изменений, который состоит из обязательных и необязательных полей. Далее происходит валидация длины пароля, находящегося в наборе изменений. После этого, используя созданную нами функцию, мы можем проверить, что подтверждение пароля является таким же как и сам пароль, а также проверить уникальность имени пользователя. В конце, обновим пароль в базе данных. Для этого используем функцию `put_change/3`, которая обновит значение в наборе изменений.

Использование `User.changeset/2` довольно просто:

```elixir
alias ExampleApp.{User,Repo}

pw = "passwords should be hard"
changeset = User.changeset(%User{}, %{username: "doomspork",
email: "[email protected]",
password: pw,
password_confirmation: pw})

case Repo.insert(changeset) do
{:ok, model} -> # Inserted with success
{:error, changeset} -> # Something went wrong
end
```

И это все! Теперь Вы готовы обновлять записи в базе данных.

0 comments on commit 763c0c4

Please sign in to comment.