fix: join_relationship in many to many can make through optional (#846)

This commit is contained in:
Dmitry Maganov 2024-01-23 20:15:51 +02:00 committed by GitHub
parent c60c5111ae
commit 91c34c3939
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 31 deletions

View file

@ -581,9 +581,9 @@ belongs_to :word, Word, primary_key?: true, allow_nil?: false
| Name | Type | Default | Docs |
|------|------|---------|------|
| [`through`](#relationships-many_to_many-through){: #relationships-many_to_many-through .spark-required} | `module` | | The resource to use as the join resource. |
| [`source_attribute_on_join_resource`](#relationships-many_to_many-source_attribute_on_join_resource){: #relationships-many_to_many-source_attribute_on_join_resource } | `atom` | | The attribute on the join resource that should line up with `source_attribute` on this resource. Defaults to `<snake_cased_last_part_of_source_module_name>_id`. |
| [`destination_attribute_on_join_resource`](#relationships-many_to_many-destination_attribute_on_join_resource){: #relationships-many_to_many-destination_attribute_on_join_resource } | `atom` | | The attribute on the join resource that should line up with `destination_attribute` on the related resource. Defaults to `<snake_cased_last_part_of_destination_module_name>_id`. |
| [`through`](#relationships-many_to_many-through){: #relationships-many_to_many-through } | `module` | | The resource to use as the join resource. |
| [`join_relationship`](#relationships-many_to_many-join_relationship){: #relationships-many_to_many-join_relationship } | `atom` | | The has_many relationship to the join resource. Defaults to `<relationship_name>_join_assoc`. |
| [`description`](#relationships-many_to_many-description){: #relationships-many_to_many-description } | `String.t` | | An optional description for the relationship |
| [`destination_attribute`](#relationships-many_to_many-destination_attribute){: #relationships-many_to_many-destination_attribute } | `atom` | `:id` | The attribute on the related resource that should match the `source_attribute` configured on this resource. |

View file

@ -58,19 +58,16 @@ defmodule Ash.Resource.Relationships.ManyToMany do
[
source_attribute_on_join_resource: [
type: :atom,
required: false,
doc:
"The attribute on the join resource that should line up with `source_attribute` on this resource. Defaults to `<snake_cased_last_part_of_source_module_name>_id`."
],
destination_attribute_on_join_resource: [
type: :atom,
required: false,
doc:
"The attribute on the join resource that should line up with `destination_attribute` on the related resource. Defaults to `<snake_cased_last_part_of_destination_module_name>_id`."
],
through: [
type: Ash.OptionsHelpers.ash_resource(),
required: true,
doc: "The resource to use as the join resource."
],
join_relationship: [

View file

@ -4,7 +4,7 @@ defmodule Ash.Resource.Transformers.CreateJoinRelationship do
"""
use Spark.Dsl.Transformer
alias Spark.Dsl.Transformer
alias Spark.{Dsl.Transformer, Error.DslError}
@extension Ash.Resource.Dsl
@ -12,38 +12,63 @@ defmodule Ash.Resource.Transformers.CreateJoinRelationship do
dsl_state
|> Transformer.get_entities([:relationships])
|> Enum.filter(&(&1.type == :many_to_many))
|> Enum.reject(fn relationship ->
|> Enum.reduce_while({:ok, dsl_state}, fn relationship, {:ok, dsl_state} ->
dsl_state
|> Transformer.get_entities([:relationships])
|> Enum.find(&(&1.name == relationship.join_relationship))
end)
|> Enum.reduce({:ok, dsl_state}, fn relationship, {:ok, dsl_state} ->
autogenerated_join_relationship_of = relationship.name
|> case do
nil when relationship.through == nil ->
error =
DslError.exception(
path: [:relationships, relationship.name],
message:
"Either `through` or `join_relationship` with an existing relationship is required."
)
{:ok, relationship} =
Transformer.build_entity(
@extension,
[:relationships],
:has_many,
[
name: relationship.join_relationship,
destination: relationship.through,
destination_attribute: relationship.source_attribute_on_join_resource,
api: relationship.api,
source_attribute: relationship.source_attribute,
private?: true
]
|> add_messages(relationship)
)
{:halt, {:error, error}}
relationship =
Map.put(
relationship,
:autogenerated_join_relationship_of,
autogenerated_join_relationship_of
)
nil ->
{:ok, join_relationship} =
Transformer.build_entity(
@extension,
[:relationships],
:has_many,
[
name: relationship.join_relationship,
destination: relationship.through,
destination_attribute: relationship.source_attribute_on_join_resource,
api: relationship.api,
source_attribute: relationship.source_attribute,
private?: true
]
|> add_messages(relationship)
)
{:ok, Transformer.add_entity(dsl_state, [:relationships], relationship)}
join_relationship =
Map.put(join_relationship, :autogenerated_join_relationship_of, relationship.name)
dsl_state = Transformer.add_entity(dsl_state, [:relationships], join_relationship)
{:cont, {:ok, dsl_state}}
join_relationship ->
relationship =
%{
relationship
| through: join_relationship.destination,
destination_attribute_on_join_resource: join_relationship.destination_attribute
}
dsl_state =
Transformer.replace_entity(
dsl_state,
[:relationships],
relationship,
&(&1.name == relationship.name)
)
{:cont, {:ok, dsl_state}}
end
end)
end