Many to Many Error

frankdugan3
2023-05-13

frankdugan3:

Given these relationships:

# Capability
relationships do
  many_to_many :steps, Hsm.Ash.MCP.Step do
    through Hsm.Ash.MCP.StepCapability
    source_attribute_on_join_resource :capability_id
    destination_attribute_on_join_resource :step_id
  end
end

# Step
relationships do
  many_to_many :capabilities, Hsm.Ash.Workcenters.Capability do
    through Hsm.Ash.MCP.StepCapability
    source_attribute_on_join_resource :step_id
    destination_attribute_on_join_resource :capability_id
  end
end

# StepCapability
relationships do
  belongs_to :capability, Hsm.Ash.Workcenters.Capability,
    allow_nil?: false,
    api: Hsm.Ash.Workcenters
  belongs_to :step, Hsm.Ash.MCP.Step, allow_nil?: false, api: Hsm.Ash.MCP
end

All of them are in the same API and Registry.

I’m getting an error that I don’t quite understand (join_relationship_name???):

** (EXIT from #PID<0.96.0>) an exception was raised:
    ** (RuntimeError) Resource `Hsm.Ash.MCP.StepCapability` is not in registry `Hsm.Ash.Workcenters.Registry` for autogenerated join relationship: `:steps_join_assoc`

Relationship was generated by the `many_to_many` relationship `:steps`

If the `through` resource `Hsm.Ash.MCP.StepCapability` is not accepted by the same
api as the destination resource `Hsm.Ash.MCP.Step`,
then you must define that relationship manually. To define it manually, add the following to your
relationships:

    has_many :steps_join_assoc, Hsm.Ash.MCP.StepCapability do
      # configure the relationship attributes
      ...
    end

You can use a name other than `:steps_join_assoc`, but if you do, make sure to
add that to `:steps`, i.e

    many_to_many :steps_join_assoc, Hsm.Ash.MCP.StepCapability do
      ...
      join_relationship_name :your_new_name
    end

        (ash 2.9.5) lib/ash/registry/extensions/resource_validations/verifiers/validate_related_resource_inclusion.ex:43: anonymous fn/5 in Ash.Registry.ResourceValidations.Verifiers.ValidateRelatedResourceInclusion.verify/1
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (ash 2.9.5) lib/ash/registry/extensions/resource_validations/verifiers/validate_related_resource_inclusion.ex:16: anonymous fn/4 in Ash.Registry.ResourceValidations.Verifiers.ValidateRelatedResourceInclusion.verify/1
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (ash 2.9.5) lib/ash/registry/extensions/resource_validations/verifiers/validate_related_resource_inclusion.ex:15: Ash.Registry.ResourceValidations.Verifiers.ValidateRelatedResourceInclusion.verify/1
        lib/hsm/ash/workcenters/registry.ex:1: anonymous fn/1 in Hsm.Ash.Workcenters.Registry.__verify_ash_dsl__/1
        (elixir 1.14.4) lib/enum.ex:975: Enum."-each/2-lists^foreach/1-0-"/2
        lib/hsm/ash/workcenters/registry.ex:1: Hsm.Ash.Workcenters.Registry.__verify_ash_dsl__/1

I’m probably missing something obvious, but I can’t spot it.

TechnoMage:

I have not gotten many to many to work quite yet, but mine at lest compile. I suggest looking for references or imports or aliases of Hsm.Ash.MCP.StepCapability given it is in the error and not appearly what you are expecting to be involved.

frankdugan3:

Yeah, pasted the wrong defs. I don’t use aliases to avoid those kinds of problems. Post edited w/ correct definitions. ^

frankdugan3:

Oh, is this as simple as needing to add it to both registries? <:thinkies:915154230078222336>

TechnoMage:

It is making up the resource name ‘steps_join_assoc’ for some reason. It looks like you have through specified, I would double check that the join resource is in the same API and registry as the others.

frankdugan3:

The join resource is in the MCP registry/api, not Workcenters . I’m just not clear on how to deal with that, as specifying the api on either/both many_to_many still produces the same error. It’s also not clear in the docs if the api option in a many_to_many is supposed to point to the API of t he destination resource or the join resource.

TechnoMage:

The docs state that the API option is for the destination resource. Looks like it is a missing option. I would file an issue, and in the mean time add the join resource to both API and Registries.

frankdugan3:

The docs say “related entity” for the api on a many_to_many, which to me is not clearly indicating “destination”, which is the term it consistently uses. I mean, that’s probably what it means, but it’s unclear IMO.

frankdugan3:

Yeah, I’m consistently doing something wrong w/ my cross-api many-to-many relationships because they all have this problem. <@197905764424089601> When you get a chance, could use some guidance. (no rush) 😵‍💫

frankdugan3:

OK, I think the solution was a few steps: 1) add the api options to all the many-to-many relationships, 2) add the join resource to both api/registries and 3) make sure the join resource specifies the API for each relationship. Need to do this to the rest of them to make sure.

zachdaniel:

You shouldn’t need to add the join resource to both apis

zachdaniel:

*registries

zachdaniel:

The idea of the warning is that if you set the api of a many to many relationship, and we generate a has_many for it, we assume that the through resource is in the destination api.

zachdaniel:

If it’s not, then you need to define the underlying has_many yourself and not set an api (or set it to the correct api)

zachdaniel:

And then use that with join_relationship

zachdaniel:

And the api in a many to many should point to the destination resource api.

frankdugan3:

Might be a bug then, because given this:

# Step (MCP API)
many_to_many :capabilities, Hsm.Ash.Workcenters.Capability do
  through Hsm.Ash.MCP.StepCapability
  source_attribute_on_join_resource :step_id
  destination_attribute_on_join_resource :capability_id
  api Hsm.Ash.Workcenters
end

# Capability (Workcenters API)
many_to_many :mcp_steps, Hsm.Ash.MCP.Step do
  through Hsm.Ash.MCP.StepCapability
  source_attribute_on_join_resource :capability_id
  destination_attribute_on_join_resource :step_id
  api Hsm.Ash.MCP
end

# StepCapability (MCP API)
relationships do
  belongs_to :capability, Hsm.Ash.Workcenters.Capability,
    allow_nil?: false,
    api: Hsm.Ash.Workcenters

  belongs_to :step, Hsm.Ash.MCP.Step, allow_nil?: false, api: Hsm.Ash.MCP
end

Hsm.Ash.MCP.StepCapability is added to the MCP registry, but will still give the compile error unless I also add it to the Workcenters registry. <:thinkies:915154230078222336>

zachdaniel:

Well, the through resource is only in one of them

zachdaniel:

So you will always get an error unless you manually define the underlying has_many relationship for one of them

zachdaniel:

not specifying the api for that one (I.e the through resource is in my api not theirs)

zachdaniel:

If you define two many to many relationships across an api boundary without doing that, one of them will always error unless the through resource is in both.

frankdugan3:

Ahh… OK. Is there any harm to just including the through relationship in both registries to avoid the manual has_many ? Just trying to figure out best practice, will PR some of these details in the guides/docs.

zachdaniel:

No harm really, no.

zachdaniel:

But I’d suggest just picking which api it belongs in and on one end define the has_many for the many_to_many

hanrelan:

came up against this now, it doesn’t seem to be documented in the relationship docs anywhere

hanrelan:

actually I’m really confused by what I’m supposed to do here. I’m getting:

an exception was raised:
    ** (RuntimeError) Resource `Scribble.Scribe.UserMedicalTerm` is not accepted by api `Scribble.EmailHandler` for autogenerated join relationship: `:users_join_assoc`

Relationship was generated by the `many_to_many` relationship `:users`

If the `through` resource `Scribble.Scribe.UserMedicalTerm` is not accepted by the same
api as the destination resource `Scribble.EmailHandler.User`,
then you must define that relationship manually. To define it manually, add the following to your
relationships:

    has_many :users_join_assoc, Scribble.Scribe.UserMedicalTerm do
      # configure the relationship attributes
      ...
    end

You can use a name other than `:users_join_assoc`, but if you do, make sure to
add that to `:users`, i.e

    many_to_many :users_join_assoc, Scribble.Scribe.UserMedicalTerm do
      ...
      join_relationship_name :your_new_name
    end

am I supposed to literally call it :users_join_assoc ?

hanrelan:

I think having an example of this in the docs would be really helpful, right now the docs only says that the api must be defined if many_to_many is crossing api boundaries

zachdaniel:

You can call it whatever you want, but then you need to say join_relationship :relationship_name in your many to many.

zachdaniel:

Agreed an example would be helpful

tjaco:

I have a question related to the many_to_many. I have a pretty basic many to many in one Api. Currently I’m seeding some data, but it fails to write the join table when I add the associated data directly in the params. Is this not supported (yet)? In Ecto this would mean adding the cast_assoc for the related items

tjaco:

checking out the managing relationships guide as it seems that’s where I can configure this

zachdaniel:

Yep 🙂 managing relationships (or custom changes w/ after action hooks) is what you’d want there