I am creating a simple chatting app on rails 4. The controllers, models and views are created but the functionality is still incomplete. I have 2 tables in my database, conversations and messages. The conversation table holds two fields, sender id and receiver id. And the messages table holds 3 fields, body, user id and read(defaults to 0 meaning not read).
Models:
class Conversation < ActiveRecord::Base
belongs_to :sender, :foreign_key => :sender_id, :class_name => "User"
belongs_to :reciever, :foreign_key => :reciever_id, :class_name => "User"
has_many :messages, :dependent => :destroy
validates_uniqueness_of :sender_id, :scope => :reciever_id
scope :involving, lambda { |user_id|
where("sender_id = ? OR reciever_id = ?", user_id, user_id)
}
scope :between, lambda { |sender_id, reciever_id|
where("(sender_id = ? AND reciever_id = ?) OR (sender_id = ? AND reciever_id = ?)", sender_id, reciever_id, reciever_id, sender_id)
}
def other_interlocutor(user_id)
if sender.id == user_id
return reciever.id
else
return sender.id
end
end
end
class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
validates_presence_of :conversation_id, :user_id, :body
end
What I am trying to do is to create a real-time functionality of receiving unread messages count whenever someone receives a new message. I am using private pub to create the chat between users.
I have a user model which contains this function:
def unread_messages_count
unread_messages = 0
# puts "Putting self conversations ! #{self.conversations.first}"
conversations = Conversation.involving(self.id)
conversations.each do |conversation|
unread_messages += conversation.messages.where(:read => 0, :user_id => conversation.other_interlocutor(self.id)).count
end
return unread_messages = unread_messages == 0 ? nil : unread_messages
end
I have one page where all user's conversations are listed and one a conversation is clicked all the messages related to that conversation are listed too. On the same page I have subscribed every conversation_messages_path
to create separate channels for every conversation. Whenever a message is sent a create.js.erb
file is rendered where I publish to those subscribed channels:
<% publish_to conversation_messages_path(@conversation.id) do %>
$("#conversations_link").text("<%= current_user.unread_messages_count %> Conversations");
$("#messages").append("<%= escape_javascript render(:partial => 'message', :locals => { :message => @message })%>");
<% end %>
The $("#conversation_link")
is where I want to show the unread messages count.
Currently, the unread messages count is returning back the wrong count and the navbar is only updated when the conversation.sender_id
messages the receiver.
My unread message counter is not returning the correct number of unread messages. I don't know how to fix it. What's wrong in my code? Thanks.
I would argue that your domain modeling is really off.
The whole idea of a conversation is that the parties involved take turns being the the sender and recipient. What your have modeled is a monologue.
The domain model you end up with here should look something like this:
Its the message that is actually linked to two users (or more): the
sender
and therecipient
. For the sake of simplicity i'm sticking to 1:1 messaging here (vs group chats where a message may belong to many recipients).Note that we need to tells Rails the class and the foreign key when it cannot be derived from the association name.
Instead of having a boolean
read
field you might want to consider using a enum to denote the status of the message.Enums are basically an integer column which maps to a list of symbols.
Enums give you scopes like:
And conditionals such as:
And it makes it very straight forward if you want to add more states such as
:archieved
or:trashed
.Armed with this you don't need your
unread_messages_count
monstrosity. Which will eat tons of memory since you are pulling records out of the database just to count the associated records.Also we should define the relation between User and Conversation correctly:
This has_and_belongs_to_many relation would store users and converstations in the
users_conversations
join table. You can use the following generator to create the join table migration:Added:
Using
session[:user_id]
in your views is a really bad code smell. You are tightly coupling your authentication logic all over your application.Instead create a helper:
Other parts of the application should not know that you store the current user in
session[:user_id]
just that there is acurrent_user
.