Spec-Zone.ru › Phoenix

Ecto.Type поведение

Исходный код

Определяет функции и поведение Ecto.Type для реализации базовых пользовательских типов.

Ecto предоставляет два типа пользовательских типов: базовые и параметризованные. Базовые типы просты: для их реализации достаточно четырёх обратных вызовов, чего хватает в большинстве случаев. Параметризованные типы можно настраивать при определении поля, и они предоставляют широкий набор обратных вызовов.

Определение базовых пользовательских типов и все их обратные вызовы доступны в этом модуле. Подробнее о параметризованных типах можно узнать в Ecto.ParameterizedType. Если сомневаетесь, отдавайте предпочтение базовым пользовательским типам и используйте параметризованные, только если вам нужны дополнительные возможности.

Внешнее, внутреннее и представленное в базе данных значения

Основная функция пользовательского типа — сопоставление внешнего, внутреннего и представленного в базе данных значений, принадлежащих этому типу.

Определения внешних и внутренних данных см. в соответствующем разделе документации по changeset.

stateDiagram-v2
  external: External Data
  internal: Internal Data
  database: Database Data
  external --> internal: cast/1
  internal --> database: dump/1
  database --> internal: load/1

Пример

Предположим, вы хотите хранить структуру URI в схеме сервиса сокращения URL-адресов. Во время выполнения в Ecto нет типа поля для такого значения, поэтому нужен пользовательский тип.

Кроме того, вы хотите выполнять запросы не только по полному URL-адресу, но, например, и по конкретным используемым портам. Это можно сделать, сохранив данные URI в поле map, а не просто в виде строки.

from s in ShortUrl,
  where: fragment("?->>? ILIKE ?", s.original_url, "port", "443")

Таким образом, пользовательский тип должен обрабатывать преобразование внешних данных в данные времени выполнения (cast/1), а также преобразование этих данных времени выполнения во встроенный тип Ecto :map и обратно (dump/1 и load/1).

defmodule EctoURI do
  use Ecto.Type
  def type, do: :map

  # Provide custom casting rules.
  # Cast strings into the URI struct to be used at runtime
  def cast(uri) when is_binary(uri) do
    {:ok, URI.parse(uri)}
  end

  # Accept casting of URI structs as well
  def cast(%URI{} = uri), do: {:ok, uri}

  # Everything else is a failure though
  def cast(_), do: :error

  # When loading data from the database, as long as it's a map,
  # we just put the data back into a URI struct to be stored in
  # the loaded schema struct.
  def load(data) when is_map(data) do
    data =
      for {key, val} <- data do
        {String.to_existing_atom(key), val}
      end
    {:ok, struct!(URI, data)}
  end

  # When dumping data to the database, we *expect* a URI struct
  # but any value could be inserted into the schema struct at runtime,
  # so we need to guard against them.
  def dump(%URI{} = uri), do: {:ok, Map.from_struct(uri)}
  def dump(_), do: :error
end

Теперь мы можем использовать созданный тип поля в наших схемах:

defmodule ShortUrl do
  use Ecto.Schema

  schema "posts" do
    field :original_url, EctoURI
  end
end

Примечание: значения nil всегда обходят пользовательские типы и не могут ими обрабатываться.

use Ecto.Type

При вызове use Ecto.Type будет задано @behaviour Ecto.Type и определены реализации по умолчанию, которые можно переопределить, для embed_as/1 и equal?/2. Если при экспорте из Ecto вы хотите, чтобы вызывался ваш dump/1, необходимо реализовать собственную функцию embed_as/1.

Пользовательские типы и первичные ключи

Помните: если вы измените тип первичных ключей, необходимо также изменить тип всех ассоциаций, ссылающихся на эти первичные ключи.

Предположим, вы хотите закодировать идентификаторы, чтобы пользователи не могли перебирать содержимое вашего приложения. Тип Ecto может преобразовывать закодированную версию идентификатора в его представление в базе данных и обратно. Для простоты в этом примере воспользуемся кодированием base64:

defmodule EncodedId do
  use Ecto.Type

  def type, do: :id

  def cast(id) when is_integer(id) do
    {:ok, encode_id(id)}
  end
  def cast(_), do: :error

  def dump(id) when is_binary(id) do
    {:ok, id_decoded} = Base.decode64(id)
    {:ok, String.to_integer(id_decoded)}
  end

  def load(id) when is_integer(id) do
    {:ok, encode_id(id)}
  end

  defp encode_id(id) do
    id
    |> Integer.to_string()
    |> Base.encode64()
  end
end

Чтобы использовать его как тип идентификатора в нашей схеме, можно воспользоваться атрибутом модуля @primary_key:

defmodule BlogPost do
  use Ecto.Schema

  @primary_key {:id, EncodedId, autogenerate: true}
  schema "posts" do
    belongs_to :author, Author, type: EncodedId
    field :content, :string
  end
