measure.ex (3911B)
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.Measure do 19 @moduledoc """ 20 Semantic meaning for measurements: binds a quantity to its measurement 21 unit. 22 """ 23 24 use Zenflows.DB.Schema 25 26 alias Ecto.Changeset 27 alias Zenflows.DB.Schema 28 alias Zenflows.VF.Unit 29 30 @type t() :: %__MODULE__{ 31 has_unit_id: Zenflows.DB.ID.t(), 32 has_numerical_value: Decimal.t(), 33 } 34 35 @primary_key false 36 embedded_schema do 37 belongs_to :has_unit, Unit 38 field :has_numerical_value, :decimal 39 end 40 41 @doc """ 42 The cast allows you to split the virtual map field into its appropriate 43 fields. An example usage is demonstraited in `Zenflows.VF.RecipeProcess` 44 and `Zenflows.VF.ScenarioDefinition` modules. 45 """ 46 @spec cast(Changeset.t(), atom()) :: Changeset.t() 47 def cast(cset, key) do 48 case Changeset.fetch_change(cset, key) do 49 {:ok, params} -> 50 case changeset(params) do 51 %{valid?: true} = cset_meas -> 52 cset 53 |> Changeset.put_change(:"#{key}_has_unit_id", 54 Changeset.fetch_change!(cset_meas, :has_unit_id)) 55 |> Changeset.put_change(:"#{key}_has_numerical_value", 56 Changeset.fetch_change!(cset_meas, :has_numerical_value)) 57 cset_meas -> 58 Changeset.traverse_errors(cset_meas, fn {msg, opts} -> 59 Regex.replace(~r"%{(\w+)}", msg, fn _, key -> 60 opts |> Keyword.get(:"#{key}", key) |> to_string() 61 end) 62 end) 63 |> Enum.reduce(cset, fn {field, msg}, acc -> 64 Changeset.add_error(acc, key, "#{field}: #{msg}") 65 end) 66 end 67 :error -> 68 # Ecto seems to convert the params' keys to string 69 # whether they were originally string or atom. 70 strkey = "#{key}" 71 case cset.params do 72 # If the field `key` is set to `nil`, 73 # this will set the associated fields to 74 # `nil` as well. 75 %{^strkey => nil} -> 76 cset 77 |> Changeset.force_change(:"#{key}_has_unit_id", nil) 78 |> Changeset.force_change(:"#{key}_has_numerical_value", nil) 79 _ -> 80 cset 81 end 82 end 83 |> case do 84 # if not embedded schema, which doesn't allow `assoc_constraint/3`... 85 %{data: %{__meta__: %Ecto.Schema.Metadata{}}} = cset -> 86 Changeset.assoc_constraint(cset, :"#{key}_has_unit") 87 # this case is most useful when testing, which you use emebedded schemas 88 cset -> 89 cset 90 end 91 end 92 93 @doc """ 94 Propagates the virtual map field `key` with the associated fields 95 as a %Measure{} struct. Useful for GraphQL types as can be seen in 96 `Zenflows.VF.ScenarioDefinition.Type` and `Zenflows.VF.RecipeProcess.Type`. 97 """ 98 @spec preload(Schema.t(), atom()) :: Schema.t() 99 def preload(schema, key) do 100 has_unit_id = Map.get(schema, :"#{key}_has_unit_id") 101 has_numerical_value = Map.get(schema, :"#{key}_has_numerical_value") 102 if has_unit_id && has_numerical_value do 103 %{schema | key => %__MODULE__{ 104 has_unit_id: has_unit_id, 105 has_numerical_value: has_numerical_value, 106 }} 107 else 108 schema 109 end 110 end 111 112 @cast ~w[has_unit_id has_numerical_value]a 113 @reqr @cast 114 115 @spec changeset(Schema.params()) :: Changeset.t() 116 defp changeset(params) do 117 %__MODULE__{} 118 |> Changeset.cast(params, @cast) 119 |> Changeset.validate_required(@reqr) 120 |> Changeset.validate_number(:has_numerical_value, greater_than: 0) 121 end 122 end