Skip to content

Commit 9d1d06c

Browse files
committed
Ensure mix phx.gen.context is deterministic on Erlang/OTP 26
1 parent 9fb55d2 commit 9d1d06c

File tree

1 file changed

+142
-76
lines changed

1 file changed

+142
-76
lines changed

lib/mix/phoenix/schema.ex

+142-76
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ defmodule Mix.Phoenix.Schema do
3636
route_prefix: nil,
3737
api_route_prefix: nil,
3838
migration_module: nil,
39-
fixture_unique_functions: %{},
40-
fixture_params: %{},
39+
fixture_unique_functions: [],
40+
fixture_params: [],
4141
prefix: nil
4242

4343
@valid_types [
@@ -69,15 +69,15 @@ defmodule Mix.Phoenix.Schema do
6969
end
7070

7171
def new(schema_name, schema_plural, cli_attrs, opts) do
72-
ctx_app = opts[:context_app] || Mix.Phoenix.context_app()
73-
otp_app = Mix.Phoenix.otp_app()
74-
opts = Keyword.merge(Application.get_env(otp_app, :generators, []), opts)
75-
base = Mix.Phoenix.context_base(ctx_app)
76-
basename = Phoenix.Naming.underscore(schema_name)
77-
module = Module.concat([base, schema_name])
78-
repo = opts[:repo] || Module.concat([base, "Repo"])
79-
file = Mix.Phoenix.context_lib_path(ctx_app, basename <> ".ex")
80-
table = opts[:table] || schema_plural
72+
ctx_app = opts[:context_app] || Mix.Phoenix.context_app()
73+
otp_app = Mix.Phoenix.otp_app()
74+
opts = Keyword.merge(Application.get_env(otp_app, :generators, []), opts)
75+
base = Mix.Phoenix.context_base(ctx_app)
76+
basename = Phoenix.Naming.underscore(schema_name)
77+
module = Module.concat([base, schema_name])
78+
repo = opts[:repo] || Module.concat([base, "Repo"])
79+
file = Mix.Phoenix.context_lib_path(ctx_app, basename <> ".ex")
80+
table = opts[:table] || schema_plural
8181
{cli_attrs, uniques, redacts} = extract_attr_flags(cli_attrs)
8282
{assocs, attrs} = partition_attrs_and_assocs(module, attrs(cli_attrs))
8383
types = types(attrs)
@@ -96,11 +96,13 @@ defmodule Mix.Phoenix.Schema do
9696
collection = if schema_plural == singular, do: singular <> "_collection", else: schema_plural
9797
string_attr = string_attr(types)
9898
create_params = params(attrs, :create)
99+
99100
default_params_key =
100101
case Enum.at(create_params, 0) do
101102
{key, _} -> key
102103
nil -> :some_field
103104
end
105+
104106
fixture_unique_functions = fixture_unique_functions(singular, uniques, attrs)
105107

106108
%Schema{
@@ -141,7 +143,7 @@ defmodule Mix.Phoenix.Schema do
141143
context_app: ctx_app,
142144
generate?: generate?,
143145
migration_module: migration_module(),
144-
fixture_unique_functions: fixture_unique_functions,
146+
fixture_unique_functions: Enum.sort(fixture_unique_functions),
145147
fixture_params: fixture_params(attrs, fixture_unique_functions),
146148
prefix: opts[:prefix]
147149
}
@@ -158,11 +160,12 @@ defmodule Mix.Phoenix.Schema do
158160
end
159161

160162
def extract_attr_flags(cli_attrs) do
161-
{attrs, uniques, redacts} = Enum.reduce(cli_attrs, {[], [], []}, fn attr, {attrs, uniques, redacts} ->
162-
[attr_name | rest] = String.split(attr, ":")
163-
attr_name = String.to_atom(attr_name)
164-
split_flags(Enum.reverse(rest), attr_name, attrs, uniques, redacts)
165-
end)
163+
{attrs, uniques, redacts} =
164+
Enum.reduce(cli_attrs, {[], [], []}, fn attr, {attrs, uniques, redacts} ->
165+
[attr_name | rest] = String.split(attr, ":")
166+
attr_name = String.to_atom(attr_name)
167+
split_flags(Enum.reverse(rest), attr_name, attrs, uniques, redacts)
168+
end)
166169

167170
{Enum.reverse(attrs), uniques, redacts}
168171
end
@@ -174,7 +177,7 @@ defmodule Mix.Phoenix.Schema do
174177
do: split_flags(rest, name, attrs, uniques, [name | redacts])
175178

176179
defp split_flags(rest, name, attrs, uniques, redacts),
177-
do: {[Enum.join([name | Enum.reverse(rest)], ":") | attrs ], uniques, redacts}
180+
do: {[Enum.join([name | Enum.reverse(rest)], ":") | attrs], uniques, redacts}
178181

179182
@doc """
180183
Parses the attrs as received by generators.
@@ -247,8 +250,10 @@ defmodule Mix.Phoenix.Schema do
247250
end)
248251
end
249252

250-
def type_and_opts_for_schema({:enum, opts}), do: ~s|Ecto.Enum, values: #{inspect Keyword.get(opts, :values)}|
251-
def type_and_opts_for_schema(other), do: inspect other
253+
def type_and_opts_for_schema({:enum, opts}),
254+
do: ~s|Ecto.Enum, values: #{inspect(Keyword.get(opts, :values))}|
255+
256+
def type_and_opts_for_schema(other), do: inspect(other)
252257

253258
def maybe_redact_field(true), do: ", redact: true"
254259
def maybe_redact_field(false), do: ""
@@ -261,11 +266,13 @@ defmodule Mix.Phoenix.Schema do
261266
|> Map.fetch!(field)
262267
|> inspect_value(value)
263268
end
269+
264270
defp inspect_value(:decimal, value), do: "Decimal.new(\"#{value}\")"
265271
defp inspect_value(_type, value), do: inspect(value)
266272

267273
defp list_to_attr([key]), do: {String.to_atom(key), :string}
268274
defp list_to_attr([key, value]), do: {String.to_atom(key), String.to_atom(value)}
275+
269276
defp list_to_attr([key, comp, value]) do
270277
{String.to_atom(key), {String.to_atom(comp), String.to_atom(value)}}
271278
end
@@ -274,55 +281,103 @@ defmodule Mix.Phoenix.Schema do
274281

275282
defp type_to_default(key, t, :create) do
276283
case t do
277-
{:array, type} -> build_array_values(type, :create)
278-
{:enum, values} -> build_enum_values(values, :create)
279-
:integer -> 42
280-
:float -> 120.5
281-
:decimal -> "120.5"
282-
:boolean -> true
283-
:map -> %{}
284-
:text -> "some #{key}"
285-
:date -> Date.add(Date.utc_today(), -1)
286-
:time -> ~T[14:00:00]
287-
:time_usec -> ~T[14:00:00.000000]
288-
:uuid -> "7488a646-e31f-11e4-aace-600308960662"
289-
:utc_datetime -> DateTime.add(build_utc_datetime(), -@one_day_in_seconds, :second, Calendar.UTCOnlyTimeZoneDatabase)
290-
:utc_datetime_usec -> DateTime.add(build_utc_datetime_usec(), -@one_day_in_seconds, :second, Calendar.UTCOnlyTimeZoneDatabase)
291-
:naive_datetime -> NaiveDateTime.add(build_utc_naive_datetime(), -@one_day_in_seconds)
292-
:naive_datetime_usec -> NaiveDateTime.add(build_utc_naive_datetime_usec(), -@one_day_in_seconds)
293-
_ -> "some #{key}"
284+
{:array, type} ->
285+
build_array_values(type, :create)
286+
287+
{:enum, values} ->
288+
build_enum_values(values, :create)
289+
290+
:integer ->
291+
42
292+
293+
:float ->
294+
120.5
295+
296+
:decimal ->
297+
"120.5"
298+
299+
:boolean ->
300+
true
301+
302+
:map ->
303+
%{}
304+
305+
:text ->
306+
"some #{key}"
307+
308+
:date ->
309+
Date.add(Date.utc_today(), -1)
310+
311+
:time ->
312+
~T[14:00:00]
313+
314+
:time_usec ->
315+
~T[14:00:00.000000]
316+
317+
:uuid ->
318+
"7488a646-e31f-11e4-aace-600308960662"
319+
320+
:utc_datetime ->
321+
DateTime.add(
322+
build_utc_datetime(),
323+
-@one_day_in_seconds,
324+
:second,
325+
Calendar.UTCOnlyTimeZoneDatabase
326+
)
327+
328+
:utc_datetime_usec ->
329+
DateTime.add(
330+
build_utc_datetime_usec(),
331+
-@one_day_in_seconds,
332+
:second,
333+
Calendar.UTCOnlyTimeZoneDatabase
334+
)
335+
336+
:naive_datetime ->
337+
NaiveDateTime.add(build_utc_naive_datetime(), -@one_day_in_seconds)
338+
339+
:naive_datetime_usec ->
340+
NaiveDateTime.add(build_utc_naive_datetime_usec(), -@one_day_in_seconds)
341+
342+
_ ->
343+
"some #{key}"
294344
end
295345
end
346+
296347
defp type_to_default(key, t, :update) do
297348
case t do
298-
{:array, type} -> build_array_values(type, :update)
299-
{:enum, values} -> build_enum_values(values, :update)
300-
:integer -> 43
301-
:float -> 456.7
302-
:decimal -> "456.7"
303-
:boolean -> false
304-
:map -> %{}
305-
:text -> "some updated #{key}"
306-
:date -> Date.utc_today()
307-
:time -> ~T[15:01:01]
308-
:time_usec -> ~T[15:01:01.000000]
309-
:uuid -> "7488a646-e31f-11e4-aace-600308960668"
310-
:utc_datetime -> build_utc_datetime()
311-
:utc_datetime_usec -> build_utc_datetime_usec()
312-
:naive_datetime -> build_utc_naive_datetime()
313-
:naive_datetime_usec -> build_utc_naive_datetime_usec()
314-
_ -> "some updated #{key}"
349+
{:array, type} -> build_array_values(type, :update)
350+
{:enum, values} -> build_enum_values(values, :update)
351+
:integer -> 43
352+
:float -> 456.7
353+
:decimal -> "456.7"
354+
:boolean -> false
355+
:map -> %{}
356+
:text -> "some updated #{key}"
357+
:date -> Date.utc_today()
358+
:time -> ~T[15:01:01]
359+
:time_usec -> ~T[15:01:01.000000]
360+
:uuid -> "7488a646-e31f-11e4-aace-600308960668"
361+
:utc_datetime -> build_utc_datetime()
362+
:utc_datetime_usec -> build_utc_datetime_usec()
363+
:naive_datetime -> build_utc_naive_datetime()
364+
:naive_datetime_usec -> build_utc_naive_datetime_usec()
365+
_ -> "some updated #{key}"
315366
end
316367
end
317368

318369
defp build_array_values(:string, :create),
319-
do: Enum.map([1,2], &("option#{&1}"))
370+
do: Enum.map([1, 2], &"option#{&1}")
371+
320372
defp build_array_values(:integer, :create),
321-
do: [1,2]
373+
do: [1, 2]
374+
322375
defp build_array_values(:string, :update),
323376
do: ["option1"]
377+
324378
defp build_array_values(:integer, :update),
325379
do: [1]
380+
326381
defp build_array_values(_, _),
327382
do: []
328383

@@ -354,36 +409,44 @@ defmodule Mix.Phoenix.Schema do
354409
"""
355410

356411
defp validate_attr!({name, :datetime}), do: validate_attr!({name, :naive_datetime})
412+
357413
defp validate_attr!({name, :array}) do
358-
Mix.raise """
414+
Mix.raise("""
359415
Phoenix generators expect the type of the array to be given to #{name}:array.
360416
For example:
361417
362418
mix phx.gen.schema Post posts settings:array:string
363-
"""
419+
""")
364420
end
365-
defp validate_attr!({_name, :enum}), do: Mix.raise @enum_missing_value_error
421+
422+
defp validate_attr!({_name, :enum}), do: Mix.raise(@enum_missing_value_error)
366423
defp validate_attr!({_name, type} = attr) when type in @valid_types, do: attr
367424
defp validate_attr!({_name, {:enum, _vals}} = attr), do: attr
368425
defp validate_attr!({_name, {type, _}} = attr) when type in @valid_types, do: attr
426+
369427
defp validate_attr!({_, type}) do
370-
Mix.raise "Unknown type `#{inspect type}` given to generator. " <>
371-
"The supported types are: #{@valid_types |> Enum.sort() |> Enum.join(", ")}"
428+
Mix.raise(
429+
"Unknown type `#{inspect(type)}` given to generator. " <>
430+
"The supported types are: #{@valid_types |> Enum.sort() |> Enum.join(", ")}"
431+
)
372432
end
373433

374434
defp partition_attrs_and_assocs(schema_module, attrs) do
375435
{assocs, attrs} =
376436
Enum.split_with(attrs, fn
377437
{_, {:references, _}} ->
378438
true
439+
379440
{key, :references} ->
380-
Mix.raise """
441+
Mix.raise("""
381442
Phoenix generators expect the table to be given to #{key}:references.
382443
For example:
383444
384445
mix phx.gen.schema Comment comments body:text post_id:references:posts
385-
"""
386-
_ -> false
446+
""")
447+
448+
_ ->
449+
false
387450
end)
388451

389452
assocs =
@@ -399,8 +462,8 @@ defmodule Mix.Phoenix.Schema do
399462

400463
defp schema_defaults(attrs) do
401464
Enum.into(attrs, %{}, fn
402-
{key, :boolean} -> {key, ", default: false"}
403-
{key, _} -> {key, ""}
465+
{key, :boolean} -> {key, ", default: false"}
466+
{key, _} -> {key, ""}
404467
end)
405468
end
406469

@@ -428,9 +491,10 @@ defmodule Mix.Phoenix.Schema do
428491

429492
defp schema_type(:text), do: :string
430493
defp schema_type(:uuid), do: Ecto.UUID
494+
431495
defp schema_type(val) do
432496
if Code.ensure_loaded?(Ecto.Type) and not Ecto.Type.primitive?(val) do
433-
Mix.raise "Unknown type `#{val}` given to generator"
497+
Mix.raise("Unknown type `#{val}` given to generator")
434498
else
435499
val
436500
end
@@ -444,14 +508,14 @@ defmodule Mix.Phoenix.Schema do
444508
|> Enum.uniq_by(fn {key, _} -> key end)
445509
|> Enum.map(fn
446510
{key, false} -> "create index(:#{table}, [:#{key}])"
447-
{key, true} -> "create unique_index(:#{table}, [:#{key}])"
511+
{key, true} -> "create unique_index(:#{table}, [:#{key}])"
448512
end)
449513
end
450514

