diff --git a/documentation/dsls/DSL:-Ash.Resource.md b/documentation/dsls/DSL:-Ash.Resource.md index e1bf353a..6ebe07f6 100644 --- a/documentation/dsls/DSL:-Ash.Resource.md +++ b/documentation/dsls/DSL:-Ash.Resource.md @@ -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 `_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 `_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 `_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. | diff --git a/lib/ash/resource/relationships/many_to_many.ex b/lib/ash/resource/relationships/many_to_many.ex index 4c1ae305..25fd195f 100644 --- a/lib/ash/resource/relationships/many_to_many.ex +++ b/lib/ash/resource/relationships/many_to_many.ex @@ -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 `_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 `_id`." ], through: [ type: Ash.OptionsHelpers.ash_resource(), - required: true, doc: "The resource to use as the join resource." ], join_relationship: [ diff --git a/lib/ash/resource/transformers/create_join_relationship.ex b/lib/ash/resource/transformers/create_join_relationship.ex index fe1bc40c..2170d9a5 100644 --- a/lib/ash/resource/transformers/create_join_relationship.ex +++ b/lib/ash/resource/transformers/create_join_relationship.ex @@ -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