mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
fix: properly compare against decimal values
improvement: support floats & decimals in the `compare` validation
This commit is contained in:
parent
7540b31244
commit
986e08e0c2
8 changed files with 135 additions and 7 deletions
|
@ -95,7 +95,7 @@ defmodule Ash.CiString do
|
|||
def to_comparable_string(nil), do: nil
|
||||
end
|
||||
|
||||
use Comp
|
||||
import Ash.Type.Comparable
|
||||
|
||||
defcomparable left :: Ash.CiString, right :: BitString do
|
||||
Ash.CiString.compare(left, right)
|
||||
|
|
|
@ -12,22 +12,22 @@ defmodule Ash.Resource.Validation.Compare do
|
|||
doc: "The attribute to check"
|
||||
],
|
||||
greater_than: [
|
||||
type: {:or, [:integer, :atom, {:fun, 0}]},
|
||||
type: {:or, [:integer, :atom, :float, {:struct, Decimal}, {:fun, 0}]},
|
||||
required: false,
|
||||
doc: "The value that the attribute should be greater than."
|
||||
],
|
||||
greater_than_or_equal_to: [
|
||||
type: {:or, [:integer, :atom, {:fun, 0}]},
|
||||
type: {:or, [:integer, :atom, :float, {:struct, Decimal}, {:fun, 0}]},
|
||||
required: false,
|
||||
doc: "The value that the attribute should be greater than or equal to"
|
||||
],
|
||||
less_than: [
|
||||
type: {:or, [:integer, :atom, {:fun, 0}]},
|
||||
type: {:or, [:integer, :atom, :float, {:struct, Decimal}, {:fun, 0}]},
|
||||
required: false,
|
||||
doc: "The value that the attribute should be less than"
|
||||
],
|
||||
less_than_or_equal_to: [
|
||||
type: {:or, [:integer, :atom, {:fun, 0}]},
|
||||
type: {:or, [:integer, :atom, :float, {:struct, Decimal}, {:fun, 0}]},
|
||||
required: false,
|
||||
doc: "The value that the attribute should be less than or equal to"
|
||||
]
|
||||
|
|
65
lib/ash/type/comparable.ex
Normal file
65
lib/ash/type/comparable.ex
Normal file
|
@ -0,0 +1,65 @@
|
|||
defmodule Ash.Type.Comparable do
|
||||
@moduledoc "Helpers for working with `Comparable`"
|
||||
defmacro defcomparable(
|
||||
{:"::", _, [left_expression, quoted_left_type]},
|
||||
{:"::", _, [right_expression, quoted_right_type]},
|
||||
do: code
|
||||
) do
|
||||
{left_type, []} = Code.eval_quoted(quoted_left_type, [], __CALLER__)
|
||||
|
||||
{right_type, []} = Code.eval_quoted(quoted_right_type, [], __CALLER__)
|
||||
|
||||
lr_type =
|
||||
[Comparable, Type, left_type, To, right_type]
|
||||
|> Module.concat()
|
||||
|
||||
rl_type =
|
||||
[Comparable, Type, right_type, To, left_type]
|
||||
|> Module.concat()
|
||||
|
||||
lr_impl =
|
||||
quote do
|
||||
defmodule unquote(lr_type) do
|
||||
@fields [:left, :right]
|
||||
@enforce_keys @fields
|
||||
defstruct @fields
|
||||
end
|
||||
|
||||
defimpl Comparable, for: unquote(lr_type) do
|
||||
def compare(%unquote(lr_type){
|
||||
left: unquote(left_expression),
|
||||
right: unquote(right_expression)
|
||||
}) do
|
||||
unquote(code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if lr_type == rl_type do
|
||||
lr_impl
|
||||
else
|
||||
quote do
|
||||
unquote(lr_impl)
|
||||
|
||||
defmodule unquote(rl_type) do
|
||||
@fields [:left, :right]
|
||||
@enforce_keys @fields
|
||||
defstruct @fields
|
||||
end
|
||||
|
||||
defimpl Comparable, for: unquote(rl_type) do
|
||||
def compare(%unquote(rl_type){
|
||||
left: unquote(right_expression),
|
||||
right: unquote(left_expression)
|
||||
}) do
|
||||
case unquote(code) do
|
||||
:gt -> :lt
|
||||
:lt -> :gt
|
||||
:eq -> :eq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -112,4 +112,8 @@ defmodule Ash.Type.Decimal do
|
|||
def dump_to_native(value, _) do
|
||||
Ecto.Type.dump(:decimal, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def new(%Decimal{} = v), do: v
|
||||
def new(v), do: Decimal.new(v)
|
||||
end
|
||||
|
|
17
lib/ash/type/decimal_comp.ex
Normal file
17
lib/ash/type/decimal_comp.ex
Normal file
|
@ -0,0 +1,17 @@
|
|||
import Ash.Type.Comparable
|
||||
|
||||
defcomparable left :: Decimal, right :: Integer do
|
||||
Decimal.compare(left, Ash.Type.Decimal.new(right))
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: Decimal do
|
||||
Decimal.compare(left, right)
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: Float do
|
||||
Decimal.compare(Ash.Type.Decimal.new(left), right)
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: BitString do
|
||||
Decimal.compare(left, Ash.Type.Decimal.new(right))
|
||||
end
|
2
mix.exs
2
mix.exs
|
@ -217,7 +217,7 @@ defmodule Ash.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:spark, "~> 0.1 and >= 0.1.28"},
|
||||
{:spark, "~> 0.1 and >= 0.1.29"},
|
||||
{:ecto, "~> 3.7"},
|
||||
{:ets, "~> 0.8.0"},
|
||||
{:decimal, "~> 2.0"},
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -36,7 +36,7 @@
|
|||
"providers": {:hex, :providers, "1.8.1", "70b4197869514344a8a60e2b2a4ef41ca03def43cfb1712ecf076a0f3c62f083", [:rebar3], [{:getopt, "1.0.1", [hex: :getopt, repo: "hexpm", optional: false]}], "hexpm", "e45745ade9c476a9a469ea0840e418ab19360dc44f01a233304e118a44486ba0"},
|
||||
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
|
||||
"sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"},
|
||||
"spark": {:hex, :spark, "0.1.28", "8ce732daa56ad0dc11190b28461f85e71b67c5b61ce4818841bc8fcdbf799676", [:mix], [{:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "11b2d52b473345e2ecb4fe70c76ca8400b2fa9417acb629a6bd92db9d3ff953b"},
|
||||
"spark": {:hex, :spark, "0.1.29", "36f29894fdf8b30aa866a677134654db72807cf02a998aee948a0c5e98a48018", [:mix], [{:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "97ed044974cd47e9286d9fa0fd033620bee6b3569bee27e79d1b9bdb4605371e"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
|
||||
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
|
||||
|
|
|
@ -11,6 +11,8 @@ defmodule Ash.Test.Resource.Validation.CompareTest do
|
|||
uuid_primary_key :id
|
||||
attribute :number_one, :integer
|
||||
attribute :number_two, :integer
|
||||
attribute :number_three, :decimal
|
||||
attribute :number_four, :float
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -43,6 +45,46 @@ defmodule Ash.Test.Resource.Validation.CompareTest do
|
|||
assert :ok = Compare.validate(changeset, opts)
|
||||
end
|
||||
|
||||
test "decimals can be compared against" do
|
||||
{:ok, opts} = Compare.init(attribute: :number_three, greater_than: 0)
|
||||
|
||||
changeset =
|
||||
Post
|
||||
|> Ash.Changeset.new(%{number_three: Decimal.new(1)})
|
||||
|
||||
assert :ok = Compare.validate(changeset, opts)
|
||||
end
|
||||
|
||||
test "floats can be compared against" do
|
||||
{:ok, opts} = Compare.init(attribute: :number_four, greater_than: 0)
|
||||
|
||||
changeset =
|
||||
Post
|
||||
|> Ash.Changeset.new(%{number_four: 1.0})
|
||||
|
||||
assert :ok = Compare.validate(changeset, opts)
|
||||
end
|
||||
|
||||
test "decimals can be compared with" do
|
||||
{:ok, opts} = Compare.init(attribute: :number_one, greater_than: Decimal.new(0))
|
||||
|
||||
changeset =
|
||||
Post
|
||||
|> Ash.Changeset.new(%{number_one: 1})
|
||||
|
||||
assert :ok = Compare.validate(changeset, opts)
|
||||
end
|
||||
|
||||
test "floats can be compared with" do
|
||||
{:ok, opts} = Compare.init(attribute: :number_one, greater_than: 0.0)
|
||||
|
||||
changeset =
|
||||
Post
|
||||
|> Ash.Changeset.new(%{number_one: 1})
|
||||
|
||||
assert :ok = Compare.validate(changeset, opts)
|
||||
end
|
||||
|
||||
test "validate failure against number" do
|
||||
{:ok, opts} = Compare.init(attribute: :number_one, greater_than: 100)
|
||||
changeset = Post |> Ash.Changeset.new(%{number_one: 1})
|
||||
|
|
Loading…
Reference in a new issue