end

defmodule Author do
  use Ecto.Schema

  @primary_key {:id, EncodedId, autogenerate: true}
  schema "authors" do
    field :name, :string
    has_many :posts, BlogPost
  end
end

Атрибут @primary_key сообщит ecto, какой тип использовать для идентификатора.

Обратите внимание на параметр type: EncodedId, переданный в belongs_to в схеме BlogPost. По умолчанию Ecto рассматривает ассоциации так, будто их ключи имеют тип :integer. Наши первичные ключи имеют пользовательский тип, поэтому Ecto завершит работу с ошибкой при попытке привести эти идентификаторы к нужному типу.

В качестве альтернативы после @primary_key можно задать @foreign_key_type EncodedId, чтобы автоматически настроить тип всех полей belongs_to.

Сводка

Типы

base()
composite()
custom()

Пользовательские типы представлены модулями, определёнными пользователем.

primitive()

Примитивные типы Ecto (обрабатываются Ecto).

t()

Тип Ecto: примитивный или пользовательский.

Обратные вызовы

autogenerate()

Создаёт загруженную версию данных.

cast(term)

Приводит переданное значение к пользовательскому типу.

dump(term)

Преобразует переданное значение во встроенный тип Ecto.

embed_as(format)

Определяет, как следует обрабатывать тип внутри встроенных структур.

equal?(term, term)

Проверяет, равны ли два значения семантически.

load(term)

Загружает переданное значение в пользовательский тип.

type()

Возвращает базовый тип схемы для пользовательского типа.

Функции

base?(atom)

Проверяет, можно ли использовать переданный атом как базовый тип.

cast(type, value)

Приводит значение к указанному типу.

cast!(type, value)

Приводит значение к указанному типу или вызывает ошибку.

composite?(atom)

Проверяет, можно ли использовать переданный атом как составной тип.

dump(type, value, dumper \\ &dump/2)

Преобразует значение в указанный тип.

embed_as(base, format)

Возвращает способ обработки типа внутри встроенных структур для указанного формата.

embedded_dump(type, value, format)

Преобразует value для type с учётом того, что он будет встроен в format.

embedded_load(type, value, format)

Загружает value для type с учётом того, что он был встроен в format.

equal?(type, term1, term2)

Проверяет, равны ли два значения.

format(type)

Форматирует тип для сообщений об ошибках и журналов.

include?(type, term, collection)

Проверяет, содержит ли collection значение term.

load(type, value, loader \\ &load/2)

Загружает значение с указанным типом.

match?(schema_type, query_type)

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

parameterized?(arg1, module)

Проверяет, параметризован ли заданный тип указанным модулем.

primitive?(base)

Проверяет, является ли тип примитивным.

trim(type, value)

Обрезает значение в соответствии с его типом.

type(type)

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

Типы

base()Исходный код

@type base() ::
  :integer
  | :float
  | :boolean
  | :string
  | :bitstring
  | :map
  | :binary
  | :decimal
  | :id
  | :binary_id
  | :utc_datetime
  | :naive_datetime
  | :date
  | :time
  | :any
  | :utc_datetime_usec
  | :naive_datetime_usec
  | :time_usec
  | :duration

composite()Исходный код

@type composite() :: {:array, t()} | {:map, t()} | private_composite()

custom()Исходный код

@type custom() :: module() | {:parameterized, {module(), term()}}

Пользовательские типы представлены модулями, определёнными пользователем.

primitive()Исходный код

@type primitive() :: base() | composite()

Примитивные типы Ecto (обрабатываются Ecto).

t()Исходный код

@type t() :: primitive() | custom()

Тип Ecto: примитивный или пользовательский.

Обратные вызовы

autogenerate()Исходный код

@callback autogenerate() :: term()

Создаёт загруженную версию данных.

Этот обратный вызов вызывается, когда пользовательский тип передаётся в field с флагом :autogenerate.

cast(term)Исходный код

@callback cast(term()) :: {:ok, term()} | :error | {:error, keyword()}

Приводит переданное значение к пользовательскому типу.

Этот обратный вызов вызывается для внешних данных и может возвращать значение любого типа, если функция dump/1 способна преобразовать возвращённое значение во встроенный тип Ecto. Этот обратный вызов вызывается в двух случаях:

  1. При приведении значений с помощью Ecto.Changeset
  2. При передаче аргументов в Ecto.Query

Если переданное значение невозможно привести к нужному типу, можно вернуть :error. В changeset будет добавлено стандартное сообщение об ошибке «недопустимое значение».

Также можно вернуть {:error, keyword()}, чтобы настроить сообщение об ошибке changeset и его метаданные. Если передать ключ :message, он заменит стандартное сообщение. Переопределить ключ :type невозможно.

