Search code examples
ruby-on-railsrubypostgresqlactiverecordforeign-keys

Correct way of making a polymorphic ActiveRecord association


I have a User model and a Dispute model. A dispute contains a list of involved users, as well as an "accuser" and a "defendant". I want to be able to do this:

Dispute.first.users
#[<User 1>, <User 2>]

Dispute.first.accuser
#<User 1>

Dispute.first.defendant
#<User 2>

So here's my models:

class Dispute < ApplicationRecord
  has_and_belongs_to_many :users
  belongs_to :accuser,    polymorphic: true
  belongs_to :defendant,  polymorphic: true
end
class User < ApplicationRecord
  has_and_belongs_to_many :disputes

  has_one :user, as: :accuser
  has_one :user, as: :defendant
end

Migrations:

class CreateDisputes < ActiveRecord::Migration[5.0]
  def change
    create_table :disputes do |t|
      t.references :accuser, polymorphic: true, index: true
      t.references :defendant, polymorphic: true, index: true
    end
  end
end
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.timestamps
    end
  end
end

This gives me the behavior I want except that Dispute.new.save errors out unless I assign a user to dispute.accuser and dispute.defendant.

It also looks wrong: Shouldn't a user have one dispute as an accuser/defendant? I can't seem to get it to work though.


Solution

  • May I suggest something simpler? I hope I got it right.

    Models:

    class Dispute < ApplicationRecord
      belongs_to :accuser,    class_name: 'user'
      belongs_to :defendant,  class_name: 'user'
      
      def users
        [accuser, defendant]
      end 
    
    end
    
    class User < ApplicationRecord
    end
    

    Migration:

    class CreateDisputes < ActiveRecord::Migration[5.0]
      def change
        create_table :disputes do |t|
          t.references :accuser, index: true, references: :user
          t.references :defendant, index: true, references: :user
        end
      end
    end