A simple guide to Action Cable
Action cable is the Rails way of implementing WebSockets - with some Rails magic.
Why use it
Usually your client to connects with your server by making requests:
With ActionCable, you create an open connection between your client and your server, allowing a communication flow:
Example:
You have a simple blog - posts and comments - and multiple users reading that post. If one user adds a comment, the other one will never know:
But with the open connection from ActionCable, he will receive updates from that posts (the important comments from other people)
How to do it
First of all, generate a channel for your Posts. This class will be able to broadcast updates to all clients listening:
rails generate channel posts
Which will create some files for you:
create test/channels/posts_channel_test.rb create app/channels/posts_channel.rb identical app/javascript/channels/index.js identical app/javascript/channels/consumer.js create app/javascript/channels/posts_channel.js
Sending messages
We will work with our newly generated posts_channel.rb
We want to specify from which channel to stream, so we can pass the an id
params and ask rails to make a stream for that post:
class PostsChannel < ApplicationCable::Channel def subscribed post = Post.find(params[:id]) stream_for post endend
And now, from anywhere in our app, we can call PostsChannel and ask it to broadcast something to anyone listening to that post:
PostsChannel.broadcast_to(@post, @comment.body)
We will add this to our create action, to broadcast the comment to the post channel every time a comment is created:
# app/controllers/comments_controller.rbdef create @comment = @post.comments.new(comment_params) if @comment.save PostsChannel.broadcast_to(@post, @comment.body) redirect_to @post, notice: "Comment was successfully created." else render :new endend
And that does nothing so far, since no one is listening to this broadcast. Moving forward!
Receiving messages
Opinionated setup:
I do not like to create a separate file for every consumer, I prefer to do the connection in script tags in the view. It feels more like a separate front end, where only the views that need a connection create one. To do so, change your app/javascript/channels/consumer.js
to expose the action cable:
import { createConsumer } from "@rails/actioncable"(function() { window.App || (window.App = {}); window.App.cable = ActionCable.createConsumer();}).call(this);export default createConsumer()
Note: Exposing the cable was the default according to official docs until Rails 6, where Webpacker was introduced
By default, the generator we used before creates a file in app/javascript/channels/posts_channel.js
This might be bad for some reasons:
- It it always required, so it always run
- We might not want to have everyone on our app to try and open a connection, just the ones listening to our post show page
So you can go ahead and delete the created posts_channel.js
Now, we can make the listener to our broadcast in the view:
<!-- app/views/posts/show.html.erb --><script> App.cable.subscriptions.create({ channel: "PostsChannel", id: "<%= @post.id %>" }, { connected() { console.log("Connected to the channel:", this); }, disconnected() { console.log("Disconnected"); }, received(data) { console.log("Received some data:", data); } });</script>
And now, upon opening our blog post page, we can see the connected message on our Console, and some Rails main that enabled this connection on our terminal:
The posts:Z2lkOi8vYWN0aW9uY2FibGUtYXBwL1Bvc3QvMg
is the name of the channel created by rails when we told it to stream_for post
in our posts_channel file.
And youre done!
~Almost~
While the above script received data, it doesnt show it on the page. We can update it to add the comment to our list upon receiving it:
App.cable.subscriptions.create({ channel: "PostsChannel", id: "<%= @post.id %>" }, { received(comment) { el = document.createElement('li'); el.innerHTML = comment; document.querySelector('ul').appendChild(el) } });
(All functions are optional, I removed the disconnected and connected from here)
And there you go, your app now talks to any browser listening to it via Action Cable:
References
Rails Guide:
https://guides.rubyonrails.org/action_cable_overview.html
Heroku Guide:
https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable
Cable.yml Config:
https://github.com/rails/rails/issues/28118
Cable for specific pages:
https://stackoverflow.com/questions/39597665/rails-actioncable-for-specific-pages
https://stackoverflow.com/questions/36438323/page-specific-actioncable
Good JS subscription examples:
https://stackoverflow.com/questions/39597665/rails-actioncable-for-specific-pages
https://samuelmullen.com/articles/introduction-to-actioncable-in-rails-5/
https://stackoverflow.com/questions/36266746/actioncable-not-receiving-data
Usage with ActiveJob
https://www.pluralsight.com/guides/creating-a-chat-using-rails-action-cable
Cable on ReactNative
https://stackoverflow.com/questions/43510021/action-cable-not-receiving-response-on-client
AnyCable
Action Cable vs AnyCable: fight! | Nebulab
Original Link: https://dev.to/lucaskuhn/a-simple-guide-to-action-cable-2dk2
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To