451515
defp migration_defaults(attrs) do
452516
Enum.into(attrs, %{}, fn
453-
{key, :boolean} -> {key, ", default: false, null: false"}
454-
{key, _} -> {key, ""}
517+
{key, :boolean} -> {key, ", default: false, null: false"}
518+
{key, _} -> {key, ""}
455519
end)
456520
end
457521

@@ -482,7 +546,7 @@ defmodule Mix.Phoenix.Schema do
482546
defp migration_module do
483547
case Application.get_env(:ecto_sql, :migration_module, Ecto.Migration) do
484548
migration_module when is_atom(migration_module) -> migration_module
485-
other -> Mix.raise "Expected :migration_module to be a module, got: #{inspect(other)}"
549+
other -> Mix.raise("Expected :migration_module to be a module, got: #{inspect(other)}")
486550
end
487551
end
488552

@@ -521,17 +585,19 @@ defmodule Mix.Phoenix.Schema do
521585
{function_def, true}
522586
end
523587

524-
525588
{attr, {function_name, function_def, needs_impl?}}
526589
end)
527590
end
528591

529592
defp fixture_params(attrs, fixture_unique_functions) do
530-
Map.new(attrs, fn {attr, type} ->
531-
case Map.fetch(fixture_unique_functions, attr) do
532-
{:ok, {function_name, _function_def, _needs_impl?}} ->
593+
attrs
594+
|> Enum.sort()
595+
|> Enum.map(fn {attr, type} ->
596+
case fixture_unique_functions do
597+
%{^attr => {function_name, _function_def, _needs_impl?}} ->
533598
{attr, "#{function_name}()"}
534-
:error ->
599+
600+
%{} ->
535601
{attr, inspect(type_to_default(attr, type, :create))}
536602
end
537603
end)

0 commit comments

Comments
 (0)