Skip to content

Commit

Permalink
chore: finish up the prior fix on required inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Jan 21, 2025
1 parent 5e0697f commit 1c23266
Show file tree
Hide file tree
Showing 9 changed files with 988 additions and 136 deletions.
36 changes: 6 additions & 30 deletions documentation/dsls/DSL-AshGraphql.Resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ Generates input objects for `manage_relationship` arguments on resource actions.
### Examples
```
managed_relationships do
manage_relationship :create_post, :comments
managed_relationship :create_post, :comments
end
```
Expand All @@ -580,36 +580,10 @@ managed_relationship action, argument
```


Instructs ash_graphql that a given argument with a `manage_relationship` change should have its input objects derived automatically from the potential actions to be called.
Configures the behavior of a given managed_relationship for a given action.

For example, given an action like:

```elixir
actions do
create :create do
argument :comments, {:array, :map}

change manage_relationship(:comments, type: :direct_control) # <- we look for this change with a matching argument name
end
end
```

You could add the following managed_relationship

```elixir
graphql do
...

managed_relationships do
managed_relationship :create, :comments
end
end
```

By default, the `{:array, :map}` would simply be a `json[]` type. If the argument name
is placed in this list, all of the potential actions that could be called will be combined
into a single input object. If there are type conflicts (for example, if the input could create
or update a record, and the create and update actions have an argument of the same name but with a different type),
If there are type conflicts (for example, if the input could create or update a record, and the
create and update actions have an argument of the same name but with a different type),
a warning is emitted at compile time and the first one is used. If that is insufficient, you will need to do one of the following:

1.) provide the `:types` option to the `managed_relationship` constructor (see that option for more)
Expand All @@ -625,6 +599,8 @@ For `non_null` use `{:non_null, type}`, and for a list, use `{:array, type}`, fo

To *remove* a key from the input object, simply pass `nil` as the type.

Use `ignore?: true` to skip this type generation.




Expand Down
3 changes: 2 additions & 1 deletion lib/graphql/resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -961,8 +961,9 @@ defmodule AshGraphql.Graphql.Resolver do
manage_opts = Spark.Options.validate!(opts[:opts], manage_opts_schema)

fields =
manage_opts
resource
|> AshGraphql.Resource.manage_fields(
manage_opts,
managed_relationship,
relationship,
__MODULE__
Expand Down
175 changes: 100 additions & 75 deletions lib/resource/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2157,7 +2157,8 @@ defmodule AshGraphql.Resource do

manage_opts = Spark.Options.validate!(opts[:opts], manage_opts_schema)

fields = manage_fields(manage_opts, managed_relationship, relationship, schema)
fields =
manage_fields(resource, manage_opts, managed_relationship, relationship, schema)

type = managed_relationship.type_name || default_managed_type_name(resource, action, argument)

Expand All @@ -2184,7 +2185,7 @@ defmodule AshGraphql.Resource do
end

@doc false
def manage_fields(manage_opts, managed_relationship, relationship, schema) do
def manage_fields(_resource, manage_opts, managed_relationship, relationship, schema) do
[
on_match_fields(manage_opts, relationship, schema),
on_no_match_fields(manage_opts, relationship, schema),
Expand All @@ -2204,15 +2205,7 @@ defmodule AshGraphql.Resource do
new_fields =
Enum.reduce(field_set, [], fn {resource, action, field}, fields ->
if required?(field) do
if Enum.all?(field_sets, fn field_set ->
Enum.all?(field_set, fn {_, _, other_field} ->
other_field.identifier != field.identifier || required?(other_field)
end)
end) do
[{resource, action, field} | fields]
else
[{resource, action, %{field | type: field.type.of_type}} | fields]
end
maybe_unrequire(field_sets, resource, action, field, fields)
else
[{resource, action, field} | fields]
end
Expand All @@ -2223,6 +2216,18 @@ defmodule AshGraphql.Resource do
end
end

defp maybe_unrequire(field_sets, resource, action, field, fields) do
if Enum.all?(field_sets, fn field_set ->
Enum.any?(field_set, fn {_, _, other_field} ->
other_field.identifier == field.identifier && required?(other_field)
end)
end) do
[{resource, action, field} | fields]
else
[{resource, action, %{field | type: field.type.of_type}} | fields]
end
end

defp required?(%{type: %Absinthe.Blueprint.TypeReference.NonNull{}}), do: true
defp required?(_), do: false

Expand Down Expand Up @@ -2457,90 +2462,110 @@ defmodule AshGraphql.Resource do
opts
|> ManagedRelationshipHelpers.on_match_destination_actions(relationship)
|> List.wrap()
|> Enum.flat_map(fn
|> Enum.reject(fn
{:join, nil, _} ->
true

{:destination, nil} ->
true

_ ->
false
end)
|> case do
[] ->
:none

{:destination, action_name} ->
action = Ash.Resource.Info.action(relationship.destination, action_name)
actions ->
Enum.flat_map(actions, fn
{:destination, action_name} ->
action = Ash.Resource.Info.action(relationship.destination, action_name)

relationship.destination
|> mutation_fields(schema, action, action.type)
|> Enum.map(fn field ->
{relationship.destination, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier == relationship.destination_attribute
end)

{:join, nil, _} ->
:none
relationship.destination
|> mutation_fields(schema, action, action.type)
|> Enum.map(fn field ->
{relationship.destination, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier == relationship.destination_attribute
end)

{:join, action_name, fields} ->
action = Ash.Resource.Info.action(relationship.through, action_name)
{:join, action_name, fields} ->
action = Ash.Resource.Info.action(relationship.through, action_name)

if fields == :all do
mutation_fields(relationship.through, schema, action, action.type)
else
relationship.through
|> mutation_fields(schema, action, action.type)
|> Enum.filter(&(&1.identifier in fields))
end
|> Enum.map(fn field ->
{relationship.through, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier in [
relationship.destination_attribute_on_join_resource,
relationship.source_attribute_on_join_resource
]
if fields == :all do
mutation_fields(relationship.through, schema, action, action.type)
else
relationship.through
|> mutation_fields(schema, action, action.type)
|> Enum.filter(&(&1.identifier in fields))
end
|> Enum.map(fn field ->
{relationship.through, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier in [
relationship.destination_attribute_on_join_resource,
relationship.source_attribute_on_join_resource
]
end)
end)
end)
end
end

defp on_no_match_fields(opts, relationship, schema) do
opts
|> ManagedRelationshipHelpers.on_no_match_destination_actions(relationship)
|> List.wrap()
|> Enum.flat_map(fn
|> Enum.reject(fn
{:join, nil, _} ->
true

{:destination, nil} ->
:none
true

{:destination, action_name} ->
action = Ash.Resource.Info.action(relationship.destination, action_name)
_ ->
false
end)
|> case do
[] ->
:none

relationship.destination
|> mutation_fields(schema, action, action.type)
|> Enum.map(fn field ->
{relationship.destination, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier == relationship.destination_attribute
end)
actions ->
Enum.flat_map(actions, fn
{:destination, action_name} ->
action = Ash.Resource.Info.action(relationship.destination, action_name)

{:join, nil, _} ->
:none
relationship.destination
|> mutation_fields(schema, action, action.type)
|> Enum.map(fn field ->
{relationship.destination, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier == relationship.destination_attribute
end)

{:join, action_name, fields} ->
action = Ash.Resource.Info.action(relationship.through, action_name)
{:join, action_name, fields} ->
action = Ash.Resource.Info.action(relationship.through, action_name)

if fields == :all do
mutation_fields(relationship.through, schema, action, action.type)
else
relationship.through
|> mutation_fields(schema, action, action.type)
|> Enum.filter(&(&1.identifier in fields))
end
|> Enum.map(fn field ->
{relationship.through, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier in [
relationship.destination_attribute_on_join_resource,
relationship.source_attribute_on_join_resource
]
if fields == :all do
mutation_fields(relationship.through, schema, action, action.type)
else
relationship.through
|> mutation_fields(schema, action, action.type)
|> Enum.filter(&(&1.identifier in fields))
end
|> Enum.map(fn field ->
{relationship.through, action.name, field}
end)
|> Enum.reject(fn {_, _, field} ->
field.identifier in [
relationship.destination_attribute_on_join_resource,
relationship.source_attribute_on_join_resource
]
end)
end)
end)
end
end

defp manage_pkey_fields(opts, managed_relationship, relationship, schema) do
Expand Down
Loading

0 comments on commit 1c23266

Please sign in to comment.