Rails: very elaborate associations - ruby-on-rails

I'm having a lot of issues with my messaging system. I would love some help. It works fully, except the User Message relation. I'm attempting to relate authored_messages to User and received_messages to User. The roadblock from doing this is that received_messages is supposed to go through two different models (ConversationUser and Conversation), whereas authored_messages should be a direct relationship using the message's user_id field. Here she is:
Models
User:
class User < ActiveRecord::Base
has_many(
:conversation_users,
inverse_of: :user,
dependent: :destroy
)
has_many(
:conversations,
through: :conversation_users
)
# has_many :messages ?
# has_many( ?
# :received_messages,
# class_name: "Messages",
# through: :conversations,
# source: :conversation_users
# )
Message:
class Message < ActiveRecord::Base
belongs_to :user # ?
belongs_to :recipient, class_name: "User" # ?
belongs_to(
:conversation,
inverse_of: :messages
)
has_many(
:conversation_users,
through: :conversation
)
accepts_nested_attributes_for :conversation
Conversation:
class Conversation < ActiveRecord::Base
has_many(
:messages,
inverse_of: :conversation
)
has_many(
:conversation_users,
inverse_of: :conversation
)
has_many(
:users,
through: :conversation_users
)
accepts_nested_attributes_for :messages
ConversationUser (a join model):
class ConversationUser < ActiveRecord::Base
belongs_to(
:user,
inverse_of: :conversation_users
)
belongs_to(
:conversation,
inverse_of: :conversation_users
)
has_many(
:messages,
through: :conversation
)
delegate :users, to: :conversation
accepts_nested_attributes_for :conversation
Migrations
User
# None involved with messaging
Message
t.integer :user_id
t.integer :conversation_id
t.text :body
Conversation
# None, except the :id field. It helps relate messages and conversation_users
ConversationUser
t.integer :user_id
t.integer :conversation_id
It seems to me that I'd be adding something like this (how do I associate one model twice to another), but I'm having a hard time applying this to my models. And to further clarify how these associations work, this is a really simplified visual:
users--->conversation_users
: |
: |
V V
messages<---conversation
I'd really, really appreciate any help I can get, and hopefully this question will help other people dealing with complex associations!
EDIT
I forgot to mention it, but this messaging system can have as many recipients (conversation_users) as the sender decides. (when the sender creates a new conversation)

How do you know whether a message is sent or received?
In your message model, you're only referencing user_id and conversation_id. You'd typically want recipient_id and sender_id instead of just user_id:
class User < ActiveRecord::Base
has_many :conversation_users, inverse_of: :user, dependent: :destroy
has_many :conversations, through: :conversation_users
end
class Conversation < ActiveRecord::Base
has_many :conversation_users
has_many :users, through: conversation_users
has_many :sent_messages, class: "Message", through: :conversations, foreign_key: "sender_id", source: :user
has_many :received_messages, class: "Message", through: :conversations, foregin_key: "recipient_id", source: :user
end
class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :sender, class_name: "User", primary_key: "sender_id"
belongs_to :recipient, class_name: "User", primary_key: "recipient_id"
end
messages
t.integer :sender_id
t.integer :recipient_id
t.integer :conversation_id
t.text :body
This should yield:
#user.conversations[x].sent_messages
#user.conversations[x].received_messages

I ended up figuring it out via further trial and error. I was making it much harder than it was. Here are the correct changes for the User and Message classes and the relations between them:
Message
class Message < ActiveRecord::Base
belongs_to :user # This
belongs_to(
:conversation,
inverse_of: :messages
)
has_many(
:conversation_users,
through: :conversation
)
has_many( # This
:recipients,
class_name: "User",
through: :conversation_users,
source: :user
)
accepts_nested_attributes_for :conversation
User
class User < ActiveRecord::Base
has_many(
:conversation_users,
inverse_of: :user,
dependent: :destroy
)
has_many(
:conversations,
through: :conversation_users
)
has_many :messages # This
has_many( # This
:received_messages,
class_name: "Message",
through: :conversations,
source: :messages
)
The associations I marked with # This are the four relations I was having trouble connecting in the first place. This messaging system is a unique four-model messaging system that supports (hypothetically) limitless conversation users. Its behavior is not unlike many other social network messaging systems out there. It is fully functional, and any of these four class objects can be accessed from any other related class object. I hope this Q/A will serve as a great reference for anyone trying to build a similar messaging system. Cheers!

Related

Create a relationship that allows a user to follow multiple types of records

