PrimeTimeTran
PrimeTimeTran

Reputation: 2137

ActiveRecord Polymorphic association

I have a project that allows users to have conversations with other users. Conversations can contain multiple users via a UserConversations modal. UserConversations need to be polymorphic so that they can also belong to Chatrooms. However, when I add the polymorphic association my relationships break.

class User < ApplicationRecord
  has_many :user_conversations, dependent: :destroy
  has_many :conversations, through: :user_conversations
end

class UserConversation < ApplicationRecord
  belongs_to :user
  belongs_to :parent, polymorphic: true
  has_many :conversations, through: :user_conversations
end

class Conversation < ApplicationRecord
  has_many :messages, as: :context, dependent: :destroy
  has_many :user_conversations, as: :parent, dependent: :destroy
  has_many :users, through: :user_conversations
end

class Chatroom < ApplicationRecord
  belongs_to :venue
  has_many :messages, as: :context, dependent: :destroy
  has_many :user_conversations, as: :parent, dependent: :destroy
  has_many :users, through: :user_conversations
end


2.3.1 :009 > user = User.first
  User Load (1.8ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC 
LIMIT $1  [["LIMIT", 1]]
 => #<User id: 1, name: "Loi Tran", email: "[email protected]", 
password_digest: "$2a$10$eDZ6IyTAzVK4qNvJfIhPP.3fhEIhdv0bWuVqrTjJk86...",         
image_url: "https://scontent.fsgn5-2.fna.fbcdn.net/v/t1.0-1/p3...",         
created_at: "2017-08-21 07:20:11", updated_at: "2017-08-29 18:47:04", city: 
"Tallahassee", state: "Florida", position: "Getting yelled at", school: 
"Florida State University", quote: "If it was easy, everyone would do it.", 
avatar: nil, last_name: "Tran", first_name: "Loi">

2.3.1 :010 > user.user_conversations
  UserConversation Load (0.8ms)  SELECT  "user_conversations".* FROM 
"user_conversations" WHERE "user_conversations"."user_id" = $1 LIMIT $2  
[["user_id", 1], ["LIMIT", 11]]
 => #<ActiveRecord::Associations::CollectionProxy [#<UserConversation id: 453, user_id: 1, created_at: "2017-09-10 06:01:27", updated_at: "2017-09-10 06:01:27", parent_type: "Chatroom", parent_id: 8>, #<UserConversation id: 454, user_id: 1, created_at: "2017-09-10 06:02:22", updated_at: "2017-09-10 06:02:22", parent_type: "Conversation", parent_id: 318>]>

2.3.1 :011 > user.conversations
NoMethodError: undefined method `klass' for nil:NilClass
Did you mean?  class
    from (irb):11

I'm using Rails 5.1.3 & ruby 2.3.1

I need user.conversations to work. Please help!

Upvotes: 0

Views: 917

Answers (3)

max
max

Reputation: 102016

Use two join models instead, its both simpler and will avoid the major cons of polymorphism:

  • No foreign key support as the DB does not know what table the association points to.
  • Joins are tricky since you have to query the table to know what table to join. Which is really bad for a join table.

Since join tables just consist of two columns and the model has very little logic using polymorphism does not really give you anything but headaches.

class User < ApplicationRecord
  has_many :user_conversations, dependent: :destroy
  has_many :conversations, through: :user_conversations
  has_many :chatroom_users, dependent: :destroy
  has_many :chatrooms, through: :chatroom_users
end

class UserConversation < ApplicationRecord
  belongs_to :user
  belongs_to :conversation
end

class Conversation < ApplicationRecord
  has_many :messages, as: :context, dependent: :destroy
  has_many :user_conversations, dependent: :destroy
  has_many :users, through: :user_conversations
end

class ChatroomUser < ApplicationRecord
  belongs_to :user
  belongs_to :chatroom
end

class Chatroom < ApplicationRecord
  belongs_to :venue
  has_many :messages, as: :context, dependent: :destroy
  has_many :chatroom_users, dependent: :destroy
  has_many :users, through: :user_chatrooms
end

Upvotes: 1

PrimeTimeTran
PrimeTimeTran

Reputation: 2137

This is how I got the result I was looking for(finding a users conversations). It's not as elegant as I would like but I think it serves the purpose.

# user.rb

def conversations
  Conversation.where(id: user_conversations.where(parent_type: 
  "Conversation").map(&:parent_id))
end

Upvotes: 0

Amro Abdalla
Amro Abdalla

Reputation: 584

According to rails documentation when using has_many through (http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association), if we consider UserConversation is the middle table between User and conversations, it should be something like this:

class User < ApplicationRecord
  has_many :user_conversations
  has_many :conversations, through: :user_conversations 
end

and

class UserConversation < ApplicationRecord
  belongs_to :user
  belongs_to :conversation
end

and

class Conversation < ApplicationRecord
  has_many :user_conversations
  has_many :users, through: :user_conversations
end

Put the rest of your relations and :dependent again.

Upvotes: 0

Related Questions