Skip to content

Commit 3f17c80

Browse files
committed
use ecto type instead
1 parent 8695f69 commit 3f17c80

File tree

8 files changed

+118
-167
lines changed

8 files changed

+118
-167
lines changed

lib/realtime_web/channels/payloads/broadcast.ex

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ defmodule RealtimeWeb.Channels.Payloads.Broadcast do
55
use Ecto.Schema
66
import Ecto.Changeset
77
alias RealtimeWeb.Channels.Payloads.Join
8-
alias RealtimeWeb.Channels.Payloads.ChangesetNormalizer
8+
alias RealtimeWeb.Channels.Payloads.FlexibleBoolean
99

1010
embedded_schema do
11-
field :ack, :boolean, default: false
12-
field :self, :boolean, default: false
11+
field :ack, FlexibleBoolean, default: false
12+
field :self, FlexibleBoolean, default: false
1313
embeds_one :replay, RealtimeWeb.Channels.Payloads.Broadcast.Replay
1414
end
1515

1616
def changeset(broadcast, attrs) do
17-
attrs = ChangesetNormalizer.normalize_boolean_fields(attrs, [:ack, :self])
18-
1917
broadcast
2018
|> cast(attrs, [:ack, :self], message: &Join.error_message/2)
2119
|> cast_embed(:replay, invalid_message: "unable to parse, expected a map")

lib/realtime_web/channels/payloads/changeset_normalizer.ex

Lines changed: 0 additions & 48 deletions
This file was deleted.

lib/realtime_web/channels/payloads/config.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ defmodule RealtimeWeb.Channels.Payloads.Config do
88
alias RealtimeWeb.Channels.Payloads.Broadcast
99
alias RealtimeWeb.Channels.Payloads.Presence
1010
alias RealtimeWeb.Channels.Payloads.PostgresChange
11-
alias RealtimeWeb.Channels.Payloads.ChangesetNormalizer
11+
alias RealtimeWeb.Channels.Payloads.FlexibleBoolean
1212

1313
embedded_schema do
1414
embeds_one :broadcast, Broadcast
1515
embeds_one :presence, Presence
1616
embeds_many :postgres_changes, PostgresChange
17-
field :private, :boolean, default: false
17+
field :private, FlexibleBoolean, default: false
1818
end
1919

2020
def changeset(config, attrs) do
@@ -25,7 +25,6 @@ defmodule RealtimeWeb.Channels.Payloads.Config do
2525
{k, v} -> {k, v}
2626
end)
2727
|> Map.new()
28-
|> ChangesetNormalizer.normalize_boolean_fields([:private])
2928

3029
config
3130
|> cast(attrs, [:private], message: &Join.error_message/2)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
defmodule RealtimeWeb.Channels.Payloads.FlexibleBoolean do
2+
@moduledoc """
3+
Custom Ecto type that handles boolean values coming as strings.
4+
5+
Accepts:
6+
- Boolean values (true/false) - used as-is
7+
- Strings "true", "True", "TRUE", etc. - cast to true
8+
- Strings "false", "False", "FALSE", etc. - cast to false
9+
- Any other value - returns error
10+
"""
11+
use Ecto.Type
12+
13+
@impl true
14+
def type, do: :boolean
15+
16+
@impl true
17+
def cast(value) when is_boolean(value), do: {:ok, value}
18+
19+
def cast(value) when is_binary(value) do
20+
case String.downcase(value) do
21+
"true" -> {:ok, true}
22+
"false" -> {:ok, false}
23+
_ -> :error
24+
end
25+
end
26+
27+
def cast(_), do: :error
28+
29+
@impl true
30+
def load(value), do: {:ok, value}
31+
32+
@impl true
33+
def dump(value) when is_boolean(value), do: {:ok, value}
34+
def dump(_), do: :error
35+
end

lib/realtime_web/channels/payloads/join.ex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ defmodule RealtimeWeb.Channels.Payloads.Join do
5252
type = Keyword.get(meta, :type)
5353

5454
if type,
55-
do: "unable to parse, expected #{type}",
55+
do: "unable to parse, expected #{format_type(type)}",
5656
else: "unable to parse"
5757
end
58+
59+
defp format_type(RealtimeWeb.Channels.Payloads.FlexibleBoolean), do: :boolean
60+
defp format_type(type), do: type
5861
end

