Trong những bài trước mình đã làm xong TODO App với chức năng cơ bản nhất như Add, Update, Delete, Done, … Hôm này mình sẽ implement thêm chức năng realtime cho những thao tác đó.
Như các bạn đã biết từ Rails 5 trở lên thì đã có hỗ trợ sẵn action cable để làm realtime cho project Rails của mình.
Implement phía Server
Đầu tiên, generate channel:
1 2 | rails generate channel Tasks |
Nó sẽ tạo những files sau đây:
1 2 3 4 | create app/channels/tasks_channel.rb identical app/assets/javascripts/cable.js create app/assets/javascripts/channels/tasks.coffee |
Trong file app/channels/tasks_channel.rb
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token comment"># app/channels/tasks_channel.rb</span> <span class="token keyword">class</span> <span class="token class-name">TasksChannel</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">"tasks_channel"</span> <span class="token keyword">end</span> <span class="token keyword">def</span> unsubscribed <span class="token comment"># Any cleanup needed when channel is unsubscribed</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Ở đây mình sẽ subscribed vào channel có tên là tasks_channel. Những data broadcast đến channel này sẽ nhận được.
Tiêp theo ở trong controller mình sẽ broadcast đến channel tasks_channel với data cần thiết như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <span class="token keyword">def</span> create task <span class="token operator">=</span> <span class="token constant">Task</span><span class="token punctuation">.</span>create<span class="token operator">!</span> task_params <span class="token constant">ActionCable</span><span class="token punctuation">.</span>server<span class="token punctuation">.</span><span class="token function">broadcast</span><span class="token punctuation">(</span><span class="token string">"tasks_channel"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>type<span class="token punctuation">:</span> <span class="token string">"add"</span><span class="token punctuation">,</span> task<span class="token punctuation">:</span> <span class="token constant">Api</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">V1</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">TaskSerializer</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span> render json<span class="token punctuation">:</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span> data<span class="token punctuation">:</span> <span class="token constant">Api</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">V1</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">TaskSerializer</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> <span class="token keyword">def</span> update task <span class="token operator">=</span> <span class="token constant">Task</span><span class="token punctuation">.</span>find params<span class="token punctuation">[</span><span class="token symbol">:id</span><span class="token punctuation">]</span> task<span class="token punctuation">.</span>update_attributes<span class="token operator">!</span> task_params <span class="token constant">ActionCable</span><span class="token punctuation">.</span>server<span class="token punctuation">.</span><span class="token function">broadcast</span><span class="token punctuation">(</span><span class="token string">"tasks_channel"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>type<span class="token punctuation">:</span> <span class="token string">"update"</span><span class="token punctuation">,</span> task<span class="token punctuation">:</span> <span class="token constant">Api</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">V1</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">TaskSerializer</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span> render json<span class="token punctuation">:</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span> data<span class="token punctuation">:</span> <span class="token constant">Api</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">V1</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">TaskSerializer</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> <span class="token keyword">def</span> destroy task <span class="token operator">=</span> <span class="token constant">Task</span><span class="token punctuation">.</span>find params<span class="token punctuation">[</span><span class="token symbol">:id</span><span class="token punctuation">]</span> <span class="token constant">ActionCable</span><span class="token punctuation">.</span>server<span class="token punctuation">.</span><span class="token function">broadcast</span><span class="token punctuation">(</span><span class="token string">"tasks_channel"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>type<span class="token punctuation">:</span> <span class="token string">"delete"</span><span class="token punctuation">,</span> task<span class="token punctuation">:</span> <span class="token constant">Api</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">V1</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">TaskSerializer</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span> task<span class="token punctuation">.</span>destroy<span class="token operator">!</span> render json<span class="token punctuation">:</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> |
Mỗi action đều có type
dùng để bên client biết được là data received là từ những action nào.
Implement phía
Actioncable không chỉ support cho bên server, nó cũng có support cho Reactjs.
1 2 | yarn add actioncable |
Phía Client, chúng ta sẽ cần 3 bước chính như sau:
- Kết nối đến ‘/cable’
- Subscribe tới channel ‘tasks_channel’
- Lắng nghe để nhận data
Trong file src/TodoList.js, trong componentDidMount
modify code như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token keyword">const</span> cable <span class="token operator">=</span> ActionCable<span class="token punctuation">.</span><span class="token function">createConsumer</span><span class="token punctuation">(</span><span class="token string">'ws://192.168.2.103:2000/cable'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Kết nói</span> <span class="token keyword">this</span><span class="token punctuation">.</span>subscriptions <span class="token operator">=</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">'TasksChannel'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment">// subscribe</span> received<span class="token punctuation">:</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// data nhận được</span> <span class="token keyword">if</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'add'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span>items<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>items<span class="token punctuation">,</span> data<span class="token punctuation">.</span>task<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 keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'update'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> updatedData <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>item <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>item<span class="token punctuation">.</span>id <span class="token operator">===</span> data<span class="token punctuation">.</span>task<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token operator">...</span>data<span class="token punctuation">.</span>task<span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> items<span class="token punctuation">:</span> updatedData <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'delete'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> filteredItems <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>item <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> item<span class="token punctuation">.</span>id <span class="token operator">!==</span> data<span class="token punctuation">.</span>task<span class="token punctuation">.</span>id <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> items<span class="token punctuation">:</span> filteredItems <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> <span class="token punctuation">}</span> |
Đến đây là đã xong. =))
Kết quả:
Để hiểu chi tiết về actioncable, bạn hãy vào document của nó xem nhé.