I am currently working on a system that allows a user to follow different records (funds or wallets) but I am blocking on relationships.
I have three models: User, Fund and Wallet.
A user must be able to follow to multiple funds or wallets, and a fund or wallet must be able to have multiple followers.
The goal is to be able to do user.follows which would give me the list of records he follows (funds and wallets), and to do fund.followers or wallet.followers and get the list of users who follow these records.
I have created this table:
class CreateFollows < ActiveRecord::Migration[6.0]
def change
create_table :follows do |t|
t.integer :user_id
t.integer :followable_id
t.string :followable_type
t.timestamps
end
end
end
then I did:
class User < ApplicationRecord
has_many :follows, dependent: :destroy
end
class Follow < ApplicationRecord
belongs_to :follower, foreign_key: :user_id, class_name: 'User', inverse_of: :follows
belongs_to :followable, polymorphic: true
end
and this concern that I added to Fund and Wallet models:
module Followable
extend ActiveSupport::Concern
included do
has_many :followers, dependent: :destroy
end
end
I know it can't work as it is now, but I don't really understand what to put in the User model and in my concern. Can someone show me the path please?
I finally did the following:
class Follow < ApplicationRecord
belongs_to :follower, foreign_key: :user_id, class_name: 'User', inverse_of: :follows
belongs_to :followable, polymorphic: true
end
class User < ApplicationRecord
has_many :follows, dependent: :destroy
has_many :funds_followings, through: :follows, source: :followable, source_type: 'Fund'
has_many :wallets_followings, through: :follows, source: :followable, source_type: 'Wallet'
def followings
funds_followings + wallets_followings
end
end
module Followable
extend ActiveSupport::Concern
included do
has_many :follows, foreign_key: :followable_id, dependent: :destroy, inverse_of: :followable
has_many :followers, through: :follows, as: :followable
end
end
So I'm able to do everything I wanted.

Rails: Favoriting System - models associations

I have the following model associations setup were a consumer can favorite a product or a variant. I just wanted to ask if my approach is correct?
class Favorite < ApplicationRecord
belongs_to :consumer
belongs_to :favorited, polymorphic: true
belongs_to :product, optional: true
belongs_to :variant, optional: true
end
class Consumer < ApplicationRecord
has_many :favorites
has_many :favorite_products, through: :favorites, source: :favorited, source_type: 'Product'
has_many :favorite_variants, through: :favorites, source: :favorited, source_type: 'Variant'
end
class Product < ApplicationRecord
has_many :favorites, dependent: :destroy
end
class Variant < ApplicationRecord
has_many :favorites, dependent: :destroy
end
class CreateFavorites < ActiveRecord::Migration[6.0]
def change
create_table :favorites do |t|
t.references :consumer, index: true
t.references :favorited, polymorphic: true, index: true
t.integer :product_id
t.integer :variant_id
t.timestamps
end
end
end
It is not clear why you have both polymorphic favorited and separate relation on product_id/variant_id. Usually one would go with one of these approaches.
Polymorphic associations can be unidirectional too - has_many :favorites, as: :favorited, dependent: :destroy
If you go with separate columns for each relation - indexes on product_id and variant_id may also be useful to prevent full table scan on product/variant deletion (on dependent: :destroy, also if you do not expect callbacks/nested relations there - delete_all is faster).

has_many through - Notes sender and receiver

I'm working on a rails app which has three models.
class User < ApplicationRecord; end
class Share < ApplicationRecord; end
class Note < ApplicationRecord; end
create_table :users do |t|
t.timestamps
end
create_table :notes do |t|
t.integer 'user_id'
t.text 'title'
t.text 'short_description'
t.string 'name'
t.timestamps
end
create_table :shares do |t|
t.integer 'user_id'
t.integer 'receiver_id'
t.integer 'note_id'
t.timestamps
end
How can I create associations between them so, I can get
Notes which are shared by User A.
Notes which are received by User A.
Notes which are created by User A.
#Mehmet Adil İstikbal gives part of the answer so I'll try to complete it.
This is another way to do it using only associations :
class User < ApplicationRecord
has_many :created_notes, class_name: 'Note', foreign_key: :user_id
has_many :received_shares, foreign_key: :receiver_id, class_name: 'Share'
has_many :received_notes, through: :received_shares, source: :note
has_many :shares
has_many :shared_notes, through: :shares, source: :note
end
class Share < ApplicationRecord
# Optional
belongs_to :creator, class_name: 'User', foreign_key: :user_id
belongs_to :receiver, class_name: 'User', foreign_key: :receiver_id
# Mandatory
belongs_to :note
end
class Note < ApplicationRecord ; end
user_a = User.first
user_a.shared_notes
user_a.received_notes
user_a.created_notes
If you choose #Mehmet Adil İstikbal answer, please make sure to transform
user.shares.each {|share| share.note} to user.shares.map(&:note) (Use map and not each)
My answer uses has_many through association which allows you to go "through" join table.
In user model you can do like this:
has_many :shares, foreign_key: 'user_id', class_name: 'Share', dependent: :destroy
has_many :receives, foreign_key: 'receiver_id', class_name: 'Share', dependent: :destroy
and you can call like this:
User.first.shares.each {|share| share.note}
This will get all shares with first users id and all of their notes.
For receiver :
User.first.receives.each {|share| share.note}
In your share model you can also specify the opposite connection like this:
belongs_to :sender, foreign_key: 'user_id', class_name: 'User'
belongs_to :receiver, foreign_key: 'receiver_id', class_name: 'User'
With this you can call:
Share.first.receiver this will get you to user that receives this post
And for the notes which are created by user you can call:
User.first.notes
You may want to delete those dependents in order to your project.
Hope it helps

Is there a way to have two join tables associate the same two classes in a rails application?

Long time listener, first time caller. I'm trying to create two associations between the same database tables, Chatrooms and Users. What I have so far is a has_many through relationship where a Chatroom has many Users through Messages. This part works fine. What I want to do is to create a second join table that connects Chatrooms to Users, through a join table called Chatroom_players. So what I'd like is for Chatroom.first.users to get me users through the messages join table and Chatroom.first.players to get me everyone from the chatroom_players join table. The reason I want this is so that I can maintain user presence even if a user hasn't written any messages in the chat, also so that a user can leave the room but maintain his or her messages in the chat.
Here is what I have so far that does not work:
chatroom.rb:
class Chatroom < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :users, through: :messages
has_many :chatroom_players
has_many :users, through: :chatroom_players
end
message.rb:
class Message < ApplicationRecord
belongs_to :chatroom
belongs_to :user
validates :content, presence: true, length: {minimum: 2, maximum: 200}
end
chatroom_player.rb
class ChatroomPlayer < ApplicationRecord
belongs_to :chatroom
belongs_to :user
end
user.rb
class User < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :chatrooms, through: :messages
has_many :chatroom_players
has_many :chatrooms, through: :chatroom_players
end
chatroom_players migration:
class AddChatroomPlayers < ActiveRecord::Migration[5.0]
def change
create_table :chatroom_players do |t|
t.references :user, index: true, foreign_key: true, null: false
t.references :chatroom, index: true, foreign_key: true, null: false
t.boolean :creator, default: false
t.timestamps null: false
end
end
end
You need to use different names for the associations:
class Chatroom < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :users, through: :messages
has_many :chatroom_players
# this is a separate association to users through the
# chatroom_players table.
has_many :participants,
through: :chatroom_players,
source: :user, # what association on chatroom_players to use
class_name: 'User' # since it cannot be deduced automatically
end

has_many (different objects) as: one type of object

I need a way of referrencing 2 different objects as 1.
I have a Message object with needs to keep track of Recipients. the problem is that Recipients could be a User or a Contact.
should the models be: ?
class Message < ActiveRecord::Base
has_many :users, as: :recipients
has_many :contacts, as: :recipients
end
class User < ActiveRecord::Base
belongs_to :recipient, polymorphic: true
end
class User < ActiveRecord::Base
belongs_to :recipient, polymorphic: true
end
because, I feel like polymorphic relationships are built to go the opposite way.
also, this way doesn't allow me to reference #message.recipients which is what I need.
I hope this makes sense
Thank you
What you have done is completely incorrect. I think you need many-to-many association. My association whould be that:
class Message < ActiveRecord::Base
has_many :recipient_links
has_many :users, through: :recipient_links, source: :recipient, source_type: 'User'
has_many :contacts, through: :recipient_links, source: :recipient, source_type: 'Contact'
end
class Contact < ActiveRecord::Base
has_many :recipient_links, as: :recipient
has_many :messages, through: :recipient_links
end
class User < ActiveRecord::Base
has_many :recipient_links, as: :recipient
has_many :messages, through: :recipient_links
end
# fields: message_id, recipient_id, recipient_type
class RecipientLink < ActiveRecord::Base
belongs_to :recipient, polymorphic: true
belongs_to :message
end
... I can't add comments yet so i give here solution for: When use answer from up to receive all #message.recipients every type in only 2 request:
RecipientLink.includes(:recipient).where(message_id: #message.id).collect(&:recipient)

Resources