Rails many-to-many self relationship failing validation

Viewed 35

I'm building a toy Rails app that will run tournaments. The thing with this type of tournament is though is that rounds can have both multiple input rounds and multiple output rounds (i.e., you can't model a tournament's rounds as a tree, but it's still a DAG).

I'm running into an issue where I believe I have my associations set up properly, but Rails is complaining about the models suddenly becoming invalid, and I can't figure out how to get more information.

I have these relevant ActiveModel classes:

class Round < ApplicationRecord
    belongs_to :tournament
    has_and_belongs_to_many :players

    has_many :previous_round_edges, foreign_key: :end_id, class_name: "RoundEdge"
    has_many :previous_rounds, through: :previous_round_edges, source: :start

    has_many :next_round_edges, foreign_key: :start_id, class_name: "RoundEdge"
    has_many :next_rounds, through: :next_round_edges, source: :end
end
class RoundEdge < ApplicationRecord
    belongs_to :start, foreign_key: :start_id, class_name: "Round"
    belongs_to :end, foreign_key: :end_id, class_name: "Round"
end

The code in question that triggers errors:

final_round = rounds.build()
players.each_slice(4) do |player_chunk|
  round = rounds.build(players: player_chunk, position: round_position)
  # Objects are valid right until we start associating them with each other
  round.next_rounds << final_round
  final_round.previous_rounds << round
end

I've also tried throwing in a debugger, and these are the results:

round.errors
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=next_round_edges, type=invalid, options={}>, #<ActiveModel::Error attribute=next_rounds, type=invalid, options={}>]>

final_round.errors
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=previous_round_edges, type=invalid, options={}>, #<ActiveModel::Error attribute=previous_rounds, type=invalid, options={}>]>

If I look at the relations though, they seem correct:

round
=> #<Round:0x000000010fc0e760 id: nil, position: 0, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>

final_round
=> #<Round:0x00000001104e2580 id: nil, position: nil, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>

round.previous_rounds
=> []

round.next_rounds
=> [#<Round:0x00000001104e2580 id: nil, position: nil, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>]

final_round.previous_rounds
=> [#<Round:0x000000010fc0e760 id: nil, position: 0, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>]

final_round.next_rounds
=> []

So it looks like they point to each other fine, but I can't figure out what is going on.

Thanks!

1 Answers

I figured it out! I needed to manually specific the inverse relations. What was happening was that even though the Rounds were linked, the RoundEdges` were not being created properly.

The solution was to update the Round class to look as follows:

class Round < ApplicationRecord
    belongs_to :tournament
    has_and_belongs_to_many :players

    has_many :previous_round_edges, foreign_key: :end_id, class_name: "RoundEdge", inverse_of: :end
    has_many :previous_rounds, through: :previous_round_edges, source: :start, inverse_of: :next_rounds

    has_many :next_round_edges, foreign_key: :start_id, class_name: "RoundEdge", inverse_of: :start
    has_many :next_rounds, through: :next_round_edges, source: :end, inverse_of: :previous_rounds
end
Related