Skip to main content
Realtime Group Chat With ActionCable:

Group Chat with ActionCable: Part 5

17

Episode 133 · August 17, 2016

Sending chat messages from the browser to the server with ActionCable

ActionCable


Transcripts

What's up guys? This episode we're going to take and convert the AJAX form for submitting a new message in a chat room, and we're going to convert that using ActionCable, and the reason why we're going to do that is because we've already got this open connection, and it has to be open, otherwise our chat is kind of useless, so we might as well take advantage of that and send our messages across through ActionCable instead of an AJAX request, so we're going to refactor what we've built and convert it to using ActionCable. The first thing that we want to do is actually look at the form itself, so I've pulled that up here in the chatroom/show.html.erb and we have our messages container, and our form just underneath that, and we were currently submitting this as remote= true which will send it over with jquery ujs as an AJAX request. Now this is fine, but we want to remove that because we want to intercept this form submission with our own code in order to go use the chatrooms ActionCable channel. We're going to write our own JavaScript and instead of jquery ujs doing it, we're going to do the intercept and that will be that. This is pretty straightforward, we're going to need to pull out the chatroom id and we're also going to need to pull out the message text from the body field here so we'll need to write some JavaScript to pull that off. We've already written a little function here that says anytime you type the enter key in that message box, we're going to not do a new line and we're going to submit that to the server, so this really says: Well if the character code or the key code is 13, which is the Enter key, we're going to not insert a new line so that was the default, so we're going to prevent the default and then we're going to submit that form. We can actually say

assets/javascripts/chatrooms.coffee

  $("#new_message").on "submit", (e) ->
    e.preventDefault()
    console.log "SUBMITTED"

Let's refresh this page and let's type: "hello random" and if hit enter, like I just did, nothing happens. That is a good sign, because then you should see a submitted form in the console and that means that our JavaScript here it not only listened to that Enter key in the first function, it submitted the form with this first dot submit, and immediately after that this function to this submit grabbed it and then canceled the submission. This is where we're going to need to grab both the chatroom id and the message text for that text field. First we can really just grab the chatroom id and this is pretty straightforward, it actually is on the messages container, and so we have the data-behavior messages class or attribute and then on that tag we can grab the chatroom id so we'll just take that one place that we will always know that will be there because the rest of our JavaScript will use that, so we'll just make sure that we grab it from there, so we'll have data-behavior

assets/javascripts/chatrooms.coffee

  $("#new_message").on "submit", (e) ->
    e.preventDefault()

    chatroom_id = $("[data-behavior='messages']").data("chatroom-id")
    body        = $("#message_body")

assets/javascripts/channels/chatrooms.coffee

  send_message: (chatroom_id, message) ->
    @perform "send_message", {chatroom_id: chatroom_id, body: message}

We'll send over this object, this JSON object, and so the server side will receive that. If you go into your

app/channels/chatrooms_channel.rb

def send_message 
    Rails.logger.info data
end 

So this @perform is calling send_message and we'll have some data that we receive which will be this object here and we will be able to receive that. We'll have this set up so that we can perform that send message function server side, and we can just pass that data over, and ActionCable is going to know what to do, it will convert it to the ruby function. It will call that function, it will execute that server side and do whatever you want. That means all we have to do now is to go back to our original

chatrooms.coffee

 App.chatrooms.send_message(chatroom_id, body.val())

We're going to grab the value of the field, so the actual text that you type and we'll send that over as the message argument which will get sent over again to the server side channel and will be received as one object, because we have this in a JSON object. This makes it all into one thing, and then we have data on the server which is that one thing, and it's just the hash and we can pull that apart and display. Let's save all of this. Refresh our page here, and then let's type "Test random" and hit Enter, and this should submit and we should be able to look at our logs here, and we'll see that user two received this message which was the info that we did. The Rails.logger.info, so you can see that the user 2 called the send_message function and passed over this JSON hash that got converted into a ruby hash, or JSON object, so this got converted to a ruby hash and you have full access to all of that stuff just from the ruby side of things just like you did from the client side. So it's cool it actually just transmits this data and says: Well from the JavaScript you can call any functions that you want server side and you're good to go as long as they're defined in chatrooms_channel.rb so as long as you access the right ActionCable subscription in your JavaScript, you can always call those methods server side from your JavaScript, so that's pretty neat. This is not very hard, and then the last thing that we want to do is say

chatrooms.coffee

body.val("") 

That will go clear out that string which when we hit enter here it didn't do anything. And if we do it now, and we hit Enter, it sends it over to the server it logs it on the server and then it clears this out. And so that is all working. The only thing we need to do next is to take that and instead of logging that message server side like we did here, we can actually just take this and transmit or broadcast that to all of the recipients for this channel for that chatroom. So we have the ID so we know the id and we're basically able to do the exact same publish as we did with the AJAX action. Now the question is: How do we actually go save that message and then send it out and broadcast it again? Well conveniently we already did that, so we can go into that messages controller that we created before and we can basically duplicate all that work that this does and put it in that function in the chatrooms_channel.rb, so basically we can set that chatroom just as we did before and we can say

chatrooms_channel.rb

def send_message(data)
    @chatroom = Chatroom.find(data)["chatroom_id"]
    message = @chatroom.messages.create(body: data["body"], user: current_user)
    MessageRelayJob.perform_later(message)
end 

This is going to effectively do the same thing that the entire controller action did but we'll be able to do that over the websocket connection instead. We don't really need this controller anymore because we're not going to be submitting this over AJAX. This won't ever really be that useful and you could probably go and delete this controller if you wanted. That is going to work, and we should be able to restart our rails servers, and sometimes when you're changing those channels you'll need to restart your rails server because once it's already loaded and the connections are live and all that you can't actually change it and get live updates like you can with the new request. So let's test this out and refresh our browser and get the latest JavaScript and let's test random, hit enter, and voila. We've sent that now over the websocket connection it has landed in the channel, it processes it, saves it in a database, and it sends it over to the same relay job which goes to your background workers which then talks to your ActionCable redis connection again which sends it out to everybody, and then everybody receives it and displays it in the browser.

Now we're doing quite a bit of stuff with ActionCable, we're receiving messages, we're sending messages, we have the ability to add new features in so like if I receive a message I can send a little notice to the server and say I've read up to this time and we can keep track of unread times and all that stuff which we're going to do in a future episode, but I do want to point out that now that we've done this, ActionCable is 100% critical to be running in our application. The way we had it before, where the form submitted with AJAX, if ActionCable was down, yeah, we don't receive new messages, but we can still send them and they will still work as long as the rails app is up. But now that that's sending goes over the websockets as well, the rails app could be functional, but if ActionCable isn't, our app doesn't do anything. It isn't able to save any messages and that's a problem, so you have to kind of keep in mind what you want to use your websockets for and determine weather or not it's crucial for it to be up as much as possible. Of course in a chatroom application you're going to focus more on this connection being up as much as possible, but if it's sort of a side project or something where you're doing notifications, you might consider making the requests over AJAX and sending data and just using it for receiving notifications. GitHub is a good example of this where the GitHub issues will update in real time using websockets but if you were to go down you don't really lose out on much of that user experience at the end of the day. You just kind of lose out that someone commented, but you can still comment and you can still use it even if they're struggling keeping their websocket servers up for whatever reason, like maybe they blow up in traffic one day and they're able to keep the website up but not the websockets. It's just something to keep in mind. It's now a new dependency that your 100% relying on, and that should affect the way that you go build your application, just to keep that in mind so that when you build something that really when it goes down it goes down hard. That's kind of just be something to keep in mind so that you can build a lot more sturdy applications just in case something bad happens.

That's it for this episode, we will be diving in most likely in the next one to have the ability to mark as unread so that we can see when the most recent messages were displayed. That should be fun but we will save that to the next episode. I hope you enjoyed it, I'll talk to you in the next one.

Transcript written by Miguel

Loading...

Subscribe to the newsletter

Join 18,000+ developers who get early access to new screencasts, articles, guides, updates, and more.

By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

More of a social being? We're also on Twitter and YouTube.