Packages
absinthe_relay
1.3.3
1.6.0
1.5.2
1.5.1
1.5.0
1.5.0-rc.0
1.5.0-beta.0
1.5.0-alpha.0
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.1
1.4.0-rc.0
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.3.0-rc.0
1.3.0-beta.1
1.3.0-beta.0
1.2.0
1.2.0-alpha0
1.2.0-alpha.3
1.2.0-alpha.2
1.2.0-alpha.1
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.8.0
Relay framework support for Absinthe
Current section
Files
Jump to
Current section
Files
lib/absinthe/relay/connection/notation.ex
defmodule Absinthe.Relay.Connection.Notation do
@moduledoc """
Macros used to define Connection-related schema entities
See `Absinthe.Relay.Connection` for more information.
"""
alias Absinthe.Schema.Notation
defmodule Naming do
@moduledoc false
defstruct base_identifier: nil, node_type_identifier: nil, connection_type_identifier: nil, edge_type_identifier: nil
def define(node_type_identifier) do
define(node_type_identifier, node_type_identifier)
end
def define(nil, nil) do
nil
end
def define(node_type_identifier, nil) do
define(node_type_identifier, node_type_identifier)
end
def define(node_type_identifier, base_identifier) do
%__MODULE__{
node_type_identifier: node_type_identifier,
base_identifier: base_identifier,
connection_type_identifier: ident(base_identifier, :connection),
edge_type_identifier: ident(base_identifier, :edge)
}
end
defp ident(base, category) do
:"#{base}_#{category}"
end
end
defp naming_from_attrs!(attrs) do
naming = Naming.define(attrs[:node_type], attrs[:connection])
naming || raise("Must provide a `:node_type' option (an optional `:connection` option is also supported)")
end
@doc """
Define a connection type for a given node type.
## Examples
A basic connection for a node type, `:pet`. This well generate simple
`:pet_connection` and `:pet_edge` types for you:
```
connection node_type: :pet
```
You can provide a custom name for the connection type (just don't include the
word "connection"). You must still provide the `:node_type`. You can create as
many different connections to a node type as you want.
This example will create a connection type, `:favorite_pets_connection`, and
an edge type, `:favorite_pets_edge`:
```
connection :favorite_pets, node_type: :pet
```
You can customize the connection object just like any other `object`:
```
connection :favorite_pets, node_type: :pet do
field :total_age, :float do
resolve fn
_, %{source: conn} ->
sum = conn.edges
|> Enum.map(fn edge -> edge.node.age)
|> Enum.sum
{:ok, sum}
end
end
edge do
# ...
end
end
```
Just remember that if you use the block form of `connection`, you must call
the `edge` macro within the block to make sure the edge type is generated.
See the `edge` macro below for more information.
"""
defmacro connection({:field, _, [identifier, attrs]}, [do: block]) when is_list(attrs) do
field_attrs = Keyword.drop(attrs, [:node_type, :connection])
do_connection_field(__CALLER__, identifier, naming_from_attrs!(attrs), field_attrs, block)
end
defmacro connection(attrs, [do: block]) do
do_connection_definition(__CALLER__, naming_from_attrs!(attrs), [], block)
end
defmacro connection(attrs) do
do_connection_definition(__CALLER__, naming_from_attrs!(attrs), [], nil)
end
defmacro connection(identifier, attrs, [do: block]) do
naming = naming_from_attrs!(attrs |> Keyword.put(:connection, identifier))
object_attrs = attrs |> Keyword.drop([:node_type, :connection])
do_connection_definition(__CALLER__, naming, object_attrs, block)
end
@doc """
Customize the edge type.
## Examples
```
connection node_type: :pet do
# ...
edge do
field :node_name_backwards, :string do
resolve fn
_, %{source: edge} ->
{:ok, edge.node.name |> String.reverse}
end
end
end
end
```
"""
defmacro edge(attrs, [do: block]) do
__CALLER__
|> do_edge(attrs, block)
end
defmacro edge([do: block]) do
__CALLER__
|> do_edge([], block)
end
@private_node_type_identifier_path [Absinthe.Relay, :connection_node]
@private_base_identifier_path [Absinthe.Relay, :connection_base]
defp do_edge(env, attrs, block) do
Notation.recordable!(env, :edge, private_lookup: @private_node_type_identifier_path)
# Hydrate naming struct from values stored in `private`
node_type_identifier = Notation.get_in_private(env.module, @private_node_type_identifier_path)
base_identifier = Notation.get_in_private(env.module, @private_base_identifier_path)
naming = Naming.define(node_type_identifier, base_identifier)
record_edge_object!(env, naming, attrs, block)
end
defp do_connection_field(env, identifier, naming, attrs, block) do
env
|> Notation.recordable!(:field)
|> record_connection_field!(identifier, naming, attrs, block)
end
# Generate connection & edge objects
defp do_connection_definition(env, naming, attrs, block) do
env
|> Notation.recordable!(:object)
|> record_connection_definition!(naming, attrs, block)
end
@doc false
# Record a connection field
def record_connection_field!(env, identifier, naming, attrs, block) do
pagination = Keyword.get(attrs, :paginate, :both)
Notation.record_field!(
env,
identifier,
[type: naming.connection_type_identifier] ++ Keyword.delete(attrs, :paginate),
[paginate_args(pagination), block]
)
end
@doc false
# Record a connection and edge types
def record_connection_definition!(env, naming, attrs, nil) do
record_connection_object!(env, naming, attrs, nil)
record_edge_object!(env, naming, attrs, nil)
end
def record_connection_definition!(env, naming, attrs, block) do
record_connection_object!(env, naming, attrs, block)
end
@doc false
# Record the connection object
def record_connection_object!(env, naming, attrs, block) do
Notation.record_object!(
env,
naming.connection_type_identifier,
attrs,
[connection_object_body(naming), block]
)
end
@doc false
# Record the edge object
def record_edge_object!(env, naming, attrs, block) do
Notation.record_object!(
env,
naming.edge_type_identifier,
attrs,
[edge_object_body(naming), block]
)
end
defp connection_object_body(naming) do
edge_type = naming.edge_type_identifier
[private_category_node, private_key_node] = @private_node_type_identifier_path
[private_category_base, private_key_base] = @private_base_identifier_path
quote do
field :page_info, type: non_null(:page_info)
field :edges, type: list_of(unquote(edge_type))
private unquote(private_category_node), unquote(private_key_node), unquote(naming.node_type_identifier)
private unquote(private_category_base), unquote(private_key_base), unquote(naming.base_identifier)
end
end
defp edge_object_body(naming) do
node_type = naming.node_type_identifier
quote do
@desc "The item at the end of the edge"
field :node, unquote(node_type)
@desc "A cursor for use in pagination"
field :cursor, non_null(:string)
end
end
# Forward pagination arguments.
#
# Arguments appropriate to include on a field whose type is a connection
# with forward pagination.
defp paginate_args(:forward) do
quote do
arg :after, :string
arg :first, :integer
end
end
# Backward pagination arguments.
# Arguments appropriate to include on a field whose type is a connection
# with backward pagination.
defp paginate_args(:backward) do
quote do
arg :before, :string
arg :last, :integer
end
end
# Pagination arguments (both forward and backward).
# Arguments appropriate to include on a field whose type is a connection
# with both forward and backward pagination.
defp paginate_args(:both) do
[
paginate_args(:forward),
paginate_args(:backward)
]
end
end