@cjbottaro The problem with absinthe
is that they are using macros and work only with raw data (not in variables).
defmodule Example do
defmacro sample(some_data) do
{:here, [], [:just, :goes, :ast, :without, :quote]}
end
end
# instead of:
defmodule Example do
defmacro sample(some_data) do
quote bind_quoted: [some_data: some_data], unquote: false do
here(:just, :goes, :normal, :data, :not, :in, :ast, :format, some_data)
end
end
end
This is because absinthe
is making some checks on raw data.
Let’s say we have such simple code:
defmodule Example do
defmacro sample(data) do
IO.inspect(data)
end
end
Example.sample(5)
5 # IO.inspect call here
5
# vs
data = 5
5
Example.sample(data)
{:data, [line: 8], nil} # IO.inspect call here
5
As you can see it’s not possible to work on it without proper quoting. Same goes if you want to use absinthe
macros i.e. you need to pass raw data.
However this does not mean that it’s not possible to pass variables - this only means that we need pass raw data to absinthe macros. It should be hint for more experienced developers. Just write your own macro!
Firstly you need to know what AST
you need to return:
# inside iex call
quote do
# code of which ast you want to preview
end
# for example:
quote do
input_object :user_filter do
field :id, :integer_filter
field :email, :string_filter
end
end
{:input_object, [],
[
:user_filter,
[
do: {:__block__, [],
[
{:field, [], [:id, :integer_filter]},
{:field, [], [:email, :string_filter]}
]}
]
]}
Let’s split it:
-
{:field, [], [:id, :integer_filter]}
As you can see it’s field/2
macro AST
-
{:__block__, [], […]}
Block here is list of AST
expressions inside function which are not single literals. For example: def sample(…) do 5 end
gives us just raw 5
in place of whole :__block__
part, but if we add one more line with same literal they would be arguments in :__block__
AST.
-
Finally {:input_object, [], [:user_filter, [do: …]]}
Similarly to 1st point it’s ast for input_object/2
call. Here do … end
goes to 2nd argument which is keyword list [do: …]
. As in 2nd point we could have: [do: 5]
or [do: {:__block__, [], […]}]
. For us it’s 2nd case as we will never contain literals there.
From this here goes example code:
defmodule Example do
defmacro sample do
name = :user_filter
fields = [
[:id, :integer_filter],
[:email, :string_filter]
]
data = Enum.map(fields, &Example.ast_call(:field, &1))
block = Example.ast_call(:__block__, data)
do_block_keyword = [do: block]
Example.ast_call(:input_object, [:user_filter, do_block_keyword])
# since you generated AST you do not need `quote do … end` here
end
# in same way you can simply create helper functions
# for specific absinthe calls,
# so you can minimize your initial data (i.e. no need to pass empty list as 2nd argument in each ast_call)
# and your code is more readable
def ast_call(name, args), do: {name, [], args}
# for example:
# def field_ast(name, type), do: {:field, [], [name, type]}
end
and here is usage:
defmodule InputObjects do
use Absinthe.Schema.Notation
require Example
Example.sample()
end
Sorry if I made any typo - I wrote everything from memory. 