• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vinniefranco / exandra / 41364f3f7d0ed89b7a6c1a7ebc636887e0634085-PR-77

03 Jan 2025 09:50AM UTC coverage: 87.356% (-0.1%) from 87.5%
41364f3f7d0ed89b7a6c1a7ebc636887e0634085-PR-77

Pull #77

github

noaccOS
fix(tuple): handle nil casting

load/dump already handle nil this way.

default ecto modules cast to nil for nil values,
tuples should behave the same way
Pull Request #77: fix(tuple): handle nil casting

1 of 1 new or added line in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

532 of 609 relevant lines covered (87.36%)

40.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.5
/lib/exandra/tuple.ex
1
defmodule Exandra.Tuple do
2
  opts_schema = [
3
    types: [
4
      type: {:list, :any},
5
      required: true,
6
      doc: "The types of the elements in the tuple."
7
    ],
8
    field: [
9
      type: :atom,
10
      doc: false
11
    ],
12
    schema: [
13
      type: :atom,
14
      doc: false
15
    ]
16
  ]
17

18
  @moduledoc """
19
  `Ecto.ParameterizedType` for tuples.
20

21
  *Available since v0.11.0*.
22

23
  ## Options
24

25
  #{NimbleOptions.docs(opts_schema)}
26

27
  ## Examples
28

29
      schema "user" do
30
        field :favorite_movie_with_score, Exandra.Tuple, types: [:string, :integer]
31
      end
32

33
  """
34

35
  @moduledoc since: "0.11.0"
36

37
  use Ecto.ParameterizedType
38

39
  alias Exandra.Types
40

41
  @type t() :: Tuple.t()
42

43
  @opts_schema NimbleOptions.new!(opts_schema)
44

45
  # Made public for testing.
46
  @doc false
47
  def params(embed), do: %{embed: embed}
4✔
48

49
  @impl Ecto.ParameterizedType
50
  def type(_opts), do: :exandra_tuple
16✔
51

52
  @impl Ecto.ParameterizedType
53
  def init(opts) do
54
    {types, opts} = Keyword.pop_first(opts, :types)
10✔
55

56
    if types == [] do
10✔
57
      raise ArgumentError, "CQL tuples must have at least one element, got: []"
×
58
    end
59

60
    types = types |> Enum.map(&Types.check_type!(__MODULE__, &1, opts))
10✔
61

62
    opts
63
    |> Keyword.put(:types, types)
64
    |> Keyword.take(Keyword.keys(@opts_schema.schema))
10✔
65
    |> NimbleOptions.validate!(@opts_schema)
66
    |> Map.new()
10✔
67
  end
68

69
  @impl Ecto.ParameterizedType
70
  def cast(tuple, opts) when is_tuple(tuple), do: tuple |> Tuple.to_list() |> cast(opts)
16✔
71

72
  def cast(list, %{types: types}) when is_list(list) and length(list) == length(types) do
73
    casted =
18✔
74
      list
75
      |> Enum.zip(types)
76
      |> Enum.reduce_while([], fn {element, type}, acc ->
77
        case Ecto.Type.cast(type, element) do
34✔
78
          {:ok, value} -> {:cont, [value | acc]}
30✔
79
          error -> {:halt, error}
4✔
80
        end
81
      end)
82

83
    if is_list(casted) do
18✔
84
      casted =
14✔
85
        casted
86
        |> Enum.reverse()
87
        |> List.to_tuple()
88

89
      {:ok, casted}
90
    else
91
      casted
4✔
92
    end
93
  end
94

95
  def cast(nil, _), do: {:ok, nil}
2✔
96

97
  def cast(val, %{types: [type]}) do
98
    case Ecto.Type.cast(type, val) do
4✔
99
      {:ok, casted} -> {:ok, {casted}}
4✔
100
      err -> err
×
101
    end
102
  end
103

UNCOV
104
  def cast(_key, _val), do: :error
×
105

106
  @impl Ecto.ParameterizedType
107
  def load(value, loader \\ &Ecto.Type.load/2, params)
4✔
108

109
  def load(tuple, loader, %{types: types}) when is_tuple(tuple) do
110
    loaded =
12✔
111
      tuple
112
      |> Tuple.to_list()
113
      |> Enum.zip(types)
114
      |> Enum.reduce_while([], fn {element, type}, acc ->
115
        case Ecto.Type.load(type, element, loader) do
24✔
116
          {:ok, value} -> {:cont, [value | acc]}
24✔
117
          error -> {:halt, error}
×
118
        end
119
      end)
120

121
    if is_list(loaded) do
12✔
122
      loaded =
12✔
123
        loaded
124
        |> Enum.reverse()
125
        |> List.to_tuple()
126

127
      {:ok, loaded}
128
    else
129
      loaded
×
130
    end
131
  end
132

133
  def load(nil, _, _), do: {:ok, nil}
6✔
134

135
  def load(_field_name, loader, field) do
136
    load(nil, loader, field)
2✔
137
  end
138

139
  @impl Ecto.ParameterizedType
140
  def dump(nil, _dumper, _opts), do: {:ok, nil}
2✔
141

142
  def dump(tuple, dumper, %{types: types}) do
143
    dumped =
12✔
144
      tuple
145
      |> Tuple.to_list()
146
      |> Enum.zip(types)
147
      |> Enum.reduce_while([], fn {element, type}, acc ->
148
        case Ecto.Type.dump(type, element, dumper) do
26✔
149
          {:ok, value} -> {:cont, [value | acc]}
26✔
150
          error -> {:halt, error}
×
151
        end
152
      end)
153

154
    if is_list(dumped) do
12✔
155
      dumped =
12✔
156
        dumped
157
        |> Enum.reverse()
158
        |> List.to_tuple()
159

160
      {:ok, dumped}
161
    else
162
      dumped
×
163
    end
164
  end
165

166
  # From Ecto.Type
167
  @doc false
168
  def embed_as(_format), do: :self
2✔
169
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc