2
votes

Currently, it requires recipient_id and sender_id to start a Conversation between 2 users.

How would the associations work if I wanted to allow a 3rd user to join a Conversation?

When a user starts a conversation, the conversation.id belongs to sender_id and recipient_id.

I want visitor_id(which is the current_user) to be able to join any conversation and all users to be able to view all conversations.

conversation.rb

class Conversation < ActiveRecord::Base
  belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
  belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'

  has_many :messages, dependent: :destroy

  validates_uniqueness_of :sender_id, :scope => :recipient_id

  scope :involving, -> (user) do
    where("conversations.sender_id =? OR conversations.recipient_id =?",user.id,user.id)
  end

  scope :between, -> (sender_id,recipient_id) do
    where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND conversations.recipient_id =?)", sender_id,recipient_id, recipient_id, sender_id)
  end
end

user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable


  has_many :conversations, :foreign_key => :sender_id
  after_create :create_default_conversation

I'm curious is if it's easy as adding

belongs_to :visitor, :foreign_key => :visitor_id, class_name: 'User'

to the conversation.rb model. But i'm not sure how i can get visitor_id which belongs to current_user to join a specific conversation (or make all conversations viewable to everyone).

EDIT: Added message.rb and controllers.

message.rb

class Message < ActiveRecord::Base
  belongs_to :conversation
  belongs_to :user

  validates_presence_of :body, :conversation_id, :user_id
end

conversations_controller.rb

class ConversationsController < ApplicationController
  before_filter :authenticate_user!

  layout false

  def create
    if Conversation.between(params[:sender_id],params[:recipient_id]).present?
      @conversation = Conversation.between(params[:sender_id],params[:recipient_id]).first
    else
      @conversation = Conversation.create!(conversation_params)
    end

    render json: { conversation_id: @conversation.id }
  end

  def show
    @conversation = Conversation.find(params[:id])
    @reciever = interlocutor(@conversation)
    @messages = @conversation.messages
    @message = Message.new
  end

  private
  def conversation_params
    params.permit(:sender_id, :recipient_id)
  end

  def interlocutor(conversation)
    current_user == conversation.recipient ? conversation.sender : conversation.recipient
  end
end

messages_controller.rb

class MessagesController < ApplicationController
  before_filter :authenticate_user!

  def create
    @conversation = Conversation.find(params[:conversation_id])
    @message = @conversation.messages.build(message_params)
    @message.user_id = current_user.id
    @message.save!

    #@path = conversation_path(@conversation)
  end

  private

  def message_params
    params.require(:message).permit(:body)
  end
end
1
Are you requesting conversations through the Conversation controller? Or are you using User to fetch involved conversations? If you just ask the conversation index route for all conversations you can get viewable conversations that way.Pyrce
Adding :visitor: would allow for the third party with minimal changes, but would not allow for a 4th or kth additional visitor easily. For that you'd need a join table that tracks all users_conversations.Pyrce
@Pyrce You're right. I can just do '@conversations = Conversation.all' to show all conversations to all users. And that way, any User can join any conversations and talk inside it. But one problem is that there's only 2 people associated with it now ('recipient_id' and 'sender_id'), so when the current_user (who isn't associated with recipient_id and sender_id) sends a message, it shows the incorrect email of who sent it.Raidspec
So you'd need to move the sender attributes from conversation to message and make conversations know about sender/receiver via messages (has_many :senders through: message). That way each message tracks its senders. Your receiver becomes all users in a conversation that didn't send a particular message. Going this route you'd probably want visitors, senders, and receivers to all just be users on a conversation instance. If this sounds right I'll form an answer along these lines.Pyrce
@Pyrce that sounds about right! It also gets rid of the need for visitor_id. I'll update my original question with my models!Raidspec

1 Answers

2
votes

Adding :visitor would allow for the third party with minimal changes, but would not allow for a 4th or kth additional visitor easily. This seems a likely scenario and for that you'd need a join table that tracks all users_conversations.

First create a migration for the join table:

class CreateUsersConversationsJoinTable < ActiveRecord::Migration
  def change
    create_table :users_conversations do |t|
      t.integer :user_id, null: false
      t.integer :conversation_id, null: false
    end
    add_index :users_conversations, [:user_id, :conversation_id], unique: true
  end
end

Then create a model along these lines:

class UsersConversations
  belongs_to :users, class_name: User, inverse_of: :users_conversations
  belongs_to :conversations, class_name: Conversation, inverse_of: :users_conversations
  validates :user_id, :conversation_id, presence: true
end

You'd need to also create migration to move sender and receiver into this new model.

You'd need to move the sender attributes from conversation to message and make conversations know about senders via messages: has_many :senders through: message, foreign_key: sender_id. That way each message tracks its senders.

Your receiver becomes all users in a conversation that didn't send a particular message. Your message class would need something like this added:

has_many :users_conversations, through: :conversations
has_many :receivers, -> { where('users_conversations.user_id != messages.sender_id') }, through: :users_conversations, foreign_key: :user_id, class_name: 'User'

While your conversation class would need to change :between to use/be replaced by users and your scope would look like:

scope :involving, -> (user) do
  users_conversations.where(user: user)
end

To get all conversations you can simply assign @conversations = Conversation.all in the relevant controller, or use the controller index route.