Для {:array, CustomType} или {:map, CustomType} возвращённый список ключевых слов будет удалён, а отображено стандартное сообщение об ошибке.

dump(term)Исходный код

@callback dump(term()) :: {:ok, term()} | :error

Преобразует переданное значение во встроенный тип Ecto.

Этот обратный вызов получает любое значение, сохранённое в структуре. Он должен проверить его и преобразовать во встроенный тип Ecto.

embed_as(format)Исходный код

@callback embed_as(format :: atom()) :: :self | :dump

Определяет, как следует обрабатывать тип внутри встроенных структур.

По умолчанию тип передаётся без изменений, без вызова преобразования, чтобы сохранить представление более высокого уровня. Однако можно задать :dump, чтобы преобразовать его перед кодированием.

equal?(term, term)Исходный код

@callback equal?(term(), term()) :: boolean()

Проверяет, равны ли два значения семантически.

Этот обратный вызов используется для определения равенства типов в Ecto.Changeset.

По умолчанию значения сравниваются оператором равенства ==/2.

load(term)Исходный код

@callback load(term()) :: {:ok, term()} | :error

Загружает переданное значение в пользовательский тип.

Этот обратный вызов вызывается при загрузке данных из базы данных и получает встроенный тип Ecto. Он может возвращать значение любого типа, если функция dump/1 способна преобразовать возвращённое значение обратно во встроенный тип Ecto.

type()Исходный код

@callback type() :: t()

Возвращает базовый тип схемы для пользовательского типа.

Например, если вы хотите предоставить собственные структуры для дат, функция type должна возвращать :date.

Обратите внимание: эта функция не обязана возвращать примитивные типы Ecto — достаточно, чтобы тип был известен адаптеру.

Функции

base?(atom)Исходный код

@spec base?(atom()) :: boolean()

Проверяет, можно ли использовать переданный атом как базовый тип.

iex> base?(:string)
true
iex> base?(:array)
false
iex> base?(Custom)
false

cast(type, value)Исходный код

@spec cast(t(), term()) :: {:ok, term()} | {:error, keyword()} | :error

Приводит значение к указанному типу.

cast/2 используется запросами поиска и changeset для приведения внешних значений к определённым типам.

Обратите внимание: nil можно привести ко всем примитивным типам, поскольку хранилища данных позволяют устанавливать nil в любом столбце.

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

iex> cast(:any, "whatever")
{:ok, "whatever"}

iex> cast(:any, nil)
{:ok, nil}
iex> cast(:string, nil)
{:ok, nil}

iex> cast(:integer, 1)
{:ok, 1}
iex> cast(:integer, "1")
{:ok, 1}
iex> cast(:integer, "1.0")
:error

iex> cast(:id, 1)
{:ok, 1}
iex> cast(:id, "1")
{:ok, 1}
iex> cast(:id, "1.0")
:error

iex> cast(:float, 1.0)
{:ok, 1.0}
iex> cast(:float, 1)
{:ok, 1.0}
iex> cast(:float, "1")
{:ok, 1.0}
iex> cast(:float, "1.0")
{:ok, 1.0}
iex> cast(:float, "1-foo")
:error

iex> cast(:boolean, true)
{:ok, true}
iex> cast(:boolean, false)
{:ok, false}
iex> cast(:boolean, "1")
{:ok, true}
iex> cast(:boolean, "0")
{:ok, false}
iex> cast(:boolean, "whatever")
:error

iex> cast(:string, "beef")
{:ok, "beef"}
iex> cast(:binary, "beef")
{:ok, "beef"}

iex> cast(:decimal, Decimal.new("1.0"))
{:ok, Decimal.new("1.0")}
iex> cast(:decimal, "1.0bad")
:error

iex> cast({:array, :integer}, [1, 2, 3])
{:ok, [1, 2, 3]}
iex> cast({:array, :integer}, ["1", "2", "3"])
{:ok, [1, 2, 3]}
iex> cast({:array, :string}, [1, 2, 3])
:error
iex> cast(:string, [1, 2, 3])
:error

iex> cast(:utc_datetime, "2014-04-17T14:00:00Z")
{:ok, ~U[2014-04-17 14:00:00Z]}
iex> cast(:utc_datetime, "2014-04-17T14:00:00.030Z")
{:ok, ~U[2014-04-17 14:00:00Z]}
iex> cast(:utc_datetime, "2014-04-17T12:00:00-02:00")
{:ok, ~U[2014-04-17 14:00:00Z]}

cast!(type, value)Исходный код

Приводит значение к указанному типу или вызывает ошибку.

Дополнительные сведения см. в cast/2.

Примеры

iex> Ecto.Type.cast!(:integer, "1")
1
iex> Ecto.Type.cast!(:integer, 1)
1
iex> Ecto.Type.cast!(:integer, nil)
nil

