Spec-Zone.ru › Phoenix

Начало работы

Исходный код

Это руководство — введение в 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.

← Предыдущая страница Список изменений для v3.x
Следующая страница → Основы CRUD

Пакет Hex Предпросмотр Hex (текущий файл) Просмотреть llms.txt Скачать версию ePub

Создано с помощью ExDoc (v0.40.2) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://ecto.hexdocs.pm/getting-started.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API