duration.ex (3429B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule Zenflows.VF.Duration do 19 @moduledoc """ 20 Represents an interval between two DateTime values. 21 """ 22 23 use Zenflows.DB.Schema 24 25 alias Ecto.Changeset 26 alias Zenflows.DB.Schema 27 alias Zenflows.VF.TimeUnitEnum 28 29 @type t() :: %__MODULE__{ 30 unit_type: TimeUnitEnum.t(), 31 numeric_duration: Decimal.t(), 32 } 33 34 @primary_key false 35 embedded_schema do 36 field :unit_type, TimeUnitEnum 37 field :numeric_duration, :decimal 38 end 39 40 @doc """ 41 The cast allows you to split the virtual map field into its appropriate 42 fields. An example usage is demonstraited in `Zenflows.VF.RecipeProcess` 43 and `Zenflows.VF.ScenarioDefinition` modules. 44 """ 45 @spec cast(Changeset.t(), atom()) :: Changeset.t() 46 def cast(cset, key) do 47 case Changeset.fetch_change(cset, key) do 48 {:ok, params} -> 49 case changeset(params) do 50 %{valid?: true} = cset_dur -> 51 cset 52 |> Changeset.put_change(:"#{key}_unit_type", 53 Changeset.fetch_change!(cset_dur, :unit_type)) 54 |> Changeset.put_change(:"#{key}_numeric_duration", 55 Changeset.fetch_change!(cset_dur, :numeric_duration)) 56 cset_dur -> 57 cset_dur.errors 58 |> Enum.reduce(cset, fn {field, {msg, _opts}}, acc -> 59 Changeset.add_error(acc, key, "#{field}: #{msg}") 60 end) 61 end 62 :error -> 63 # Ecto seems to convert the params' keys to string 64 # whether they were originally string or atom. 65 strkey = "#{key}" 66 case cset.params do 67 # If, for example, `key` is 68 # `:has_duration`, and it's set to `nil`, 69 # this will set the associated fields to 70 # `nil` as well. 71 %{^strkey => nil} -> 72 cset 73 |> Changeset.force_change(:"#{key}_unit_type", nil) 74 |> Changeset.force_change(:"#{key}_numeric_duration", nil) 75 _ -> 76 cset 77 end 78 end 79 end 80 81 @doc """ 82 Propagates the virtual map field `key` with the associated fields 83 as a %Duration{} struct. Useful for GraphQL types as can be seen in 84 `Zenflows.VF.ScenarioDefinition.Type` and `Zenflows.VF.RecipeProcess.Type`. 85 """ 86 @spec preload(Schema.t(), atom()) :: Schema.t() 87 def preload(schema, key) do 88 unit_type = Map.get(schema, :"#{key}_unit_type") 89 numeric_duration = Map.get(schema, :"#{key}_numeric_duration") 90 if unit_type && numeric_duration do 91 %{schema | key => %__MODULE__{ 92 unit_type: unit_type, 93 numeric_duration: numeric_duration, 94 }} 95 else 96 schema 97 end 98 end 99 100 @cast ~w[unit_type numeric_duration]a 101 @reqr @cast 102 103 @spec changeset(Schema.params()) :: Changeset.t() 104 defp changeset(params) do 105 %__MODULE__{} 106 |> Changeset.cast(params, @cast) 107 |> Changeset.validate_required(@reqr) 108 |> Changeset.validate_number(:numeric_duration, greater_than_or_equal_to: 0) 109 end 110 end