Preamble
Today, real-time web applications are no stranger to us. We observe on social networking applications will see: posts, notifications or messages we will receive information immediately. That is the real time feature (realtime).
There are so many technologies to implement such real-time functionality, WebSocket protocol has emerged as a prominent technology since it was developed in 2009. There was a time ago when implementing WebSocket protocols in Rails is very difficult. We need to use third libraries like Faye or use Javascript libraries. And now it’s a lot easier. And let’s learn about WebSocket and how Rails 6 supports real-time with Action Cable. First we learn about websocket.
1. WebSocket
WebSoket is a technology that supports two-way communication between the client and the server by using a TCP socket to make an efficient and inexpensive connection. They maintain a connection to the server, so that the server can send information to the client, even while there is no request from the client.
WebSockets allow two-way parallel communication channels and are now supported in many browsers (Firefox, Google Chrome and Safari).
The data is transmitted via HTTP protocol (often used with Ajax technique), the header contains the data.
With the support of Action Cable in Rails 6, we can implement WebSockets according to Rails design standards.
2. Action cable
2.1 Introduction
In docs , it is introduced as “full-stack offering”: It provides both client-side JavaScript framework, and Ruby server-side framework. Because it is tied to Rails, we will have to access models from within WebSocket workers
Action Cable can be run independently of the server, or we can set it to run on the inside of the Rails application server.
ActionCable uses the Rack Socket Hijacking API to take over connection control from the server application. ActionCable will then manage the connection in isolation, multithreading, multiple channels.
For each application instance when spins up, an Action Cable instance is created using Rack to open and maintain the connection, and use a cohesive channel on a sub-URI in the application to stream from certain parts of the application and broadcaset to other sections.
ActionCable provides server-side code to broadcast certain content (new message or notification) through the “channel” channel to a “subscriber”. This subscriber is initialized from the clint side with a JS function that uses JQuery to append content to the DOM.
ActionCable uses Redis to store data and synchronize content through instances of the application.
2.2 Some terminology
A server can handle multiple instance connections. Each instance of the connection will correspond to the WebSocket connection. One user can open multiple WebSockets. Each client of the WebSockets connection is called a consumer.
Each consumer can subscribe to multiple channels. A channel is encapsulated in a logical unit, set up like any regular controller in an MVC model.
Once a consumer has subscribed to channels, it acts like a subscriber. The connection between consumer and channels is surprise-surprise, called a subscription.
Each chanels can then stream zero or more than one broadcastings. Broadcast is a pub / sub link where everything transmitted by a publisher routes directly to one of the channel subscribers.
2.3 Pub / Sub
Full of Publisher and Subscriber: The use of the queue mechanism to send messages from an abstract class of the subscriber without the need for a specific recipient. ActionCable uses this method to communicate between client and server.
2.4 Server side
2.4.1 Connection
Connection forms the basis of the client and server relationship. Every time the server accepts websocket, a connection object is initialized. The connection will not perform any other logic except authentication and authorization. The client of the Websocket connection is called the connection consumer.
Connection is an instance of ApplicationCable :: Connection. In this class, we will delegate and make a connection if the user is specified.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token comment"># app/channels/application_cable/connection.rb</span> <span class="token keyword">module</span> <span class="token constant">ApplicationCable</span> <span class="token keyword">class</span> <span class="token class-name">Connection</span> <span class="token operator"><</span> <span class="token constant">ActionCable</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Connection</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Base</span> identified_by <span class="token symbol">:current_user</span> <span class="token keyword">def</span> connect <span class="token keyword">self</span> <span class="token punctuation">.</span> current_user <span class="token operator">=</span> find_verfied_user <span class="token keyword">end</span> <span class="token keyword">protected</span> <span class="token keyword">def</span> find_verfied_user user_id <span class="token operator">=</span> cookies <span class="token punctuation">.</span> signed <span class="token punctuation">[</span> <span class="token symbol">:user_id</span> <span class="token punctuation">]</span> <span class="token operator">||</span> request <span class="token punctuation">.</span> session <span class="token punctuation">[</span> <span class="token symbol">:user_id</span> <span class="token punctuation">]</span> <span class="token constant">User</span> <span class="token punctuation">.</span> <span class="token function">find_by</span> <span class="token punctuation">(</span> id <span class="token punctuation">:</span> user_id <span class="token punctuation">)</span> <span class="token operator">||</span> reject_unauthorized_connection <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
2.4.2 Channels
A channel is packaged in a logical unit, set up like normal controllers in MVC model. By default, Rails creates a parent class for packaging logical sharing between ApplicationCable :: Channel channels
Parent channels:
1 2 3 4 5 6 | <span class="token comment"># app/channels/application_cable/channel.rb</span> <span class="token keyword">module</span> <span class="token constant">ApplicationCable</span> <span class="token keyword">class</span> <span class="token class-name">Channel</span> <span class="token operator"><</span> <span class="token constant">ActionCable</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Channel</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Base</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
We can create our own channels with the command: rails g channel Notifications
1 2 3 4 5 6 7 8 9 10 11 | <span class="token comment"># app/channels/notifications_channel.rb</span> <span class="token keyword">class</span> <span class="token class-name">NotificationsChannel</span> <span class="token operator"><</span> <span class="token constant">ApplicationCable</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Channel</span> <span class="token keyword">def</span> subscribed stream_from <span class="token string">"notifications: <span class="token interpolation"><span class="token delimiter tag">#{</span> current_user <span class="token punctuation">.</span> id <span class="token delimiter tag">}</span></span> "</span> <span class="token keyword">end</span> <span class="token keyword">def</span> unsubscribed stop_all_streams <span class="token keyword">end</span> <span class="token keyword">end</span> |
2.5 Client side
Consumers will require an instance of the client-side connection, which can be done using Javascript, which will default to being generated by Rails.
1 2 3 4 5 6 | <span class="token comment">// app/javascript/channels/consumer.js</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> createConsumer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@rails/actioncable"</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">createConsumer</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> |
Connect to the server based on / cable, The connection will not be established until you have at least one subscription.
1 2 3 | <span class="token comment">#config/routes.rb</span> mount <span class="token constant">ActionCable</span> <span class="token punctuation">.</span> server <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">'/cable'</span> |
A consumer becomes a subscriber by subscribing to a channel, A consumer can act as a subscriber to subscribe to multiple channels.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">// app/javascript/channels/notifications_channel.js</span> <span class="token keyword">import</span> consumer <span class="token keyword">from</span> <span class="token string">"./consumer"</span> consumer <span class="token punctuation">.</span> subscriptions <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token string">"NotificationsChannel"</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token function">connected</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Called when the subscription is ready for use on the server</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">disconnected</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Called when the subscription has been terminated by the server</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">received</span> <span class="token punctuation">(</span> data <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
2.6 Interaction between server side and client side
2.6.1 Stream
Streams provide a channel routing mechanism to pulished content to subscribers.
1 2 3 4 5 6 7 | <span class="token comment"># app/channels/notifications_channel.rb</span> <span class="token keyword">class</span> <span class="token class-name">NotificationsChannel</span> <span class="token operator"><</span> <span class="token constant">ApplicationCable</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Channel</span> <span class="token keyword">def</span> subscribed stream_from <span class="token string">"notifications: <span class="token interpolation"><span class="token delimiter tag">#{</span> current_user <span class="token punctuation">.</span> id <span class="token delimiter tag">}</span></span> "</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
If the stream relates to a model, the use of broadcast can be made from the model and the channel.
2.6.2 Broadcasting
Broadcast is a pub / sub link where everything transmitted by a publisher routes directly to one of the channel subscribers. A channel may not stream to broadcasts or multiple broadcasts.
Broadcast is completely a queue and depends on time. If a consumer does not subscribe to a channel they will not be broadcast.
1 2 3 4 5 6 7 8 9 | <span class="token comment">#app/jobs/notification_broadcast_job.rb</span> <span class="token keyword">class</span> <span class="token class-name">NotificationBroadcastJob</span> <span class="token operator"><</span> <span class="token constant">ApplicationJob</span> queue_as <span class="token symbol">:default</span> <span class="token keyword">def</span> <span class="token function">perform</span> <span class="token punctuation">(</span> notification <span class="token punctuation">)</span> <span class="token constant">ActionCable</span> <span class="token punctuation">.</span> server <span class="token punctuation">.</span> broadcast <span class="token string">"notifications: <span class="token interpolation"><span class="token delimiter tag">#{</span> notification <span class="token punctuation">.</span> user_id <span class="token delimiter tag">}</span></span> "</span> <span class="token punctuation">,</span> notification <span class="token punctuation">:</span> notification <span class="token keyword">end</span> <span class="token keyword">end</span> |
2.6.3 Subscriptions
When a consumer subscribes to a channel, becomes a subscriber, the connection between the subscriber and the channel is called the subscription.
These messages are routed to these channel subscriptions based on the identifier sent by the cable consumer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">// app/javascript/channels/notifications_channel.js</span> <span class="token keyword">import</span> consumer <span class="token keyword">from</span> <span class="token string">"./consumer"</span> consumer <span class="token punctuation">.</span> subscriptions <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token string">"NotificationsChannel"</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token function">connected</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Called when the subscription is ready for use on the server</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">disconnected</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Called when the subscription has been terminated by the server</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">received</span> <span class="token punctuation">(</span> data <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
2.6.4 Pass parameters to channels
We can pass params from the client to the server when creating new subsciptions:
1 2 3 4 5 6 7 | <span class="token comment"># app/channels/notifications_channel.rb</span> <span class="token keyword">class</span> <span class="token class-name">NotificationsChannel</span> <span class="token operator"><</span> <span class="token constant">ApplicationCable</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Channel</span> <span class="token keyword">def</span> subscribed stream_from <span class="token string">"notifications: <span class="token interpolation"><span class="token delimiter tag">#{</span> current_user <span class="token punctuation">.</span> id <span class="token delimiter tag">}</span></span> "</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
The first parameter passed to subscriptions.create becomes the params hash in the cable channel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">// app/javascript/channels/notifications_channel.js</span> <span class="token keyword">import</span> consumer <span class="token keyword">from</span> <span class="token string">"./consumer"</span> consumer <span class="token punctuation">.</span> subscriptions <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token string">"NotificationsChannel"</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token function">connected</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Called when the subscription is ready for use on the server</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">disconnected</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Called when the subscription has been terminated by the server</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">received</span> <span class="token punctuation">(</span> data <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
3. Realtime notification
Step 1
Add gem to gemfile file
1 2 3 | gem <span class="token string">"devise"</span> gem <span class="token string">"redis"</span> <span class="token punctuation">,</span> <span class="token string">"~> 3.0"</span> |
Step 2
Modify the config of config / cable.yml file
1 2 3 4 | development <span class="token punctuation">:</span> adapter <span class="token punctuation">:</span> redis url <span class="token punctuation">:</span> redis <span class="token punctuation">:</span> <span class="token operator">/</span> <span class="token operator">/</span> localhost <span class="token punctuation">:</span> <span class="token number">6379</span> <span class="token operator">/</span> <span class="token number">1</span> |
Execute the bundle install
command to install and restart the server.
Step 3
Devise install User: rails g devise:install
and rails g devise User
Create model notification:
1 2 | rails g model Notification user:references recipient_id:integer action notifiable_type notifiable_id:integer |
And run the rails db:migrate
command to create the corresponding table in the database
Step 4
Relationship for models
Model Users
1 2 3 | <span class="token comment">#app/models/user.rb</span> has_many <span class="token symbol">:notifications</span> <span class="token punctuation">,</span> as <span class="token punctuation">:</span> <span class="token symbol">:recipient</span> |
Model Notification
1 2 3 4 5 | <span class="token comment">#app/models/notification.rb</span> belongs_to <span class="token symbol">:user</span> belongs_to <span class="token symbol">:recipient</span> <span class="token punctuation">,</span> class_name <span class="token punctuation">:</span> <span class="token string">"User"</span> belongs_to <span class="token symbol">:notifiable</span> <span class="token punctuation">,</span> polymorphic <span class="token punctuation">:</span> <span class="token keyword">true</span> |
Step 5
Create view
1 2 3 4 | <span class="token comment">#app/views/main/index.html.erb</span> <span class="token operator"><</span> div id <span class="token operator">=</span> <span class="token string">"notifications"</span> <span class="token operator">></span> <span class="token operator"><</span> <span class="token operator">/</span> div <span class="token operator">></span> |
Step 6
ActionCable will allow you to open the chanel and keep the chanel connected to the server without having to refresh the page. First we will initialize the project chanel with syntax
1 2 | rails g channel notifications |
1 2 3 4 5 | create app/channels/notifications_channel.rb identical app/javascript/channels/index.js identical app/javascript/channels/consumer.js create app/javascript/channels/notifications_channel.js |
Rails will automatically create two more files for us, app/channels/notifications_channel.rb
and app/javascript/channels/notifications_channel.js
Step 7
Set the connetion on the server side
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token comment"># app/channels/application_cable/connection.rb</span> <span class="token keyword">module</span> <span class="token constant">ApplicationCable</span> <span class="token keyword">class</span> <span class="token class-name">Connection</span> <span class="token operator"><</span> <span class="token constant">ActionCable</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Connection</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Base</span> identified_by <span class="token symbol">:current_user</span> <span class="token keyword">def</span> connect <span class="token keyword">self</span> <span class="token punctuation">.</span> current_user <span class="token operator">=</span> find_verfied_user <span class="token keyword">end</span> <span class="token keyword">protected</span> <span class="token keyword">def</span> find_verfied_user <span class="token keyword">if</span> current_user <span class="token operator">=</span> env <span class="token punctuation">[</span> <span class="token string">'warden'</span> <span class="token punctuation">]</span> <span class="token punctuation">.</span> user current_user <span class="token keyword">else</span> reject_unauthorized_connection <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
If you do not use devise, but use session, cookies, the find_verfied_user function will look like this:
1 2 3 4 5 | <span class="token keyword">def</span> find_verfied_user user_id <span class="token operator">=</span> cookies <span class="token punctuation">.</span> signed <span class="token punctuation">[</span> <span class="token symbol">:user_id</span> <span class="token punctuation">]</span> <span class="token operator">||</span> request <span class="token punctuation">.</span> session <span class="token punctuation">[</span> <span class="token symbol">:user_id</span> <span class="token punctuation">]</span> <span class="token constant">User</span> <span class="token punctuation">.</span> <span class="token function">find_by</span> <span class="token punctuation">(</span> id <span class="token punctuation">:</span> user_id <span class="token punctuation">)</span> <span class="token operator">||</span> reject_unauthorized_connection <span class="token keyword">end</span> |
Step 8
Set up channels on server side as follows:
1 2 3 4 5 6 7 8 9 10 11 | <span class="token comment"># app/channels/notifications_channel.rb</span> <span class="token keyword">class</span> <span class="token class-name">NotificationsChannel</span> <span class="token operator"><</span> <span class="token constant">ApplicationCable</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Channel</span> <span class="token keyword">def</span> subscribed stream_from <span class="token string">"notifications: <span class="token interpolation"><span class="token delimiter tag">#{</span> current_user <span class="token punctuation">.</span> id <span class="token delimiter tag">}</span></span> "</span> <span class="token keyword">end</span> <span class="token keyword">def</span> unsubscribed stop_all_streams <span class="token keyword">end</span> <span class="token keyword">end</span> |
Step 9
Establish a connection on the client side
1 2 3 | <span class="token comment">#config/routes.rb</span> mount <span class="token constant">ActionCable</span> <span class="token punctuation">.</span> server <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">'/cable'</span> |
Step 10
Set up Subscribers on the client side
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">#app/assets/javascripts/channels/notifications.js</span> <span class="token constant">App</span> <span class="token punctuation">.</span> notifications <span class="token operator">=</span> <span class="token constant">App</span> <span class="token punctuation">.</span> cable <span class="token punctuation">.</span> subscriptions <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token string">"NotificationsChannel"</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> connected <span class="token punctuation">:</span> <span class="token function">function</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">/</span> <span class="token operator">/</span> <span class="token constant">Called</span> <span class="token keyword">when</span> the subscription is ready <span class="token keyword">for</span> use on the server <span class="token punctuation">}</span> <span class="token punctuation">,</span> disconnected <span class="token punctuation">:</span> <span class="token function">function</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">/</span> <span class="token operator">/</span> <span class="token constant">Called</span> <span class="token keyword">when</span> the subscription has been terminated by the server <span class="token punctuation">}</span> <span class="token punctuation">,</span> received <span class="token punctuation">:</span> <span class="token function">function</span> <span class="token punctuation">(</span> data <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">/</span> <span class="token operator">/</span> <span class="token constant">Called</span> <span class="token keyword">when</span> there's incoming data on the websocket <span class="token keyword">for</span> this channel $ <span class="token punctuation">(</span> <span class="token string">"#notifications"</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">prepend</span> <span class="token punctuation">(</span> data <span class="token punctuation">.</span> html <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Step 11
Create a job to perform the response for the client.
1 2 | rails g job NotificationRelay |
1 2 3 4 5 6 7 8 9 10 | <span class="token comment">#app/jobs/notification_relay_job.rb</span> <span class="token keyword">class</span> <span class="token class-name">NotificationRelayJob</span> <span class="token operator"><</span> <span class="token constant">ApplicationJob</span> queue_as <span class="token symbol">:default</span> <span class="token keyword">def</span> <span class="token function">perform</span> <span class="token punctuation">(</span> notification <span class="token punctuation">)</span> html <span class="token operator">=</span> <span class="token constant">ApplicationController</span> <span class="token punctuation">.</span> render partial <span class="token punctuation">:</span> <span class="token string">"notifications/ <span class="token interpolation"><span class="token delimiter tag">#{</span> notification <span class="token punctuation">.</span> notifiable_type <span class="token punctuation">.</span> underscore <span class="token punctuation">.</span> pluralize <span class="token delimiter tag">}</span></span> / <span class="token interpolation"><span class="token delimiter tag">#{</span> notification <span class="token punctuation">.</span> action <span class="token delimiter tag">}</span></span> "</span> <span class="token punctuation">,</span> locals <span class="token punctuation">:</span> <span class="token punctuation">{</span> notification <span class="token punctuation">:</span> notification <span class="token punctuation">}</span> <span class="token punctuation">,</span> formats <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token symbol">:html</span> <span class="token punctuation">]</span> <span class="token constant">ActionCable</span> <span class="token punctuation">.</span> server <span class="token punctuation">.</span> broadcast <span class="token string">"notifications: <span class="token interpolation"><span class="token delimiter tag">#{</span> notification <span class="token punctuation">.</span> recipient_id <span class="token delimiter tag">}</span></span> "</span> <span class="token punctuation">,</span> html <span class="token punctuation">:</span> html <span class="token keyword">end</span> <span class="token keyword">end</span> |
Step 12
Finally, we will use the Call Record of Active Record to call Jobs to make a response to the client.
1 2 3 | <span class="token comment">#app/models/notification.rb</span> after_commit <span class="token operator">-</span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token constant">NotificationRelayJob</span> <span class="token punctuation">.</span> <span class="token function">perform_later</span> <span class="token punctuation">(</span> <span class="token keyword">self</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
summary
Thus, we have learned about action cable and applied the practice to a simple case of creating a realtime message. We will need to revise and practice more to understand and further refine this feature and later on can apply to other more difficult articles.
My article here is the end. Hope your article can help you better understand the action cable and use it in the darkest and most flexible way. The article is also difficult to avoid mistakes, hope everyone is sympathetic, and look forward to the comments of everyone to make the article more complete. Thank you for taking the time to write your own post !!!
References
https://edgeguides.rubyonrails.org/action_cable_overview.html#server-side-components https://gist.github.com/excid3/4ca7cbead79f06365424b98fa7f8ecf6 https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable