zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

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