lib/realtime_web/channels/payloads/presence.ex

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ defmodule RealtimeWeb.Channels.Payloads.Presence do
55
use Ecto.Schema
66
import Ecto.Changeset
77
alias RealtimeWeb.Channels.Payloads.Join
8-
alias RealtimeWeb.Channels.Payloads.ChangesetNormalizer
8+
alias RealtimeWeb.Channels.Payloads.FlexibleBoolean
99

1010
embedded_schema do
11-
field :enabled, :boolean, default: true
11+
field :enabled, FlexibleBoolean, default: true
1212
field :key, :any, default: UUID.uuid1(), virtual: true
1313
end
1414

1515
def changeset(presence, attrs) do
16-
attrs = ChangesetNormalizer.normalize_boolean_fields(attrs, [:enabled])
17-
1816
cast(presence, attrs, [:enabled, :key], message: &Join.error_message/2)
1917
end
2018
end

test/realtime_web/channels/payloads/changeset_normalizer_test.exs

Lines changed: 0 additions & 106 deletions
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
defmodule RealtimeWeb.Channels.Payloads.FlexibleBooleanTest do
2+
use ExUnit.Case, async: true
3+
4+
alias RealtimeWeb.Channels.Payloads.FlexibleBoolean
5+
6+
describe "type/0" do
7+
test "returns :boolean" do
8+
assert FlexibleBoolean.type() == :boolean
9+
end
10+
end
11+
12+
describe "cast/1" do
13+
test "casts boolean true as-is" do
14+
assert FlexibleBoolean.cast(true) == {:ok, true}
15+
end
16+
17+
test "casts boolean false as-is" do
18+
assert FlexibleBoolean.cast(false) == {:ok, false}
19+
end
20+
21+
test "casts string 'true' in any case to boolean true" do
22+
assert FlexibleBoolean.cast("true") == {:ok, true}
23+
assert FlexibleBoolean.cast("True") == {:ok, true}
24+
assert FlexibleBoolean.cast("TRUE") == {:ok, true}
25+
assert FlexibleBoolean.cast("tRuE") == {:ok, true}
26+
end
27+
28+
test "casts string 'false' in any case to boolean false" do
29+
assert FlexibleBoolean.cast("false") == {:ok, false}
30+
assert FlexibleBoolean.cast("False") == {:ok, false}
31+
assert FlexibleBoolean.cast("FALSE") == {:ok, false}
32+
assert FlexibleBoolean.cast("fAlSe") == {:ok, false}
33+
end
34+
35+
test "returns error for invalid string values" do
36+
assert FlexibleBoolean.cast("test") == :error
37+
assert FlexibleBoolean.cast("yes") == :error
38+
assert FlexibleBoolean.cast("no") == :error
39+
assert FlexibleBoolean.cast("1") == :error
40+
assert FlexibleBoolean.cast("0") == :error
41+
assert FlexibleBoolean.cast("") == :error
42+
end
43+
44+
test "returns error for non-boolean, non-string values" do
45+
assert FlexibleBoolean.cast(1) == :error
46+
assert FlexibleBoolean.cast(0) == :error
47+
assert FlexibleBoolean.cast(nil) == :error
48+
assert FlexibleBoolean.cast(%{}) == :error
49+
assert FlexibleBoolean.cast([]) == :error
50+
end
51+
end
52+
53+
describe "load/1" do
54+
test "loads boolean values" do
55+
assert FlexibleBoolean.load(true) == {:ok, true}
56+
assert FlexibleBoolean.load(false) == {:ok, false}
57+
end
58+
end
59+
60+
describe "dump/1" do
61+
test "dumps boolean values" do
62+
assert FlexibleBoolean.dump(true) == {:ok, true}
63+
assert FlexibleBoolean.dump(false) == {:ok, false}
64+
end
65+
66+
test "returns error for non-boolean values" do
67+
assert FlexibleBoolean.dump("true") == :error
68+
assert FlexibleBoolean.dump(1) == :error
69+
assert FlexibleBoolean.dump(nil) == :error
70+
end
71+
end
72+
end

0 commit comments

Comments
 (0)