Packages
vtc
0.16.8
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.2
0.16.1
0.16.0
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.10
0.10.9
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A SMPTE timecode library for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/postgres/pg_rational.ex
use Vtc.Ecto.Postgres.Utils
defpgmodule Vtc.Ecto.Postgres.PgRational do
@moduledoc """
Defines a composite type for storing rational values as dual int64s. These values
are cast to `%Ratio{}` structs for use in application code, provided by the `Ratio`
library.
The composite type is defined as follows:
```sql
CREATE TYPE rational as (
numerator bigint,
denominator bigint
)
```
Rational values can be cast in SQL expressions like so:
```sql
SELECT (1, 2)::rational
```
See `Vtc.Ecto.Postgres.PgRational.Migrations` for more information on how to create
`rational` and its supporting functions in your database.
## Field migrations
You can create a field as a rational during a migration like so:
```elixir
create table("rationals") do
add(:a, PgRational.type())
add(:b, PgRational.type())
end
```
## Schema fields
Then in your schema module:
```elixir
defmodule MyApp.Rationals do
@moduledoc false
use Ecto.Schema
alias Vtc.Ecto.Postgres.PgRational
@type t() :: %__MODULE__{
a: Ratio.t(),
b: Ratio.t()
}
schema "rationals_01" do
field(:a, PgRational)
field(:b, PgRational)
end
```
... notice that the schema field type is `PgRational`, but the type-spec field uses
`Ratio.t()`, the type that our DB fields will be deserialized into.
## Changesets
With the above setup, changesets should just work:
```elixir
def changeset(schema, attrs) do
schema
|> Changeset.cast(attrs, [:a, :b])
|> Changeset.validate_required([:a, :b])
end
```
Rational values can be cast from the following values in changesets:
- `%Ratio{}` structs.
- `[numerator, denominator]` integer arrays. Useful for non-text JSON values that can
be set in a single field.
- Strings formatted as `'numerator/denominator'`. Useful for casting from a JSON
string.
"""
use Ecto.Type
@doc section: :ecto_migrations
@doc """
The database type for `PgRational`.
Can be used in migrations as the fields type.
"""
@impl Ecto.Type
@spec type() :: atom()
def type, do: :rational
@typedoc """
Type of the raw composite value that will be sent to / received from the database.
"""
@type db_record() :: {non_neg_integer(), pos_integer()}
# Handles casting PgRational fields in `Ecto.Changeset`s.
@doc false
@impl Ecto.Type
@spec cast(Ratio.t() | String.t() | [non_neg_integer()]) :: {:ok, Ratio.t()} | :error
def cast(%Ratio{} = ratio), do: {:ok, ratio}
def cast([num, denom]) when is_integer(num) and is_integer(denom), do: {:ok, Ratio.new(num, denom)}
def cast(fraction) when is_binary(fraction) do
with [num_str, denom_str] <- String.split(fraction, "/"),
{num, ""} <- Integer.parse(num_str),
{denom, ""} <- Integer.parse(denom_str) do
{:ok, Ratio.new(num, denom)}
else
_ -> :error
end
end
def cast(_), do: :error
# Handles converting database records into Ratio structs to be used by the
# application.
@doc false
@impl Ecto.Type
@spec load(db_record()) :: {:ok, Ratio.t()} | :error
def load({num, denom}) when is_integer(num) and is_integer(denom), do: {:ok, Ratio.new(num, denom)}
def load(_), do: :error
# Handles converting Ratio structs into database records.
@doc false
@impl Ecto.Type
@spec dump(Ratio.t()) :: {:ok, db_record()} | :error
def dump(%Ratio{} = rational), do: {:ok, {rational.numerator, rational.denominator}}
def dump(_), do: :error
end