Начало работы
Исходный кодЭто руководство — введение в Ecto, обёртку для баз данных и генератор запросов для Elixir. Ecto предоставляет стандартизированный API и набор абстракций для взаимодействия с базами данных разных типов, поэтому разработчики на Elixir могут запрашивать данные из используемой ими базы с помощью схожих конструкций.
В этом руководстве мы изучим основы Ecto: создание, чтение, обновление и удаление записей в базе данных PostgreSQL. Если вы хотите ознакомиться с кодом из этого руководства, он доступен в ecto/examples/friends на GitHub.
Для работы с этим руководством необходимо заранее установить PostgreSQL.
Добавление Ecto в приложение
Для начала сгенерируем новое приложение Elixir, выполнив эту команду:
mix new friends --sup
Параметр --sup гарантирует, что в приложении будет дерево супервизии, которое понадобится нам для работы с Ecto чуть позже.
Чтобы добавить Ecto в приложение, нужно выполнить несколько шагов. Сначала добавим Ecto и драйвер Postgrex в файл mix.exs, изменив определение deps в этом файле следующим образом:
defp deps do
[
{:ecto_sql, "~> 3.0"},
{:postgrex, ">= 0.0.0"}
]
end
Ecto предоставляет общий API для запросов, но нам также нужно установить драйвер Postgrex, поскольку именно с его помощью Ecto общается с базой данных PostgreSQL на понятном ей языке. Ecto обращается к собственному модулю Ecto.Adapters.Postgres, который, в свою очередь, использует пакет postgrex для взаимодействия с PostgreSQL.
Чтобы установить эти зависимости, выполним команду:
mix deps.get
Приложение Postgrex будет получать запросы от Ecto и выполнять их в нашей базе данных. Без этого шага мы не смогли бы выполнять запросы.
Первые два шага выполнены: мы добавили Ecto и Postgrex в качестве зависимостей приложения. Теперь обновим файл .formatter.exs, чтобы правила Ecto применялись при работе с mix format:
[
# Add this line to enable Ecto formatter rules
import_deps: [:ecto, :ecto_sql],
# Default Elixir project rules
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
Теперь нужно настроить Ecto, чтобы из кода приложения можно было выполнять действия с базой данных. Настроить его можно с помощью этой команды:
mix ecto.gen.repo -r Friends.Repo
Эта команда создаст конфигурацию, необходимую для подключения к базе данных. Первая часть конфигурации находится в config/config.exs:
config :friends, Friends.Repo, database: "friends", username: "user", password: "pass", hostname: "localhost"
ПРИМЕЧАНИЕ: Возможно, ваша база данных PostgreSQL настроена так, что
- для подключения не требуются имя пользователя и пароль. Если приведённая выше конфигурация не работает, попробуйте удалить поля имени пользователя и пароля или задать для обоих значение "postgres".
- она работает на нестандартном порту. Порт по умолчанию —
5432. Указать нужный порт можно, добавив его в конфигурацию, например:port: 15432.
Эта конфигурация задаёт параметры подключения Ecto к нашей базе данных с именем "friends". В частности, она настраивает репозиторий ("repo"). Дополнительную информацию о Ecto.Repo можно найти в документации.
Модуль Friends.Repo определяется в lib/friends/repo.ex командой mix ecto.gen.repo:
defmodule Friends.Repo do
use Ecto.Repo,
otp_app: :friends,
adapter: Ecto.Adapters.Postgres
end
Вскоре мы будем использовать этот модуль для запросов к базе данных. Он использует модуль Ecto.Repo, а параметр otp_app указывает Ecto, в каком приложении Elixir искать конфигурацию базы данных. В данном случае мы указали приложение :friends, в котором Ecto найдёт эту конфигурацию, поэтому Ecto будет использовать конфигурацию, заданную в config/config.exs. Наконец, мы указываем, что база данных :adapter — это Postgres.
Наконец, Friends.Repo необходимо запустить в дереве супервизии приложения. Это можно сделать в lib/friends/application.ex внутри функции start/2:
def start(_type, _args) do
children = [
Friends.Repo,
]
...
Этот фрагмент конфигурации запускает процесс Ecto, который принимает и выполняет запросы нашего приложения. Без него мы вообще не смогли бы обращаться к базе данных!
Нам осталось добавить последний фрагмент конфигурации вручную, поскольку генератор его не добавляет. Добавьте эту строку под конфигурацией в config/config.exs:
config :friends, ecto_repos: [Friends.Repo]
Так приложение узнает о репозитории, что вскоре позволит нам выполнять такие команды, как mix ecto.create.
Теперь приложение настроено и может выполнять запросы к базе данных. Давайте создадим базу данных, добавим в неё таблицу и выполним несколько запросов.
Настройка базы данных
Чтобы выполнять запросы к базе данных, она сначала должна существовать. Создать базу данных можно с помощью этой команды:
mix ecto.create
Если база данных создана успешно, вы увидите такое сообщение:
The database for Friends.Repo has been created.
ПРИМЕЧАНИЕ: Если вы получили ошибку, попробуйте изменить конфигурацию в config/config.exs: возможно, ошибка связана с аутентификацией.
Сама по себе база данных не позволяет выполнять много запросов, поэтому нам нужно создать в ней таблицу. Для этого мы воспользуемся так называемой миграцией. Если вы работали с Active Record (или аналогичным инструментом), то уже встречались с миграциями. Миграция — это отдельный шаг в процессе создания базы данных.
Создадим миграцию с помощью этой команды:
mix ecto.gen.migration create_people
Эта команда создаст новый файл миграции в priv/repo/migrations. По умолчанию он будет пустым:
defmodule Friends.Repo.Migrations.CreatePeople do use Ecto.Migration def change do end end
Добавим в миграцию код для создания новой таблицы с именем "people" и несколькими столбцами:
defmodule Friends.Repo.Migrations.CreatePeople do
use Ecto.Migration
def change do
create table(:people) do
add :first_name, :string
add :last_name, :string
add :age, :integer
end
end
end
Этот код указывает Ecto создать новую таблицу people и добавить в неё три поля: first_name, last_name и age. Типы этих полей — string и integer. (Различные типы, поддерживаемые Ecto, описаны в документации Ecto.Schema.)
ПРИМЕЧАНИЕ: В базах данных Ecto для таблиц принято использовать имена во множественном числе.
Чтобы выполнить миграцию и создать в нашей базе данных таблицу people, выполним эту команду:
mix ecto.migrate
Если мы обнаружим ошибку в миграции, можно выполнить mix ecto.rollback, чтобы отменить её изменения. Затем мы сможем исправить миграцию и снова выполнить mix ecto.migrate. Если выполнить mix ecto.rollback сейчас, созданная нами таблица будет удалена.
Теперь в базе данных есть таблица. Следующий шаг — создать схему.
Создание схемы
Схема — это представление данных из базы данных на Elixir. Обычно схемы связаны с таблицей базы данных, но их также можно связать с представлением базы данных.
Создадим схему в приложении в файле lib/friends/person.ex:
defmodule Friends.Person do
use Ecto.Schema
schema "people" do
field :first_name, :string
field :last_name, :string
field :age, :integer
end
end
Этот код задаёт схему базы данных, с которой будет сопоставлена наша схема. В данном случае мы сообщаем Ecto, что схема Friends.Person сопоставляется с таблицей people в базе данных, а также с полями first_name, last_name и age в этой таблице. Второй аргумент, переданный в field, указывает Ecto, как представить данные из базы данных в нашей схеме.
Мы назвали схему Person, поскольку в Ecto для схем принято использовать имена в единственном числе.
Можно поэкспериментировать с этой схемой в сеансе IEx: запустите его с помощью iex -S mix и выполните в нём следующий код:
person = %Friends.Person{}
Этот код создаст новую структуру Friends.Person, в которой для всех полей будут заданы значения nil. Задать значения полей можно, создав новую структуру:
person = %Friends.Person{age: 28}
Или с помощью такого синтаксиса:
person = %{person | age: 28}
Получить значения можно с помощью этого синтаксиса:
person.age # => 28
Давайте посмотрим, как добавить данные в базу данных.
Добавление данных
Добавить новую запись в таблицу people можно с помощью этого кода:
person = %Friends.Person{}
Friends.Repo.insert(person)
Чтобы добавить данные в базу данных, мы вызываем insert для Friends.Repo — модуля, который использует Ecto для взаимодействия с базой данных. Эта функция сообщает Ecto, что мы хотим добавить новую запись Friends.Person в базу данных, соответствующую Friends.Repo. Структура person представляет данные, которые мы хотим добавить в базу.
В случае успешного добавления вернётся кортеж, например:
{:ok,
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: nil,
first_name: nil, id: 1, last_name: nil}}
Атом :ok можно использовать для сопоставления с образцом, чтобы проверить, что добавление прошло успешно. Например, добавление может завершиться неудачей, если в самой базе данных задано ограничение. Допустим, для поля email задано ограничение уникальности, поэтому один адрес электронной почты можно использовать только для одной записи человека. В таком случае добавление завершится неудачей.
Можно сопоставить кортеж с образцом, чтобы получить ссылку на добавленную в базу данных запись:
{:ok, person} = Friends.Repo.insert person
Проверка изменений
В Ecto можно проверять изменения до их сохранения в базе данных. Например, можно потребовать, чтобы у человека были указаны имя и фамилия, прежде чем добавлять запись в базу данных. Для этого в Ecto предусмотрены changeset.
Добавим changeset в модуль Friends.Person в файле lib/friends/person.ex:
def changeset(person, params \\ %{}) do
person
|> Ecto.Changeset.cast(params, [:first_name, :last_name, :age])
|> Ecto.Changeset.validate_required([:first_name, :last_name])
end
Этот changeset принимает person и набор параметров, содержащих изменения, которые нужно применить к этому человеку. Функция changeset сначала приводит ключи first_name, last_name и age из переданных параметров к типам changeset. Приведение типов указывает changeset, какие параметры разрешено передавать в нём; все параметры, не включённые в список, будут проигнорированы.
В следующей строке мы вызываем validate_required, указывая, что для этого changeset должны быть заданы значения first_name и last_name. Воспользуемся этим changeset, чтобы попытаться создать новую запись без first_name и last_name:
person = %Friends.Person{}
changeset = Friends.Person.changeset(person, %{})
Friends.Repo.insert(changeset)
В первой строке мы получаем структуру из модуля Friends.Person. Мы уже знаем, что делает этот код, поскольку рассматривали его совсем недавно. Во второй строке мы делаем нечто новое: определяем changeset. В нём указано, что мы хотим внести некоторые изменения в объект person. В данном случае мы не собираемся ничего менять.
В последней строке мы добавляем не person, а changeset. changeset знает о person, изменениях и правилах проверки, которым нужно соответствовать, прежде чем данные можно будет добавить в базу данных. После выполнения третьей строки мы увидим следующее:
{:error,
#Ecto.Changeset<action: :insert, changes: %{},
errors: [first_name: "can't be blank", last_name: "can't be blank"],
data: #Friends.Person<>, valid?: false>}
Как и при предыдущем добавлении, возвращается кортеж. Однако на этот раз его первый элемент — :error, что означает, что произошла ошибка. Подробности произошедшего содержатся в возвращённом changeset. Получить их можно с помощью сопоставления с образцом:
{:error, changeset} = Friends.Repo.insert(changeset)
Затем можно получить ошибки с помощью changeset.errors:
[first_name: {"can't be blank", [validation: :required]}, last_name: {"can't be blank", [validation: :required]}]
Также можно проверить, является ли сам changeset допустимым, ещё до добавления записи:
changeset.valid? #=> false
Поскольку этот changeset содержит ошибки, новая запись не была добавлена в таблицу people.
Теперь попробуем добавить корректные данные.
person = %Friends.Person{}
changeset = Friends.Person.changeset(person, %{first_name: "Ryan", last_name: "Bigg"})
Сначала мы создаём обычную структуру Friends.Person. Затем для этой структуры person создаём changeset с заданными параметрами first_name и last_name. Теперь можно проверить, есть ли в changeset ошибки:
changeset.errors #=> []
И проверить, является ли он допустимым:
changeset.valid? #=> true
В changeset нет ошибок, и он допустим. Поэтому добавление этого changeset должно пройти успешно:
Friends.Repo.insert(changeset)
#=> {:ok,
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: nil,
first_name: "Ryan", id: 3, last_name: "Bigg"}}
Поскольку Friends.Repo.insert возвращает кортеж, с помощью case можно выбрать разные пути выполнения кода в зависимости от результата:
case Friends.Repo.insert(changeset) do
{:ok, person} ->
# do something with person
{:error, changeset} ->
# do something with changeset
end
ПРИМЕЧАНИЕ: changeset.valid? не проверяет ограничения (например, uniqueness_constraint). Для этого нужно попытаться добавить запись и проверить ошибки, возвращённые базой данных. Поэтому лучше добавлять данные и проверять возвращаемый кортеж из Friends.Repo.insert, чтобы получить точные сообщения об ошибках: до добавления changeset содержит только ошибки проверки, обнаруженные самим приложением.
Если добавление changeset прошло успешно, можно делать всё, что угодно, с возвращённым в результате person. Если оно завершилось неудачей, у вас будет доступ к changeset и его ошибкам. В случае ошибки можно показать эти ошибки конечному пользователю. Ошибки в changeset представляют собой ключевой список такого вида:
[first_name: {"can't be blank", [validation: :required]},
last_name: {"can't be blank", [validation: :required]}]
Первый элемент кортежа — сообщение о проверке, а второй — ключевой список параметров этого сообщения. Представим, что мы проверяем поле bio и его длина должна превышать 15 символов. В этом случае вернётся следующее:
[first_name: {"can't be blank", [validation: :required]},
last_name: {"can't be blank", [validation: :required]},
bio: {"should be at least %{count} character(s)", [count: 15, validation: :length, kind: :min, type: :string]}]
Чтобы вывести эти сообщения об ошибках в удобном для пользователя виде, можно использовать Ecto.Changeset.traverse_errors/2:
traverse_errors(changeset, fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
Для показанных выше ошибок это вернёт следующее:
%{
first_name: ["can't be blank"],
last_name: ["can't be blank"],
bio: ["should be at least 15 character(s)"],
}
И последнее замечание: с помощью Friends.Repo.insert!/2 можно вызвать исключение. Если changeset недопустим, будет выброшено исключение Ecto.InvalidChangesetError. Вот небольшой пример:
Friends.Repo.insert! Friends.Person.changeset(%Friends.Person{}, %{first_name: "Ryan"})
** (Ecto.InvalidChangesetError) could not perform insert because changeset is invalid.
Errors
%{last_name: [{"can't be blank", [validation: :required]}]}
Applied changes
%{first_name: "Ryan"}
Params
%{"first_name" => "Ryan"}
Changeset
#Ecto.Changeset<
action: :insert,
changes: %{first_name: "Ryan"},
errors: [last_name: {"can't be blank", [validation: :required]}],
data: #Friends.Person<>,
valid?: false
>
(ecto) lib/ecto/repo/schema.ex:257: Ecto.Repo.Schema.insert!/4
Это исключение показывает изменения из changeset и причину, по которой changeset недопустим. Это может быть полезно, если нужно добавить большой объём данных и вызвать исключение, если данные добавлены неправильно.
Теперь, когда мы рассмотрели добавление данных в базу, давайте посмотрим, как извлечь их обратно.
Первые запросы
Запрос к базе данных в Ecto состоит из двух шагов. Сначала нужно составить запрос, а затем выполнить его в базе данных, передав запрос репозиторию. Перед этим пересоздадим базу данных приложения и подготовим тестовые данные. Чтобы пересоздать базу данных, выполним следующие команды:
mix ecto.drop mix ecto.create mix ecto.migrate
Затем добавим тестовые данные, выполнив этот код в сеансе iex -S mix:
people = [
%Friends.Person{first_name: "Ryan", last_name: "Bigg", age: 28},
%Friends.Person{first_name: "John", last_name: "Smith", age: 27},
%Friends.Person{first_name: "Jane", last_name: "Smith", age: 26},
]
Enum.each(people, fn (person) -> Friends.Repo.insert(person) end)
Этот код создаст в базе данных трёх людей: Ryan, John и Jane. Обратите внимание: для проверки добавляемых в базу данных данных можно было использовать changeset, но мы решили этого не делать.
В этом разделе мы будем выполнять запросы для этих людей. Приступим!
Получение одной записи
Начнём с получения одной записи из таблицы people:
Friends.Person |> Ecto.Query.first
Этот код создаст Ecto.Query следующего вида:
#Ecto.Query<from p0 in Friends.Person, order_by: [asc: p0.id], limit: 1>
Код между угловыми скобками <...> показывает составленный запрос Ecto. Мы могли бы составить этот запрос самостоятельно, используя почти такой же синтаксис:
require Ecto.Query Ecto.Query.from p in Friends.Person, order_by: [asc: p.id], limit: 1
Здесь нам нужно выполнить require Ecto.Query, чтобы подключить макросы этого модуля. Затем достаточно вызвать функцию from из Ecto.Query и передать ей код, указанный между угловыми скобками. Как видно, Ecto.Query.first избавляет нас от необходимости задавать для запроса order и limit.
Чтобы выполнить только что составленный запрос, можно вызвать Friends.Repo.one:
Friends.Person |> Ecto.Query.first |> Friends.Repo.one
Функция one извлекает из базы данных только одну запись и возвращает новую структуру из модуля Friends.Person:
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28,
first_name: "Ryan", id: 1, last_name: "Bigg"}
Как и для first, существует также last:
Friends.Person |> Ecto.Query.last |> Friends.Repo.one
#=> %Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26,
first_name: "Jane", id: 3, last_name: "Smith"}
Функция Ecto.Repo.one возвращает структуру, только если запрос к базе данных вернул одну запись. Если вернулось больше одной записи, будет выброшено исключение Ecto.MultipleResultsError. Вот пример кода, который приведёт к такой ошибке:
Friends.Person |> Friends.Repo.one
Мы опустили здесь Ecto.Query.first, поэтому к выполненному запросу не применяется ни условие limit, ни order. Выполненный запрос можно увидеть в журнале отладки:
[timestamp] [debug] SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 [] OK query=1.8ms
Сразу после этого будет выброшено исключение Ecto.MultipleResultsError:
** (Ecto.MultipleResultsError) expected at most one result but got 3 in query:
from p in Friends.Person
lib/ecto/repo/queryable.ex:67: Ecto.Repo.Queryable.one/4
Это происходит потому, что Ecto не знает, какую запись из всех возвращённых нужно выбрать. Ecto вернёт результат, только если в запросе явно указано, какой именно результат нужен.
Если запрос не найдёт ни одной записи, one вернёт nil.
Получение всех записей
Для получения всех записей из схемы Ecto предоставляет функцию all:
Friends.Person |> Friends.Repo.all
Она вернёт представление всех существующих записей в таблице people в виде структуры Friends.Person:
[%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28,
first_name: "Ryan", id: 1, last_name: "Bigg"},
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27,
first_name: "John", id: 2, last_name: "Smith"},
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26,
first_name: "Jane", id: 3, last_name: "Smith"}]
Получение одной записи по ID
Чтобы получить запись по её ID, используйте функцию get:
Friends.Person |> Friends.Repo.get(1)
#=> %Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28,
first_name: "Ryan", id: 1, last_name: "Bigg"}
Получение одной записи по заданному атрибуту
Если нужно получить запись по атрибуту, отличному от id, можно использовать get_by:
Friends.Person |> Friends.Repo.get_by(first_name: "Ryan")
#=> %Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28,
first_name: "Ryan", id: 1, last_name: "Bigg"}
Фильтрация результатов
Если нужно получить несколько записей, соответствующих заданному атрибуту, можно использовать where:
Friends.Person |> Ecto.Query.where(last_name: "Smith") |> Friends.Repo.all
[%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27,
first_name: "John", id: 2, last_name: "Smith"},
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26,
first_name: "Jane", id: 3, last_name: "Smith"}]
Если убрать Friends.Repo.all в конце этого выражения, мы увидим запрос, который генерирует Ecto:
#Ecto.Query<from p in Friends.Person, where: p.last_name == "Smith">
Для получения тех же записей можно также использовать этот синтаксис запросов:
Ecto.Query.from(p in Friends.Person, where: p.last_name == "Smith") |> Friends.Repo.all
Важно отметить, что в обоих вариантах синтаксиса запросов переменные нужно закреплять оператором закрепления (^). Иначе произойдёт следующее:
last_name = "Smith" Friends.Person |> Ecto.Query.where(last_name: last_name) |> Friends.Repo.all
** (Ecto.Query.CompileError) unbound variable `last_name` in query. If you are attempting to interpolate a value, use ^var
(ecto) expanding macro: Ecto.Query.where/2
iex:15: (file)
(elixir) expanding macro: Kernel.|>/2
iex:15: (file)
То же самое произойдёт и при использовании более длинного синтаксиса запроса:
Ecto.Query.from(p in Friends.Person, where: p.last_name == last_name) |> Friends.Repo.all
** (Ecto.Query.CompileError) unbound variable `last_name` in query. If you are attempting to interpolate a value, use ^var
(ecto) expanding macro: Ecto.Query.where/3
iex:15: (file)
(ecto) expanding macro: Ecto.Query.from/2
iex:15: (file)
(elixir) expanding macro: Kernel.|>/2
iex:15: (file)
Чтобы этого избежать, используйте оператор закрепления (^):
last_name = "Smith" Friends.Person |> Ecto.Query.where(last_name: ^last_name) |> Friends.Repo.all
Или:
last_name = "Smith" Ecto.Query.from(p in Friends.Person, where: p.last_name == ^last_name) |> Friends.Repo.all
Оператор закрепления указывает построителю запросов использовать параметризованные SQL-запросы, защищающие от SQL-инъекций.
Композиция запросов Ecto
Запросы Ecto необязательно составлять в одном месте. Их можно постепенно формировать, вызывая функции Ecto.Query для уже существующих запросов. Например, чтобы найти всех людей с фамилией "Smith", можно написать:
query = Friends.Person |> Ecto.Query.where(last_name: "Smith")
Чтобы дополнительно отфильтровать результаты и оставить только людей с именем "Jane", можно написать:
query = query |> Ecto.Query.where(first_name: "Jane")
Теперь наш запрос будет содержать два условия where:
#Ecto.Query<from p in Friends.Person, where: p.last_name == "Smith", where: p.first_name == "Jane">
Это удобно, если нужно выполнить какие-то действия с первым запросом, а затем позднее расширить его.
Обновление записей
Чтобы обновить запись в Ecto, сначала нужно получить её из базы данных. Затем нужно создать changeset на основе этой записи и изменений, которые мы хотим внести, и вызвать функцию Ecto.Repo.update.
Получим первого человека из базы данных и изменим его возраст. Сначала извлечём запись:
person = Friends.Person |> Ecto.Query.first |> Friends.Repo.one
Затем создадим changeset. Он необходим, поскольку, если просто создать новую структуру Friends.Person с новым возрастом, Ecto не сможет понять, что возраст изменился, не обращаясь к базе данных. Создадим changeset:
changeset = Friends.Person.changeset(person, %{age: 29})
Этот changeset сообщает базе данных, что мы хотим обновить запись и задать для age значение 29. Чтобы сообщить базе данных об изменении, выполним эту команду:
Friends.Repo.update(changeset)
Как и Friends.Repo.insert, Friends.Repo.update вернёт кортеж:
{:ok,
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 29,
first_name: "Ryan", id: 1, last_name: "Bigg"}}
Если changeset по какой-либо причине не пройдёт проверку, результатом Friends.Repo.update будет {:error, changeset}. Увидеть это можно, передав пустое значение first_name в параметрах changeset:
changeset = Friends.Person.changeset(person, %{first_name: ""})
Friends.Repo.update(changeset)
#=> {:error,
#Ecto.Changeset<
action: :update,
changes: %{},
errors: [first_name: {"can't be blank", [validation: :required]}],
data: #Friends.Person<>,
valid?: false
>}
Таким образом, с помощью инструкции case можно выполнять разные действия в зависимости от результата функции update:
case Friends.Repo.update(changeset) do
{:ok, person} ->
# do something with person
{:error, changeset} ->
# do something with changeset
end
Как и для insert!, существует также update!, которая вызовет исключение, если changeset недопустим:
changeset = Friends.Person.changeset(person, %{first_name: ""})
Friends.Repo.update! changeset
** (Ecto.InvalidChangesetError) could not perform update because changeset is invalid.
Errors
%{first_name: [{"can't be blank", [validation: :required]}]}
Applied changes
%{}
Params
%{"first_name" => ""}
Changeset
#Ecto.Changeset<
action: :update,
changes: %{},
errors: [first_name: {"can't be blank", [validation: :required]}],
data: #Friends.Person<>,
valid?: false
>
(ecto) lib/ecto/repo/schema.ex:270: Ecto.Repo.Schema.update!/4
Удаление записей
Мы рассмотрели создание (insert), чтение (get, get_by, where) и обновление записей. В заключение этого руководства рассмотрим, как удалить запись с помощью Ecto.
Как и при обновлении, сначала нужно получить запись из базы данных, а затем вызвать Friends.Repo.delete, чтобы удалить её:
person = Friends.Repo.get(Friends.Person, 1)
Friends.Repo.delete(person)
#=> {:ok,
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:deleted, "people">, age: 29,
first_name: "Ryan", id: 2, last_name: "Bigg"}}
Как и insert и update, функция delete возвращает кортеж. Если удаление прошло успешно, первым элементом кортежа будет :ok, а если завершилось неудачей — :error.
© 2014 Chris McCord
Licensed under the MIT License.
https://ecto.hexdocs.pm/getting-started.html