iex> Ecto.Type.cast!(:integer, 1.0)
** (Ecto.CastError) cannot cast 1.0 to :integer

composite?(atom)Исходный код

@spec composite?(atom()) :: boolean()

Проверяет, можно ли использовать переданный атом как составной тип.

iex> composite?(:array)
true
iex> composite?(:string)
false

dump(type, value, dumper \\ &dump/2)Исходный код

@spec dump(t(), term(), (t(), term() -> {:ok, term()} | :error)) ::
  {:ok, term()} | :error

Преобразует значение в указанный тип.

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

iex> dump(:string, nil)
{:ok, nil}
iex> dump(:string, "foo")
{:ok, "foo"}

iex> dump(:integer, 1)
{:ok, 1}
iex> dump(:integer, "10")
:error

iex> dump(:binary, "foo")
{:ok, "foo"}
iex> dump(:binary, 1)
:error

iex> dump({:array, :integer}, [1, 2, 3])
{:ok, [1, 2, 3]}
iex> dump({:array, :integer}, [1, "2", 3])
:error
iex> dump({:array, :binary}, ["1", "2", "3"])
{:ok, ["1", "2", "3"]}

embed_as(base, format)Исходный код

Возвращает способ обработки типа внутри встроенных структур для указанного формата.

См. embed_as/1.

embedded_dump(type, value, format)Исходный код

Преобразует value для type с учётом того, что он будет встроен в format.

Примеры

iex> Ecto.Type.embedded_dump(:decimal, Decimal.new("1"), :json)
{:ok, Decimal.new("1")}

embedded_load(type, value, format)Исходный код

Загружает value для type с учётом того, что он был встроен в format.

Примеры

iex> Ecto.Type.embedded_load(:decimal, "1", :json)
{:ok, Decimal.new("1")}

equal?(type, term1, term2)Исходный код

@spec equal?(t(), term(), term()) :: boolean()

Проверяет, равны ли два значения.

В зависимости от указанного type выполняется структурное или семантическое сравнение.

Примеры

iex> equal?(:integer, 1, 1)
true
iex> equal?(:decimal, Decimal.new("1"), Decimal.new("1.00"))
true

format(type)Исходный код

Форматирует тип для сообщений об ошибках и журналов.

include?(type, term, collection)Исходный код

@spec include?(t(), term(), Enum.t()) :: boolean()

Проверяет, содержит ли collection значение term.

В зависимости от указанного type выполняется структурное или семантическое сравнение.

Примеры

iex> include?(:integer, 1, 1..3)
true
iex> include?(:decimal, Decimal.new("1"), [Decimal.new("1.00"), Decimal.new("2.00")])
true

load(type, value, loader \\ &load/2)Исходный код

@spec load(t(), term(), (t(), term() -> {:ok, term()} | :error)) ::
  {:ok, term()} | :error

Загружает значение с указанным типом.

iex> load(:string, nil)
{:ok, nil}
iex> load(:string, "foo")
{:ok, "foo"}

iex> load(:integer, 1)
{:ok, 1}
iex> load(:integer, "10")
:error

match?(schema_type, query_type)Исходный код

@spec match?(t(), primitive()) :: boolean()

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

iex> match?(:string, :any)
true
iex> match?(:any, :string)
true
iex> match?(:string, :string)
true

iex> match?({:array, :string}, {:array, :any})
true

iex> match?(Ecto.UUID, :uuid)
true
iex> match?(Ecto.UUID, :string)
false

parameterized?(arg1, module)Исходный код

@spec parameterized?(t(), module()) :: boolean()

Проверяет, параметризован ли заданный тип указанным модулем.

iex> type = Ecto.ParameterizedType.init(Ecto.Enum, values: [a: 1])
iex> Ecto.Type.parameterized?(type, Ecto.Enum)
true
iex> Ecto.Type.parameterized?(type, MyEnum)
false

primitive?(base)Исходный код

@spec primitive?(t()) :: boolean()

Проверяет, является ли тип примитивным.

iex> primitive?(:string)
true
iex> primitive?(Another)
false

iex> primitive?({:array, :string})
true
iex> primitive?({:array, Another})
true

trim(type, value)Исходный код

Обрезает значение в соответствии с его типом.

В настоящее время обрезаются все строки, кроме строк типа :binary.

type(type)Исходный код

@spec type(t()) :: t()

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

iex> type(:string)
:string
iex> type(Ecto.UUID)
:uuid

iex> type({:array, :string})
{:array, :string}
iex> type({:array, Ecto.UUID})
{:array, :uuid}

iex> type({:map, Ecto.UUID})
{:map, :uuid}

Пакет Hex Предпросмотр Hex Просмотреть llms.txt Скачать версию ePub

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

© 2014 Chris McCord
Licensed under the MIT License.
https://ecto.hexdocs.pm/Ecto.Type.html

Spec-Zone.ru

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