Packages
potionx
0.8.15
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.1.1
0.1.0
Potionx is a set of generators and functions that speeds up the process of setting up and deploying a full-stack application that uses Elixir with GraphQL for the server-side component and Vue for the frontend component.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/potionx.gen.gql_for_model.ex
defmodule Mix.Tasks.Potionx.Gen.GqlForModel do
alias __MODULE__
@shortdoc "Generates GraphQL mutations, queries and types for an Ecto model"
@task_name "potion.gen.gql_for_model"
@default_opts [schema: true, context: true]
@switches [binary_id: :boolean, table: :string, web: :string,
schema: :boolean, context: :boolean, context_app: :string, no_associations: :boolean]
defstruct app_otp: nil,
context_name: nil,
context_name_snakecase: nil,
module_name_data: nil,
module_name_graphql: nil,
dir_context: nil,
dir_graphql: nil,
dir_test: nil,
graphql_fields: nil,
lines: %{},
potion_name: "Potionx",
mock: nil,
mock_patch: nil,
model: nil,
model_name_atom: nil,
model_file_path: nil,
model_name: nil,
model_name_graphql_case: nil,
model_name_snakecase: nil,
no_associations: false,
no_frontend: false,
no_mutations: false,
no_queries: false,
validations: []
use Mix.Task
alias Mix.Phoenix.{Context, Schema}
alias Potionx.DocUtils
def add_lines_to_block(block, lines_to_add, start_line, indent_size) when is_binary(start_line) do
start_index = Enum.find_index(block, fn l ->
String.starts_with?(l, start_line)
end)
add_lines_to_block(block, lines_to_add, start_index, indent_size)
end
def add_lines_to_block(block, lines_to_add, start_index, indent_size) do
end_index =
Enum.slice(block, start_index..-1)
|> Enum.find_index(fn l ->
String.starts_with?(l, DocUtils.indent_to_string(indent_size) <> "end")
end)
end_index = end_index + start_index
Enum.concat(
[
Enum.slice(block, 0..(end_index - 1)),
lines_to_add,
Enum.slice(block, end_index..-1)
]
)
end
@doc false
def build(args) do
{opts, parsed, _} = parse_opts(args)
{opts, validate_args!(parsed)}
end
def common_fields do
[:description, :title]
end
def ensure_files_and_directories_exist(%GqlForModel{} = state) do
if (not File.dir?(state.dir_context)) do
Mix.raise """
Context directory #{state.dir_context} is missing
"""
end
files_to_be_generated(state)
|> Enum.map(fn {k, path_enum} ->
File.mkdir_p!(Path.join(Enum.slice(path_enum, 0..-2)))
path = Path.join(path_enum)
file_name =
cond do
String.ends_with?(to_string(k), "_test") ->
to_string(k) <> ".exs"
String.ends_with?(to_string(k), "_json") ->
String.replace_trailing(to_string(k), "_json", "") <> ".json"
true ->
to_string(k) <> ".ex"
end
# if does not exist, create using templates
if not File.exists?(path) do
EEx.eval_string(
Application.app_dir(
:potionx,
"priv/templates/#{@task_name}/#{file_name}"
)
|> File.read!,
Enum.map(Map.from_struct(state), &(&1))
)
|> (fn res ->
File.write!(path, res)
end).()
end
end)
state
end
def field_type_from_validations(type, validations) do
Enum.find(validations, fn
%{name: :inclusion} -> true
_ -> false
end)
|> case do
nil -> type
type -> type
end
end
@doc false
def files_to_be_generated(%GqlForModel{} = state) do
%{
app_schema: [state.dir_graphql, "schema.ex"],
model_mock: [state.dir_context, "#{state.model_name_snakecase}_mock.ex"],
model_mock_json: [
"shared",
"src",
"models",
state.context_name,
state.model_name,
"#{state.model_name_graphql_case}.mock.json"
],
model_json: [
"shared",
"src",
"models",
state.context_name,
state.model_name,
"#{state.model_name_graphql_case}.json"
],
mutations: [state.dir_graphql, "schemas", state.model_name_snakecase, "#{state.model_name_snakecase}_mutations.ex"],
mutations_test: [state.dir_test, "mutations", "#{state.model_name_snakecase}_mutations_test.exs"],
queries: [state.dir_graphql, "schemas", state.model_name_snakecase, "#{state.model_name_snakecase}_queries.ex"],
queries_test: [state.dir_test, "queries", "#{state.model_name_snakecase}_queries_test.exs"],
resolver: [state.dir_graphql, "resolvers", "#{state.model_name_snakecase}_resolver.ex"],
service: [state.dir_context, "#{state.model_name_snakecase}_service.ex"],
types: [state.dir_graphql, "schemas", state.model_name_snakecase, "#{state.model_name_snakecase}_types.ex"],
}
|> (fn res ->
[{:no_mutations, "mutations"}, {:no_queries, "queries"}]
|> Enum.reduce(res, fn {k, v}, acc ->
if (Map.get(state, k)) do
Map.drop(acc, [String.to_atom(v), String.to_atom(v <> "_test")])
else
acc
end
end)
end).()
end
def keyword_list_to_map(list) do
if (Keyword.keyword?(list)) do
Enum.into(list, %{})
else
list
end
end
def load_lines(%GqlForModel{} = state) do
files_to_be_generated(state)
|> Enum.filter(fn {k, _} -> Enum.member?([:app_schema, :mutations, :types, :queries], k) end)
|> Enum.reduce(state, fn {k, path_enum}, state ->
lines =
File.read!(
Path.join(path_enum)
)
|> String.trim
|> String.split(~r{(\r?)\n})
%{
state |
lines: Map.put(state.lines, k, lines)
}
end)
end
def load_model(%GqlForModel{} = state, nil) do
%{
state | model: [
"Elixir",
state.module_name_data,
state.context_name,
state.model_name
]
|> Enum.join(".")
|> String.to_atom
}
end
def load_model(%GqlForModel{} = state, model), do: %{state | model: model}
def load_validations(%GqlForModel{} = state) do
%{
state | validations: validations(
state.model.changeset(struct(state.model, %{}), %{})
)
}
end
def maybe_add_default_types(%GqlForModel{} = state) do
[
{
"input_object :#{state.model_name_atom}_filters_single do",
[
"field :id, non_null(:global_id)"
]
},
{
"object :#{state.model_name_snakecase}_mutation_result do",
[
"field :errors, list_of(:string)",
"field :errors_fields, list_of(:error)",
"field :node, :#{state.model_name_snakecase}",
"field :success_msg, :string"
]
}
# {
# "object #{state.model_name_snakecase}_collection_result do",
# [
# ":errors, list_of(:string)",
# ":nodes, list_of(#{state.model_name_snakecase})",
# ":query_info, :query_info"
# ]
# },
]
|> Enum.reduce(state, fn {head, lines}, acc ->
if Enum.find(acc.lines.types, fn l -> String.contains?(l, head) end) do
acc
else
block_to_add = Enum.concat([
[DocUtils.indent_to_string(2) <> head],
Enum.map(lines, fn l -> DocUtils.indent_to_string(4) <> l end),
[DocUtils.indent_to_string(2) <> "end"]
])
%{
acc |
lines: Map.put(acc.lines, :types,
add_lines_to_block(acc.lines.types, block_to_add, Enum.at(acc.lines.types, 0), 0)
)
}
end
end)
end
def maybe_add_line(block, line, indent_size, close \\ false) do
lines_to_add =
block
|> Enum.find_index(fn l ->
String.contains?(l, line)
end)
|> case do
nil ->
[DocUtils.indent_to_string(indent_size) <> line]
|> (fn res ->
if close do
Enum.concat([res, [DocUtils.indent_to_string(indent_size) <> "end"]])
else
res
end
end).()
_ ->
[]
end
add_lines_to_block(block, lines_to_add, Enum.at(block, 0), indent_size - 2)
end
def maybe_add_node_interface_type_resolve(%GqlForModel{} = state) do
# look for node interface, next line is resolve type
# insert into resolve_type block
interface_block_start_index = Enum.find_index(state.lines.app_schema, fn l ->
String.starts_with?(
l,
DocUtils.indent_to_string(2) <> "node interface"
)
end)
Enum.find(state.lines.app_schema, fn l ->
String.contains?(
l,
"#{state.module_name_data}.#{state.context_name}.#{state.model_name}{}, _ ->"
)
end)
|> if do
state
else
%{
state |
lines: Map.put(
state.lines,
:app_schema,
Enum.concat(
[
Enum.slice(state.lines.app_schema, 0..interface_block_start_index+1),
[
DocUtils.indent_to_string(6) <> "%#{state.module_name_data}.#{state.context_name}.#{state.model_name}{}, _ ->",
DocUtils.indent_to_string(8) <> ":#{state.model_name_atom}"
],
Enum.slice(state.lines.app_schema, (interface_block_start_index+2)..-1)
]
)
)
}
end
end
def maybe_convert_type(type, is_input \\ false) do
case type do
Ecto.UUID -> :id
{_, Ecto.Enum, _} -> :string
:binary -> :string
:binary_id -> :string
:datetime -> :naive_datetime
:id -> is_input && :global_id || :id
:map -> :json
:naive_datetime_usec -> :naive_datetime
:utc_datetime_usec -> :datetime
:utc_datetime -> :datetime
_ -> type
end
end
def maybe_init_types(%GqlForModel{} = state) do
Enum.map(types(state), fn line ->
{:types, line}
end)
|> Enum.reduce(state, fn {k, v}, acc ->
%{
acc |
lines:
Map.put(
acc.lines,
k,
maybe_add_line(Map.get(acc.lines, k), v, 2, true)
|> (fn lines_types ->
lines_types
|> Enum.find_index(fn l ->
String.contains?(l, "connection node_type: :#{state.model_name_atom}")
end)
|> case do
nil ->
add_lines_to_block(
lines_types,
[
DocUtils.indent_to_string(2) <> "connection node_type: :#{state.model_name_atom} do",
DocUtils.indent_to_string(4) <> "field :count, non_null(:integer)",
DocUtils.indent_to_string(4) <> "field :count_before, non_null(:integer)",
DocUtils.indent_to_string(4) <> "edge do",
DocUtils.indent_to_string(4) <> "end",
DocUtils.indent_to_string(2) <> "end"
],
Enum.at(lines_types, 0),
0
)
_ -> lines_types
end
end).()
)
}
end)
end
def maybe_update_main_schema(%GqlForModel{} = state) do
[
{:no_mutations, "import_types #{state.module_name_graphql}.Schema.#{state.model_name}Mutations"},
{:no_queries, "import_types #{state.module_name_graphql}.Schema.#{state.model_name}Queries"},
{:no_types, "import_types #{state.module_name_graphql}.Schema.#{state.model_name}Types"}
]
|> Enum.reduce(state.lines.app_schema, fn {k, line}, acc ->
if (Map.get(state, k)) do
acc
else
maybe_add_line(
acc,
line,
2
)
end
end)
|> (fn lines ->
[
{:no_mutations, "mutation do", "import_fields :#{state.model_name_snakecase}_mutations"},
{:no_queries, "query do", "import_fields :#{state.model_name_snakecase}_queries"},
{
:no_mutations,
"def dataloader",
"|> Dataloader.add_source(#{state.module_name_graphql}.Resolver.#{state.model_name}, #{state.module_name_graphql}.Resolver.#{state.model_name}.data())"
}
]
|> Enum.reduce(lines, fn {flag, k, v}, acc ->
(
Map.get(state, flag) or
Enum.find(acc, fn line -> String.contains?(line, v) end)
)
|> if do
acc
else
add_lines_to_block(
acc,
[DocUtils.indent_to_string(4) <> v],
DocUtils.indent_to_string(2) <> k,
2
)
end
end)
end).()
|> (fn lines ->
%{state | lines: Map.put(state.lines, :app_schema, lines)}
end).()
end
defp parse_opts(args) do
{opts, parsed, invalid} = OptionParser.parse(args, switches: @switches)
merged_opts =
@default_opts
|> Keyword.merge(opts)
|> put_context_app(opts[:context_app])
{merged_opts, parsed, invalid}
end
def prepare_mock(params, type \\ :create) do
params
|> Enum.filter(fn
{_, {:assoc, _}} -> false
_ -> true
end)
|> Enum.map(fn
{k, Ecto.UUID} -> {k, :uuid}
{k, v} -> {k, v}
end)
|> Mix.Phoenix.Schema.params(type)
|> Enum.map(fn
{:email, _} ->
{:email, type === :create && "test@example.com" || "test@example.com"}
{k, v} ->
{k, v}
end)
|> Enum.into(%{})
end
def pretty_print(m) do
inspect(m, pretty: true)
|> String.split(~r{(\r?)\n})
|> Enum.map(fn l -> " " <> l end)
|> Enum.join("\r\n")
end
defp put_context_app(opts, nil), do: opts
defp put_context_app(opts, string) do
Keyword.put(opts, :context_app, String.to_atom(string))
end
@doc false
@spec raise_with_help(String.t) :: no_return()
def raise_with_help(msg) do
Mix.raise """
#{msg}
mix #{@task_name} expects a
context module name, followed by the singular module name
mix #{@task_name} Accounts User
The context serves as the API boundary for the given resource.
Multiple resources may belong to a context and a resource may be
split over distinct contexts (such as Accounts.User and Payments.User).
"""
end
@doc false
def run(args, model \\ nil) do
args = Enum.filter(args, fn a -> not is_struct(a) end)
if Mix.Project.umbrella? do
Mix.raise "mix #{@task_name} can only be run inside an application directory"
end
if (!model) do
Mix.Task.run("app.start")
end
{opts, [context, schema]} = build(args)
this_app = Mix.Phoenix.otp_app()
dir_context = Path.join(["lib", "#{this_app}", Macro.underscore(context)])
%GqlForModel{
app_otp: this_app,
context_name: context,
context_name_snakecase: Macro.underscore(context),
dir_context: dir_context,
dir_graphql: Path.join(["lib", "#{this_app}_graphql"]),
dir_test: Path.join(["test", "#{this_app}_graphql"]),
model_file_path: Path.join([dir_context, Macro.underscore(schema) <> ".ex"]),
model_name: schema,
model_name_atom: Macro.underscore(schema) |> String.to_atom,
model_name_graphql_case: Macro.underscore(schema) |> Absinthe.Adapter.LanguageConventions.to_external_name(nil),
model_name_snakecase: Macro.underscore(schema),
module_name_data: Mix.Phoenix.context_base(
Mix.Phoenix.context_app()
),
module_name_graphql: Mix.Phoenix.context_base(
Mix.Phoenix.context_app()
) <> "GraphQl",
no_associations: Keyword.get(opts, :no_associations, false),
no_frontend: Keyword.get(opts, :no_frontend, false),
no_mutations: Keyword.get(opts, :no_mutations, false),
no_queries: Keyword.get(opts, :no_queries, false)
}
|> ensure_files_and_directories_exist
|> load_model(model)
|> load_lines
|> load_validations
|> maybe_init_types
|> sync_mocks
|> sync_objects
|> sync_graphql_files
|> maybe_add_default_types
|> maybe_add_node_interface_type_resolve
|> maybe_update_main_schema
|> sync_json_schema
|> run_npx_generator
|> write_lines_to_files
end
def run_npx_generator(%GqlForModel{no_frontend: true} = state), do: state
def run_npx_generator(%GqlForModel{} = state) do
Mix.shell().cmd(
"npx @potionapps/templates@latest model #{state.context_name} #{state.model_name} --destination=./frontend/admin"
)
state
end
def sync_graphql_files(%GqlForModel{} = state) do
fields = prepare_mock(
state.model.__changeset__
)
fields_computed =
common_fields()
|> Enum.reduce(%{}, fn key, fields ->
if (function_exported?(state.model, key, 1)) do
Map.put(fields, key, :string)
else
fields
end
end)
state = %{
state |
graphql_fields:
Map.merge(fields_computed, fields)
|> Enum.reduce([], fn
{_, {:assoc, _}}, acc ->
acc
{k, _}, acc ->
acc ++ [Absinthe.Adapter.LanguageConventions.to_external_name(to_string(k), nil)]
end)
}
[
{
:no_queries,
"priv/templates/#{@task_name}/collection.gql",
[
"shared",
"src",
"models",
state.context_name,
state.model_name,
"#{state.model_name_graphql_case}Collection.gql"
]
},
{
:no_mutations,
"priv/templates/#{@task_name}/delete.gql",
[
"shared",
"src",
"models",
state.context_name,
state.model_name,
"#{state.model_name_graphql_case}Delete.gql"
]
},
{
:no_mutations,
"priv/templates/#{@task_name}/mutation.gql",
[
"shared",
"src",
"models",
state.context_name,
state.model_name,
"#{state.model_name_graphql_case}Mutation.gql"
]
},
{
:no_queries,
"priv/templates/#{@task_name}/single.gql",
[
"shared",
"src",
"models",
state.context_name,
state.model_name,
"#{state.model_name_graphql_case}Single.gql"
]
}
]
|> Enum.each(
fn {flag, template, path_parts} ->
unless Map.get(state, flag) do
EEx.eval_string(
Application.app_dir(
:potionx,
template
)
|> File.read!,
Enum.map(
Map.from_struct(state),
&(&1)
)
)
|> (fn res ->
File.write!(
Path.join(path_parts),
res
)
end).()
end
end
)
state
end
def sync_json_schema(%GqlForModel{} = state) do
model_json_raw = File.read!(
files_to_be_generated(state).model_json
|> Path.join
)
model_json = Jason.decode!(model_json_raw, keys: :atoms)
state.model.__changeset__
|> Enum.reduce(model_json, fn
{_, {:assoc, _}}, acc -> acc
{k, {:array, opts}}, acc ->
options =
case opts do
{_, _, %{values: values}} -> values
_ -> []
end
acc ++ [%{
name: k,
options: options,
type: "checkbox",
validations:
Enum.reduce(state.validations, [], fn {key, v}, acc ->
if (key === k) do
acc ++ [v]
else
acc
end
end)
}]
{k, v}, acc ->
cond do
Enum.member?([:inserted_at, :updated_at], k) ->
acc
true ->
validations =
Enum.reduce(state.validations, [], fn {key, v}, acc ->
if (key === k) do
acc ++ [v]
else
acc
end
end)
|> Enum.uniq_by(fn v ->
v.name
end)
acc ++ [%{
name: k,
type: field_type_from_validations(v, validations),
validations: validations
}]
end
end)
|> Enum.uniq_by(fn v ->
v.name
end)
|> Enum.map(fn %{name: name} = field ->
Map.put(
field,
:name,
Absinthe.Adapter.LanguageConventions.to_external_name(
to_string(
name
),
nil
)
)
|> Map.put(
:type,
Absinthe.Adapter.LanguageConventions.to_external_name(
to_string(maybe_convert_type(field.type)),
nil
)
)
end)
|> Jason.encode!(pretty: true)
|> (fn res ->
File.write!(
files_to_be_generated(state).model_json
|> Path.join,
res
)
end).()
state
end
def sync_mocks(%GqlForModel{} = state) do
fields =
prepare_mock(
state.model.__changeset__
)
fields_patch = prepare_mock(
state.model.__changeset__,
:update
)
EEx.eval_string(
Application.app_dir(
:potionx,
"priv/templates/#{@task_name}/model_mock.ex"
)
|> File.read!,
Enum.map(
Map.from_struct(state)
|> Map.put(:mock, pretty_print(fields))
|> Map.put(:mock_patch, pretty_print(fields_patch)),
&(&1)
)
)
|> (fn res ->
File.write!(
Path.join(files_to_be_generated(state).model_mock),
res
)
end).()
EEx.eval_string(
Application.app_dir(
:potionx,
"priv/templates/#{@task_name}/model_mock.json"
)
|> File.read!,
Enum.map(
Map.from_struct(state)
|> Map.put(
:mock,
fields
|> Enum.reduce(%{}, fn {k, v}, acc ->
Map.put(
acc,
Absinthe.Adapter.LanguageConventions.to_external_name(
to_string(
k
),
nil
),
v
)
end)
|> Jason.encode!(pretty: true)
),
&(&1)
)
)
|> (fn res ->
File.write!(
Path.join(files_to_be_generated(state).model_mock_json),
res
)
end).()
state
end
def sync_objects(%GqlForModel{} = state) do
fields = state.model.__changeset__
types(state)
|> Enum.reduce(state, fn line, state ->
lines = state.lines.types
head_index =
lines
|> Enum.find_index(fn l -> String.contains?(l, line) end)
tail_index =
lines
|> Enum.slice(head_index..-1)
|> Enum.find_index(fn l -> String.contains?(l, DocUtils.indent_to_string(2) <> "end") end)
|> Kernel.+(head_index)
snippet =
Enum.slice(lines, head_index..tail_index)
Enum.reduce(fields, snippet, fn
{k, {:array, _}}, acc ->
acc ++ ["field :#{k}, list_of(:string)"]
{k, {:assoc, ass}}, acc ->
if String.starts_with?(line, "input_object") or state.no_associations do
acc
else
model = ass.related |> to_string() |> String.split(".") |> Enum.at(-1) |> Macro.underscore()
result_type = ass.cardinality === :many && "list_of(:#{model})" || model
acc ++ ["field :#{to_string(k)}, :#{result_type}, resolve: dataloader(#{state.module_name_graphql}.Resolver.#{state.model_name})"]
end
{:id, _}, acc ->
if String.starts_with?(line, "input_object") do
acc
else
acc ++ ["field :internal_id, :id, resolve: fn parent, _, _ -> {:ok, Map.get(parent || %{}, :id)} end"]
end
{k, v}, acc ->
acc ++ ["field :#{k}, :#{to_string(maybe_convert_type(v, String.starts_with?(line, "input_object")))}"]
end)
|> (fn lines ->
common_fields()
|> Enum.reduce(lines, fn key, lines ->
if (function_exported?(state.model, key, 1) && not String.starts_with?(line, "input_object")) do
lines ++ [
"field :#{to_string(key)}, :string, resolve: Potionx.Resolvers.resolve_computed(#{to_string(state.module_name_data)}.#{to_string(state.context_name)}.#{to_string(state.model_name)}, :#{to_string(key)})"
]
else
lines
end
end)
end).()
|> Enum.reduce(snippet, fn line, acc ->
maybe_add_line(acc, line, 4)
end)
|> (fn snippet ->
head = Enum.slice(lines, 0..(head_index - 1))
tail = Enum.slice(lines, (tail_index + 1)..-1)
%{
state | lines:
Map.put(state.lines, :types, Enum.concat([head, snippet, tail]))
}
end).()
end)
end
def types(%GqlForModel{} = state) do
[
"node object :#{state.model_name_atom} do",
"input_object :#{state.model_name_atom}_filters do",
"input_object :#{state.model_name_atom}_input do"
]
end
defp validate_args!([context, schema] = args) do
cond do
not Context.valid?(context) ->
raise_with_help "Expected the context, #{inspect context}, to be a valid module name"
not Schema.valid?(schema) ->
raise_with_help "Expected the schema, #{inspect schema}, to be a valid module name"
context == schema ->
raise_with_help "The context and schema should have different names"
true ->
args
end
end
defp validate_args!(_args) do
raise_with_help "Invalid arguments"
end
def validations(%Ecto.Changeset{} = cs) do
cs
|> Map.get(:required)
|> Enum.map(fn c -> {c, :required} end)
|> Kernel.++(
cs
|> Ecto.Changeset.validations()
)
|> Enum.map(fn
{k, :required = n} ->
{k, %{
name: n,
}}
{k, {:format = n, r}} ->
{k, %{
name: n,
params: %{
pattern: Regex.source(r)
}
}}
{k, {n, params}} when n == :length or n == :number ->
{k, %{
name: n,
params: keyword_list_to_map(params)
}}
{k, {n, values}} when n == :inclusion or n == :exclusion or n === :subset ->
{k, %{
name: n,
params: %{
values: keyword_list_to_map(values)
}
}}
{k, {n, _}} ->
{k, %{name: n}}
end)
|> Enum.map(fn {name, validation} ->
{
name,
Map.put(
validation,
:name,
Absinthe.Adapter.LanguageConventions.to_external_name(
to_string(
validation.name
),
nil
)
)
}
end)
end
def write_lines_to_files(%GqlForModel{} = state) do
files = files_to_be_generated(state)
Enum.each(state.lines, fn {k, lines} ->
File.write(
Path.join(Map.get(files, k)),
Enum.join(lines, "\r\n"),
[:write]
)
end)
state
end
end