Skip to main content

Subscribe to GoRails to get access to this episode and all other pro episodes, and new awesome content every month.

Subscribe Now
Only $19/month

Unlimited access. Cancel anytime.

15 Direct Messages in Realtime with ActionCable

Episode 143 · September 27, 2016

Add realtime direct messages between users in our chat application using ActionCable

ActionCable


Transcripts

Subscribe or login to view the transcript for this episode.

Discussion


Gravatar

Shouldn't be better to create a model lets say conversations with both users "sender and receiver" and maybe make messages a polymorphic association between rooms and conversations?

Gravatar

A bit counterintuitively, that actually makes things more complex if you treat direct messages that way. By doing that you complicate the database significantly because now you can't simply join tables since you're using polymorphic assocations. Plus your direct messages no longer have a Chatroom object meaning all your messaging code on the frontend and backend need to account for the different types and treat them differently.

By treating DMs as a special Chatroom with only 2 users, you can run the exact same Javascript and server-side code without modifications. The only rules you need to set in place (like we did in the episode) are to specify that Direct Message channels are only between two users. That's the only difference between a direct message channel and a public channel making this a lot easier to maintain.

Gravatar

Makes sense. I think my approach fits well in a case where i only need to communicate between 2 users. Thanks!


Gravatar

Hey Chris, thank you for this tutorial. I asked about it a few weeks ago and I got to a solution with your feedback but it was not as technically sound when I did it on my own and it didn't work as well tbh. Have you considered doing something with WebRTC for video/voice conferencing or using something like Twilio, Pusher etc, I think that would be a kickass project and could complement this one pretty well. :) Looking forward to all the new screencasts!

Gravatar

Absolutely. Some WebRTC stuff is on the agenda, but not for a little while. I'm really interested in checking out Twilio's WebRTC functionality. First gotta cover a handful of other topics like APIs, but that'll be coming after those things!

Gravatar

Awesome! Looking forward to all of it.


Gravatar

Hi Chris. I have a question for you. I was playing with the code and wanted to color the username depending on if you are or if you are not the speaker.

In rails 5 and actionable (this what I am seeing in your code), unless I am wrong, you have to 2 places that need to be touched in order to so the formatting on the messages. first, on the view (here this will be under your _message.html.erb partial) that will retrieve and render when you load the page. second, on the javascript channels (coffee file. in your case chatrooms.coffee).

I was able to color code the user (mine or others) depending on the speaker name on the view side but the channel seem to be quite different as it streaming to both. I simply compared User.find(message.user_id) to current_user.

On the channel side (used for updating the messages after you have reached the page), I am not sure how to introduce that test?

Gravatar

Hey Stan,

I would say either do client side or server side generation of colors. Server side, you could save a "color" attribute on the user that's an RGB version of their username so you only have to generate it when they sign up. You can pass that value over the websocket and use it for rendering of messages

If you went with the client side, you could have it generate the colors on all the existing messages on the page (might need to add a data attribute for the username) and then apply the colors to the existing messages on page load, and then add it to the new messages afterwards. You can just simply store those on the style="" attribute on the span around the username so that it's added directly to the record.

Either approach would work fine. The client side one is more likely to be simpler and less data intensive because it can generate colors on the fly without storing them. I would probably go with this approach personally.

Gravatar

Hi Chris,

yes i used the client side. To achieve this, I created a current_user style class in the <head> section on the main application.html.erb:

<style>
.user<%=current_user.id%>
{
background-color: red;
}
</style>

then I added this class to the span of both the _message partial and the chatrooms.coffee. each message will be marked with the user_id of the speaker and if recognized as being the current_user, CSS will spot it! ;-)

indeed, by default all usernames are styled the same but this class takes precedence as being the only one defined specifically.

It is nice and working.

Gravatar

Gravatar

Has someone seen support for file attachment using actionable?

Gravatar

You'll actually want to combine websockets with direct uploads (see: https://gorails.com/episode... ) in order to accomplish this. You don't want to upload files via websockets because they're not a good medium for this. Instead, you can upload files like you normally would and then you can use the response JSON from the upload to include the file in your application.

For example, Slack file uploads submit an AJAX file upload, you send over the message via ajax so you can upload the file and then once that's complete, it broadcasts it over websockets.


Gravatar

Here is a question. I am developing a feature where users can direct message other users directly, without the whole chatroom.
Is it possible to use actioncable the same way to have it set up this way:
A user clicks on a "chat" button on another user's profile.
It opens a little chat box in the same window (facebook chat for example)
Ability to message other users directly through that.

Thank you.

Gravatar

Absolutely. You can create any UI you want for this. My example is more like Slack, but you can do the same thing and use the button to initialize displaying a popup chat box instead.

Gravatar

I built the pop up UI in the user's profile show instead of the room show.
So now I am having trouble passing all the data @message @room and so on on that pop up UI since they are not part of the user's controller.
Also, I can see how it would work if I end up passing them but everyone will be receiving the messages like in the public chatrooms... I might need some help getting this to work in order for people to be able to direct chat each other through that UI instead of having everyone receives what one person sends each time.
Thank you.

Gravatar

Just like we did in this episode, you create direct messages between two users by using them in the pubsub channel name. Nothing different there.

For the popup UI, add data attributes to the link so when it's clicked you can find the User ID of the person you want to direct message. Then you have both the current user, and the recipient ID which you can use to start the pubsub channel between the two people.

Gravatar

Allright, here is what I did in the room show.html for now:

<% (User.all - [current_user]).each do |user| %>
<li><%= link_to user.fullname, "#", data: { behavior: "room-link",
room_id: Room.direct_message_for_users([current_user, user]).id
}, :class => "open-btn addClass" %></li>
<% end %>

The "addClass" class is the class from the javascript.
javascript, new file named room.js in assets/javascripts:

$(document).ready(function() {
$(function(){
$(".addClass").click(function (event) {
$('#sidebar_secondary').addClass('popup-box-on');
event.preventDefault();
});

$("#removeClass").click(function () {
$('#sidebar_secondary').removeClass('popup-box-on');
});
})
});

It opens the javascripts and sends the messages, but it still sends the messages publicly. What am I missing?

Thank you.

Gravatar

I believe I found what may be the problem.
I need to find a way to render the pop up js instead of the rooms/show in the DirectMessageController. What do you think?

Where I'm at now is that if I enter the public room url: (rooms/61) for example, all the users buttons open the js pop up where I can talk to the public room.
If I enter a specific users private message url (direct_messages/34) for example, then all the users buttons open the js pop up for the current logged in user to talk to that one specific user 34.


Gravatar

Hi there. If wanted to build a 1-to-1 direct message system for my application, would you suggest I build the prototype from "Group Chat with ActionCable" series first to get started? It's hard to know how I should approach this since you are starting off this video by using code already built in a previous series. Any help is greatly appreciated. Thanks!

Gravatar

It would probably be helpful to watch those because they setup the foundation for this which is kind of like a chatroom restricted to just two people. Lots of different ways you can go about it, but this is just one approach that works nicely to reuse the features of group chat.


Gravatar

You have this on a repo?


Login or create an account to join the conversation.