mirror of
https://github.com/sloanelybutsurely/typeid-elixir.git
synced 2024-11-28 01:42:54 -05:00
Add Ecto support (#14)
* add ecto type support * Ecto.ParameterizedType, `time: ...` for `new/2` * 0.3.0
This commit is contained in:
parent
428a80a633
commit
3d657061f5
7 changed files with 138 additions and 15 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
|||
# Changelog
|
||||
|
||||
## main
|
||||
|
||||
- **BREAKING:** `type/1` has been renamed to `prefix/1`
|
||||
- `Ecto.ParameterizedType` implementation
|
||||
- `new/2` now accepts an optional keyword list to specify the UUID `time:` in unix milliseconds
|
||||
```elixir
|
||||
iex> TypeID.new("test", time: 0)
|
||||
#TypeID<"test_0000000000fq893mf5039xea5j">
|
||||
```
|
||||
|
||||
## 0.2.2
|
||||
|
||||
- Lower required Elixir version
|
||||
|
|
17
README.md
17
README.md
|
@ -15,7 +15,7 @@ The package can be installed from [hex](https://hex.pm/packages/typeid_elixir) b
|
|||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:typeid_elixir, "~> 0.2.2"}
|
||||
{:typeid_elixir, "~> 0.3.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
@ -23,3 +23,18 @@ end
|
|||
## Spec
|
||||
|
||||
The original TypeID spec is defined [here](https://github.com/jetpack-io/typeid).
|
||||
|
||||
## Usage with Ecto
|
||||
|
||||
`TypeID` implements the `Ecto.ParameterizedType` behaviour so you can use
|
||||
TypeIDs as fields in your Ecto schemas.
|
||||
|
||||
```elixir
|
||||
defmodule MyApp.Accounts.User do
|
||||
use Ecto.Schema
|
||||
|
||||
@primary_key {:id, TypeID, autogenerate: true, prefix: "acct", type: :binary_id}
|
||||
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
|
|
@ -18,6 +18,9 @@ defmodule TypeID do
|
|||
@doc """
|
||||
Generates a new `t:t/0` with the given prefix.
|
||||
|
||||
**Optional**: Specify the time of the UUID v7 by passing
|
||||
`time: unix_millisecond_time` as the second argument.
|
||||
|
||||
### Example
|
||||
|
||||
iex> TypeID.new("acct")
|
||||
|
@ -25,26 +28,27 @@ defmodule TypeID do
|
|||
|
||||
"""
|
||||
@spec new(prefix :: String.t()) :: t()
|
||||
def new(prefix) do
|
||||
@spec new(prefix :: String.t(), Keyword.t()) :: t()
|
||||
def new(prefix, opts \\ []) do
|
||||
suffix =
|
||||
UUID.uuid7()
|
||||
UUID.uuid7(opts)
|
||||
|> Base32.encode()
|
||||
|
||||
%__MODULE__{prefix: prefix, suffix: suffix}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the type of the given `t:t/0`.
|
||||
Returns the prefix of the given `t:t/0`.
|
||||
|
||||
### Example
|
||||
|
||||
iex> tid = TypeID.new("doc")
|
||||
iex> TypeID.type(tid)
|
||||
iex> TypeID.prefix(tid)
|
||||
"doc"
|
||||
|
||||
"""
|
||||
@spec type(tid :: t()) :: String.t()
|
||||
def type(%__MODULE__{prefix: prefix}) do
|
||||
@spec prefix(tid :: t()) :: String.t()
|
||||
def prefix(%__MODULE__{prefix: prefix}) do
|
||||
prefix
|
||||
end
|
||||
|
||||
|
@ -242,12 +246,93 @@ defmodule TypeID do
|
|||
|
||||
:ok
|
||||
end
|
||||
|
||||
if Code.ensure_loaded?(Ecto.ParameterizedType) do
|
||||
use Ecto.ParameterizedType
|
||||
|
||||
@impl Ecto.ParameterizedType
|
||||
def init(opts), do: validate_opts!(opts)
|
||||
|
||||
@impl Ecto.ParameterizedType
|
||||
def type(%{type: type}), do: type
|
||||
|
||||
@impl Ecto.ParameterizedType
|
||||
def autogenerate(%{prefix: prefix}) do
|
||||
new(prefix)
|
||||
end
|
||||
|
||||
@impl Ecto.ParameterizedType
|
||||
def cast(nil, _params), do: {:ok, nil}
|
||||
def cast(%__MODULE__{prefix: prefix} = tid, %{prefix: prefix}), do: {:ok, tid}
|
||||
|
||||
def cast(str, %{prefix: prefix}) when is_binary(str) do
|
||||
if String.starts_with?(str, prefix) do
|
||||
from_string(str)
|
||||
else
|
||||
with {:ok, uuid} <- Ecto.UUID.cast(str) do
|
||||
from_uuid(prefix, uuid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_, _), do: :error
|
||||
|
||||
@impl Ecto.ParameterizedType
|
||||
def dump(nil, _dumper, _params), do: {:ok, nil}
|
||||
|
||||
def dump(%__MODULE__{prefix: prefix} = tid, _, %{prefix: prefix, type: :string}) do
|
||||
{:ok, __MODULE__.to_string(tid)}
|
||||
end
|
||||
|
||||
def dump(%__MODULE__{prefix: prefix} = tid, _, %{prefix: prefix, type: :binary_id}) do
|
||||
{:ok, uuid(tid)}
|
||||
end
|
||||
|
||||
def dump(_, _, _), do: :error
|
||||
|
||||
@impl Ecto.ParameterizedType
|
||||
def load(nil, _, _), do: {:ok, nil}
|
||||
|
||||
def load(str, _, %{type: :string, prefix: prefix}) do
|
||||
with {:ok, %__MODULE__{prefix: ^prefix}} = loaded <- from_string(str) do
|
||||
loaded
|
||||
end
|
||||
end
|
||||
|
||||
def load(<<_::128>> = uuid, _, %{type: :binary_id, prefix: prefix}) do
|
||||
from_uuid_bytes(prefix, uuid)
|
||||
end
|
||||
|
||||
def load(<<_::288>> = uuid, _, %{type: :binary_id, prefix: prefix}) do
|
||||
from_uuid(prefix, uuid)
|
||||
rescue
|
||||
_ -> :error
|
||||
end
|
||||
|
||||
def load(_, _, _), do: :error
|
||||
|
||||
defp validate_opts!(opts) do
|
||||
type = Keyword.get(opts, :type, :string)
|
||||
prefix = Keyword.get(opts, :prefix, "")
|
||||
|
||||
unless prefix && prefix =~ ~r/^[a-z]{0,63}$/ do
|
||||
raise ArgumentError,
|
||||
"must specify `prefix` using only lowercase letters between 0 and 63 characters long."
|
||||
end
|
||||
|
||||
unless type in ~w[string binary_id]a do
|
||||
raise ArgumentError, "`type` must be `:string` or `:binary_id`"
|
||||
end
|
||||
|
||||
%{prefix: prefix, type: type}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: TypeID do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(tid, _opts) do
|
||||
concat(["#TypeID<\"", tid.prefix, "_", tid.suffix, "\">"])
|
||||
concat(["#TypeID<\"", TypeID.to_string(tid), "\">"])
|
||||
end
|
||||
end
|
||||
|
|
3
mix.exs
3
mix.exs
|
@ -1,7 +1,7 @@
|
|||
defmodule TypeID.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "0.2.2"
|
||||
@version "0.3.0"
|
||||
|
||||
def project do
|
||||
[
|
||||
|
@ -42,6 +42,7 @@ defmodule TypeID.MixProject do
|
|||
|
||||
defp deps do
|
||||
[
|
||||
{:ecto, "~> 3.10", only: [:dev, :test], optional: true},
|
||||
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
|
||||
{:yaml_elixir, "~> 2.9", only: [:dev, :test], runtime: false}
|
||||
]
|
||||
|
|
3
mix.lock
3
mix.lock
|
@ -1,10 +1,13 @@
|
|||
%{
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"},
|
||||
"ecto": {:hex, :ecto, "3.10.2", "6b887160281a61aa16843e47735b8a266caa437f80588c3ab80a8a960e6abe37", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6a895778f0d7648a4b34b486af59a1c8009041fbdf2b17f1ac215eb829c60235"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
|
||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"},
|
||||
}
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
defmodule TypeIDTest do
|
||||
use ExUnit.Case
|
||||
doctest TypeID, except: [new: 1]
|
||||
doctest TypeID, except: [new: 2]
|
||||
|
||||
describe "new/1" do
|
||||
test "returns a new TypeID struct" do
|
||||
tid = TypeID.new("test")
|
||||
assert is_struct(tid, TypeID)
|
||||
assert "test" == TypeID.type(tid)
|
||||
assert "test" == TypeID.prefix(tid)
|
||||
end
|
||||
end
|
||||
|
||||
describe "type/1" do
|
||||
test "returns the type (prefix) of the given TypeID" do
|
||||
describe "new/2" do
|
||||
test "allows setting the time" do
|
||||
time = ~U[1950-12-17 00:00:00Z] |> DateTime.to_unix(:millisecond)
|
||||
tid = TypeID.new("test", time: time)
|
||||
assert "test" == TypeID.prefix(tid)
|
||||
assert "7zegbdn300" <> _ = TypeID.suffix(tid)
|
||||
end
|
||||
end
|
||||
|
||||
describe "prefix/1" do
|
||||
test "returns the prefix of the given TypeID" do
|
||||
tid = TypeID.from_string!("test_01h44had5rfswbvpc383ktj0aa")
|
||||
assert "test" == TypeID.type(tid)
|
||||
assert "test" == TypeID.prefix(tid)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,7 +69,7 @@ defmodule TypeIDTest do
|
|||
|
||||
test "verification" do
|
||||
tid = TypeID.from_string!("test_01h44yssjcf5daefvfr0yb70s8")
|
||||
assert "test" == TypeID.type(tid)
|
||||
assert "test" == TypeID.prefix(tid)
|
||||
assert "018909ec-e64c-795a-a73f-6fc03cb38328" == TypeID.uuid(tid)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue