ActionCable: 101 — Ruby on Rails

Mateus Braga
Sep 9, 2018 · 6 min read

(Before we start keep in mind that this is not a content about 100% of this feature or even covers half of all the documentation, It is just some kind of “Action Cable in a nutshell”.)

‘Action Cable seamlessly integrates WebSockets with the rest of your Rails application. It allows for real-time features to be written in Ruby in the same style and form as the rest of your Rails application, while still being performant and scalable. It’s a full-stack offering that provides both a client-side JavaScript framework and a server-side Ruby framework. You have access to your full domain model written with Active Record or your ORM of choice.’

Basically, ActionCable is the new “good way” (I would to say ~hype~ ;D) to handle asynchronous changes in our DOM! That means after our view is rendered, we cannot see any changes on the screen without refreshing the page but ActionCable ‘listen’ to those changes and re-render our DOM with no hard work.

Okay, first of all, lets create our new rails project:

rails new cable-in-action

okay, now, lets create a Home controller just to see what is about to happen!

rails g controller Home index

and then we define our root path on our routes.rb: (just add the following line)

root 'home#index'

Our localhost:3000 should look like this by now:

Let’s create a controller to add some messages (to be rendered on our home page)

rails g controller Messages create

(I’m using only a create method because I don’t feel like I need to use anything else than that — at least for now :D)

and inside our create method (in app/messages_controller.rb):

message = Message.create!(params.permit(:message))

and of course, lets create our model Message to have our messages that have been sent, stored in our database.

rails g model Message message:text

Now we add a little form on our root index view, just to write and send our message:

<%= form_with url: 'messages' do |form| %>  <%= form.text_field :message %>  <%= form.submit %><% end %>

(don’t forget to)

rake db:migrate

(quick note: if you are having any issue with version, try just to prepend the ~bundle exec ~on your command)

and to make that work we add the following line in our config/routes.rb

resources :messages

and our page look like this now:

Now lets get all messages and render them on our index page. Just add the following line on our home/index.html.erb

in our action index (app/home_controller.rb):

def index  @messages = Message.allend

and in our view:

<%= @messages.each do |message|%>  <div><%= message %></div><% end %>

Now, to check if there is any new message sent, we should refresh the page and the new messages will show up:

the problem is, the page is being refreshed automatically every time we click the save button by our message controller

.
.
.
redirect_to root_path

(if we take out that line, new messages only show up if we refresh the page manually)

That said, Action Cable comes to our rescue, and helps us to create a Really efficient Single Page Application. In our case its a very simple app, so lets go do our first step. (FINALLY!)

A few things we should keep in mind, ActionCable needs ~redis~ and ~jquery~ installed so:

you need to install JQUERY:

$ yarn add jquery

and after that we should require it on our application.js

//= require jquery

With that done, install redis on your machine.

brew install redis

then add the gem on your gemfille

gem 'redis'

then, bundle it:

$ bundle install

All that set, lets create our ~channel~:

rails g channel WebMessages

That last command line generated two filles for us: one is the

1 - app/channels/web_messages_channel.rb

and the other one is

2 - app/assets/javascripts/channels/web_messages.coffee. 

In our app/channels/web_messages_channel.rb (1) we should uncomment the line inside the subscribed method and stream the channel we just created:

def subscribed  stream_from "web_messages_channel"end

In our channels/web_messages.coffee (2) we are going to treat all the data exchange. Take a look at the structure of that fille:

App.web_messages = App.cable.subscriptions.create "WebMessagesChannel",connected: ->  # Called when the subscription is ready for use on the serverdisconnected: ->  # Called when the subscription has been terminated by the serverreceived: (data) ->  # Called when there's incoming data on the websocket for this channel

So, every time a ~connection~ is made, wether connecting or not, we could do something!

So basically, ActionCable works upon javascript! so, in our rails app, using coffescript, we should add a div with an id in our view (just so our js could find where to go and what to do with that):

<div id="messages"></div>

and, in our web_messages.coffee, within the received method we add the following line:

received: (data) ->
$('#messages').append data['message']

and now, to see our view being render we just add a method from action cable when a new message is saved:

class MessagesController < ApplicationController  def create

message = Message.create!(params.permit(:message))
if message.save
ActionCable.server.broadcast 'web_messages_channel', message: "<div> <h2>#{message.message}</h2> </div>"
end
end
end

we are passing ‘message:…’ because we are receiving ‘message’ in our ‘received: (data)’ method.

Now, this our app should works like this:

Although, its still weird that our input doesn’t be blank after we pressed the send button. so lets add this to our app.

within our form tag, added a id=”messagesInput”:

<%= form_with url: 'messages' do |form| %>  <%= form.text_field :message, id: "messageInput"%>
<%= form.submit %>
<% end %>

and then, inside our web_messages.coffee we add:

$('#messageInput').val('')

so, by now, your files should look like this:

  • app/assets/javascripts/channels/web_messages.coffe
App.web_messages = App.cable.subscriptions.create "WebMessagesChannel",connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
$('#messages').append data['message']
$('#messageInput').val('')
# Called when there's incoming data on the websocket for this channel
  • app/channels/web_messages_channel.rb
class WebMessagesChannel < ApplicationCable::Channel  def subscribed
stream_from "web_messages_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end

and

  • app/views/home/index.html.erb
<h1>Home#index</h1><p>Find me in app/views/home/index.html.erb</p><%= form_with url: 'messages' do |form| %>
<%= form.text_field :message, id: "messageInput" %>
<%= form.submit %>
<% end %>
<div id="messages">
<% @messages.each do |message|%>
<div>
<h2> <%= message.message %> </h2>
</div>
<% end %>
</div>

How our app looks like now:

Take a Look at the documentation: https://guides.rubyonrails.org/action_cable_overview.html

In Addition, Action Cable currently is the fastest way to handle asynchronous changes on our DOM and It’s a very easy tool to configure. That’s it for now! you can get in touch with through twitter.

Mateus Braga

Written by

github.com/bragamat - linkedin.com/in/bragamat

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade