-
Notifications
You must be signed in to change notification settings - Fork 4
/
changeset_utils.ex
181 lines (154 loc) · 5.52 KB
/
changeset_utils.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
defmodule FatUtils.Changeset do
@moduledoc """
Provides different changeset methods.
"""
@doc """
Takes changeset and check if xor keys are present and return changeset error and also checks if xor keys are empty in the record and return error.
"""
def validate_xor(changeset, record, xor_keys, _options \\ []) do
changeset =
if FatUtils.Map.has_all_keys?(changeset.changes, xor_keys) do
error_msg = Enum.join(xor_keys, " XOR ")
Enum.reduce(xor_keys, changeset, fn xor_key, acc ->
Ecto.Changeset.add_error(acc, xor_key, error_msg)
end)
else
changeset
end
if !FatUtils.Map.has_any_of_keys?(changeset.changes, xor_keys) &&
FatUtils.Map.has_all_val_equal_to?(record, xor_keys, nil) do
require_msg = Enum.join(xor_keys, " XOR ") <> " fields can not be empty at the same time"
Enum.reduce(xor_keys, changeset, fn xor_key, acc ->
Ecto.Changeset.validate_required(
acc,
[xor_key],
message: require_msg
)
end)
else
changeset
end
end
@doc """
Takes changeset and check if one of the key is present and return changeset error.
"""
def require_only_one_of(changeset, _record, single_keys, _options \\ []) do
keys_count =
Enum.reduce(single_keys, 0, fn sk, acc ->
if Ecto.Changeset.get_field(changeset, sk) do
acc + 1
else
acc
end
end)
if keys_count == 1 do
changeset
else
first_keys = Enum.drop(single_keys, -2)
first_keys = Enum.join(first_keys, ",")
last_two_elements = Enum.slice(single_keys, -2, 2)
last_two_elements = Enum.join(last_two_elements, " or ")
error_msg = first_keys <> ", " <> last_two_elements
error_msg = String.trim_leading(error_msg, ", ")
Enum.reduce(single_keys, changeset, fn single_key, acc ->
Ecto.Changeset.add_error(acc, single_key, "only one of " <> error_msg <> " is required")
end)
end
end
@doc """
Takes changeset and check if none of or keys are present and return changeset error.
"""
def require_or(changeset, _record, or_keys, _options \\ []) do
if FatUtils.Map.has_any_of_keys?(changeset.changes, or_keys) do
changeset
else
error_msg = Enum.join(or_keys, " OR ")
Enum.reduce(or_keys, changeset, fn or_key, acc ->
Ecto.Changeset.add_error(acc, or_key, error_msg <> " required")
end)
end
end
@doc """
If specific key is present in the changeset then other key passed as required will be set as required in the changeset.
"""
def require_if_change_present(changeset, if_change_key: if_change_key, require_key: require_key) do
if Map.has_key?(changeset.changes, if_change_key) do
Ecto.Changeset.validate_required(
changeset,
require_key
)
else
changeset
end
end
@doc """
Compare datetime fields and return error if start date is before end date and it can also compare time by passing compare_type: :time in options.
"""
def validate_before(changeset, start_date_key, end_date_key, options \\ []) do
start_date = Ecto.Changeset.get_field(changeset, start_date_key)
end_date = Ecto.Changeset.get_field(changeset, end_date_key)
{error_message_title, error_message} =
error_msg_title(options, start_date_key, "must be before #{end_date_key}")
cond do
options[:compare_type] == :time ->
if start_date && end_date && Time.diff(start_date, end_date) >= 0 do
add_error(changeset, error_message_title, error_message)
else
changeset
end
true ->
if start_date && end_date && DateTime.diff(start_date, end_date) >= 0 do
add_error(changeset, error_message_title, error_message)
else
changeset
end
end
end
@doc """
Compare datetime fields and return error if start date is before or equal end date and it can also compare time by passing compare_type: :time in options.
"""
def validate_before_equal(changeset, start_date_key, end_date_key, options \\ []) do
start_date = Ecto.Changeset.get_field(changeset, start_date_key)
end_date = Ecto.Changeset.get_field(changeset, end_date_key)
{error_message_title, error_message} =
error_msg_title(options, start_date_key, "must be before or equal to #{end_date_key}")
cond do
options[:compare_type] == :time ->
if start_date && end_date && Time.diff(start_date, end_date) > 0 do
add_error(changeset, error_message_title, error_message)
else
changeset
end
true ->
if start_date && end_date && DateTime.diff(start_date, end_date) > 0 do
add_error(changeset, error_message_title, error_message)
else
changeset
end
end
end
@doc """
Add custom error message with field and error message.
"""
def error_msg_title(options, field_key, default_error_msg) do
error_message_title =
if options[:error_message_title] do
options[:error_message_title]
else
field_key
end
error_message =
if options[:error_message] do
options[:error_message]
else
default_error_msg
end
{error_message_title, error_message}
end
@doc """
Add custom error to changeset. If custom message is not provided default one will be used.
"""
def add_error(changeset, error_message_title, error_message \\ "is invalid") do
Ecto.Changeset.add_error(changeset, error_message_title, error_message)
end
end