Skip to content

Instantly share code, notes, and snippets.

@ssomnoremac
Created January 2, 2018 19:30
Show Gist options
  • Save ssomnoremac/15312e8862b88ceab33c850104a9c98f to your computer and use it in GitHub Desktop.
Save ssomnoremac/15312e8862b88ceab33c850104a9c98f to your computer and use it in GitHub Desktop.
defmodule Hyr.Command do
@moduledoc """
Define a command struct and an associated GraphQL mutation.
## Example
defmodule Hyr.Shifts.Opening.Commands.CancelOpeningEmployer do
use Hyr.Command
alias Hyr.Shifts.Opening.Schema.Opening
result Opening
permission "MANAGE_OPENINGS"
command "Cancel an opening" do
field :opening_id, non_null(:id)
field :employer_id, non_null(:id)
end
end
"""
alias Hyr.Shifts.Router
alias HyrWeb.Schema.Middleware
defmacro __using__(_) do
quote do
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern
require Logger
Module.register_attribute(__MODULE__, :command_permission, [])
Module.register_attribute(__MODULE__, :command_result, [])
Module.register_attribute(__MODULE__, :command_result_aggregate_id_field, [])
import unquote(__MODULE__)
@before_compile unquote(__MODULE__)
end
end
@doc """
Define the permission required to execute the command.
"""
defmacro permission(permission)
when is_bitstring(permission)
do
quote do
Module.put_attribute(__MODULE__, :command_permission, unquote(permission))
end
end
@doc """
Provide the result type returned after successful command execution.
"""
defmacro result(result, id_field \\ :id) do
quote do
Module.put_attribute(__MODULE__, :command_result, unquote(result))
end
end
@doc """
Define the fields for the command and optionally provide a description.
## Example
defmodule Hyr.Shifts.Opening.Commands.CancelOpeningEmployer do
use Hyr.Command
command do
field :opening_id, non_null(:id)
field :employer_id, non_null(:id)
end
end
"""
defmacro command(description \\ "", [do: block] = fields) do
identifier = identify(__CALLER__.module)
input_name = String.to_atom("#{identifier}_input")
command_name = String.to_atom("#{identifier}_command")
quote do
input_object unquote(input_name), unquote(fields)
object unquote(command_name) do
payload field unquote(identifier) do
description unquote(description)
arg :input, non_null(unquote(input_name))
output do
field :result, identify(@command_result)
end
middleware Middleware.Authorize, @command_permission
resolve fn %{input: input_data}, _ ->
Logger.debug(fn -> "Attempting to dispatch command #{inspect __MODULE__} with input: #{inspect input_data}" end)
dispatch_command(input_data)
end
end
end
end
end
defmacro __before_compile__(env) do
identifier = identify(env.module)
input_name = String.to_atom("#{identifier}_input")
command_name = String.to_atom("#{identifier}_command")
fields =
env.module
|> Module.get_attribute(:absinthe_definitions)
|> Enum.map(fn %Absinthe.Schema.Notation.Definition{attrs: attrs} -> attrs end)
|> Enum.flat_map(fn attrs -> Keyword.get(attrs, :fields) end)
|> Enum.map(fn {identifier, _value} -> identifier end)
fields = fields -- [identifier, input_name, command_name, :result]
# first defined field is expected to be the identity field (last field in
# module attribute list)
identity_field = Enum.at(fields, -1)
quote do
defp dispatch_command(input_data) do
case input_data |> __MODULE__.new() |> Router.dispatch(consistency: :strong) do
:ok ->
case @command_result do
Hyr.Shifts.Opening.Schema.Opening -> {:ok, %{result: Hyr.Repo.get_by(@command_result, %{id: input_data.opening_id})}}
Hyr.Shifts.Opening.Schema.Applicant -> {:ok, %{result: Hyr.Repo.get_by(@command_result, %{opening_id: input_data.opening_id, talent_id: input_data.talent_id})}}
_ -> {:ok, %{result: Hyr.Repo.get(@command_result, input_data.id)}}
end
{:error, reason} ->
Logger.info(fn -> "Failed to dispatch command due to: #{inspect reason}" end)
message = reason |> to_string() |> String.replace("_", " ")
{:error, message}
end
end
@derive [Poison.Encoder]
defstruct unquote(fields)
use ExConstructor
end
end
def identify(module) do
module
|> Module.split()
|> Enum.at(-1)
|> Macro.underscore()
|> String.to_atom()
end
end
@venkatd
Copy link

venkatd commented Jan 2, 2018

Copying what you had in slack for reference:

import_types Hyr.Shifts.Opening.Commands.{
  CreateOpening,
  ChangeOpeningRate,
  ...
}
mutation do
    import_fields :create_opening_command
    import_fields :change_opening_rate_command
   ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment