rubyactiverecordsinatrasinatra-activerecord

Rails, using a belongs_to and has_one association to the same class


Here's a picture I made which visually describes the relationship I'm seeking: enter image description here

In the picture I say Interaction, but more specifically these interactions are in-game Kills. A kill, as we all know, has the killer, and the victim. In this case our killer will be called player, and our victim will be called victim.

Given this picture, here are some example queries I'd like to perform:

Player.find(1).Interactions
> [<#Interaction id: 1>, <#Interaction id: 2>, <#Interaction id: 3>]

Interactions.where(player: Player.find(1))
> [<#Interaction id: 1>, <#Interaction id: 2>, <#Interaction id: 3>]

Interactions.where(victim: Player.find(1))
> [<#Interaction id: 4>]

Player.find(1).Interactions.where(victim: Player.find(2))
> [<#Interaction id: 2>]

Player.find(1).Interactions.count()
> 3

# Player.find(1).Interactions should (ideally) return Interactions where <#Player id: 1> is the player (not victim)

Interactions.all.count()
> 4

Player.all.count()
> 4

Player.find(2).Interactions
> []

Player.find(3).Interactions
> [ <#Interaction id: 4> ]

Player.find(4).Interactions
> []

I've tried a lot of different associations (I've been digging through google and Stack Overflow results for hours)

This is how I think my Interaction model should look:

class Interaction < ActiveRecord::Base
  belongs_to :player
  has_one :victim, :class_name => 'Player'
end

And my Player model:

class Player < ActiveRecord::Base
  has_man :kills
end

And finally, how I think the migration should look:

class CreatePlayerAndInteraction < ActiveRecord::Migration[5.0]
  def change
    create_table :players do |t|
      t.string :steam_id
    end

    create_table :interactions do |t|
      t.string :weapon
      t.belongs_to :player, index: true
    end
end

Solution

  • interactions table just needs a victim_id column and then change the has_one to belongs_to :victim, class_name: Player.

    This will work since an Interaction is basically a join table of Player to Player.

    has_one implies that the victim (Player in this case) would have a interaction_id which is incorrect.

    Instead an Interaction belongs to the killer and the victim.

    Setup as:

    class Player
      has_many :kills, class_name: Interaction, foreign_key: :player_id inverse_of: :killer
      has_many :deaths, class_name: Interaction, foreign_key: :victim_id, inverse_of: :victim 
    end
    
    class Interaction
      belongs_to :killer, class_name: Player, foreign_key: :player_id, inverse_of: :kills
      belongs_to :victim, class_name: Player, foreign_key: :victim_id, inverse_of :deaths
    end