Forked from davidpaulhunt/stream-comments-with-rails-5-and-action-cable.md
Created
November 6, 2020 17:44
Revisions
-
davidpaulhunt revised this gist
Dec 28, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ # Rails 5 and ActionCable Assumptions: The application already exists. You have two models `article.rb` and `comment.rb`. Articles have two attributes, `title` and `text`. Comments have two attributes, `text` and `article_id`. See these [instructions](https://gist.github.com/davidpaulhunt/a185d1753e438c505227) if you need help getting started. #### Routes Assuming that you are nesting your `:comments` resources inside of `:articles`, mount `ActionCable` and make sure you have a root. -
davidpaulhunt revised this gist
Dec 28, 2015 . 1 changed file with 0 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -153,8 +153,6 @@ $('#new_comment').replaceWith('<%=j render 'comments/new', article: @article %>' <%= f.submit 'Add comment' %> <% end %> ``` #### Acknowledgements This article is based on the [video tutorial](https://www.youtube.com/watch?v=n0WUjGkDFS0) by DHH, his [actioncable-examples](https://github.com/rails/actioncable-examples), and the [public README](https://github.com/rails/rails/tree/master/actioncable) available with ActionCable. -
davidpaulhunt revised this gist
Dec 28, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # Rails 5 and ActionCable Assumptions: The application already exists. You have two models `article.rb` and `comment.rb`. Articles have two attributes, `title` and `text`. Comments have two attributes, `text` and `article_id`. -
davidpaulhunt revised this gist
Dec 28, 2015 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,5 @@ # ActionCable Assumptions: The application already exists. You have two models `article.rb` and `comment.rb`. Articles have two attributes, `title` and `text`. Comments have two attributes, `text` and `article_id`. #### Routes -
davidpaulhunt revised this gist
Dec 28, 2015 . 1 changed file with 0 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,3 @@ Assumptions: The application already exists. You have two models `article.rb` and `comment.rb`. Articles have two attributes, `title` and `text`. Comments have two attributes, `text` and `article_id`. #### Routes -
davidpaulhunt created this gist
Dec 28, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,161 @@ # Stream Comments with Rails 5 and ActionCable Scenario: You have a personal blog application that allows readers to leave anonymous comments. You want the comments to appear dynamically. Rather than bringing in a third party library, you want to use Rails 5 and it's ActionCable feature. Assumptions: The application already exists. You have two models `article.rb` and `comment.rb`. Articles have two attributes, `title` and `text`. Comments have two attributes, `text` and `article_id`. #### Routes Assuming that you are nesting your `:comments` resources inside of `:articles`, mount `ActionCable` and make sure you have a root. <small>_config/routes.rb_</small> ```rb Rails.application.routes.draw do resources :articles do resources :comments end mount ActionCable.server => '/cable' root 'articles#index' end ``` #### Models We don't want `ActionCable` or anything else within our flow to raise an error before we have a chance to record the submitted comment. To protect against this, we're going to render our comment via a background job. First, update the model to use an **after_create_commit**. The _commit_ part is important. <small>_app/models/comment.rb_</small> ```ruby class Comment < ApplicationRecord belongs_to :article after_create_commit { RenderCommentJob.perform_later self } end ``` #### Jobs Rails 5 has made the ApplicationController and its methods more widely available. This makes rendering so much easier. We're going to utilize this to render our new comment. <small>_app/jobs/render_comment_job.rb_</small> ```ruby class RenderCommentJob < ApplicationJob queue_as :default def perform(comment) ActionCable.server.broadcast "article:#{comment.article_id}:comments", foo: render_comment(comment) end private def render_comment(comment) ApplicationController.renderer.render(partial: 'comments/comment', locals: { comment: comment }) end end ``` #### Controllers Our controller is simple. We're going to create our comment and allow the js to render itself. <small>_app/controllers/comments_controller.rb_</small> ```ruby class CommentsController < ApplicationController before_action :set_article def create @comment = Comment.create! text: params[:comment][:text], article: @article end private def set_article @article = Article.find(params[:article_id]) end end ``` #### Channels Okay, this is the bread and butter. We're getting into the loop that is ActionCable. Simply put, this is a two way street between client and server. It goes something like this: 1. The client loads the url, creating a channel set to `App.foo` 2. `App.foo.connected()` is automatically called. This is where we can do necessary things like get resource ids or start/stop streams. Streams are subscriptions to a certain redis channel e.g. `articles:1:comments`. 3. `App.foo` would correspond to a server side channel i/e `channels/foo_channel.rb` and could call methods and pass arguments using `@perform` example: `@perform 'speak', message: "hello world"` => `FooChannel.speak message: "hello world"`. 4. Although, ActionCable is a two way street, we don't have to always use both directions. This means that the client can send back information and not expect a response, or as shown in our example, once a connection is established, the server can send messages prompted by other parts of the application such as a new database entry. It does this by using `ActionCable.server.broadcast()` e.g. `ActionCable.server.broadcast "some_channel", message: "something happened"`. #### Server/Channels Our server `CommentsChannel` will do two primary things; control the current stream and stop all streams if comments are not applicable on the current page. <small>_app/channels/comments_channel.rb_</small> ```ruby class CommentsChannel < ApplicationCable::Channel def follow(params) stop_all_streams stream_from "article:#{params['article_id'].to_i}:comments" end def unfollow stop_all_streams end end ``` #### Client/Channels The client `CommentsChannel` is there to initiate the subscription and to alert the server of any changes in page status i/e the client navigates away from the current article. <small>_app/assets/javascripts/channels/comments.coffee_</small> ```coffee App.comments = App.cable.subscriptions.create "CommentsChannel", collection: -> $('#comments') connected: -> setTimeout => @followCurrentArticle() , 1000 disconnected: -> followCurrentArticle: -> articleId = @collection().data('article-id') if articleId @perform 'follow', article_id: articleId else @perform 'unfollow' received: (data) -> @collection().append(data['comment']) ``` #### Assets Head over to <small>_app/assets/javascripts/cable.coffee_</small> and uncomment the two lines at the bottom. ```coffee @App ||= {} App.cable = ActionCable.createConsumer() ``` #### Views We are going to rely on Rails to render partials and handle cacheing of comments. Two important things to note. The form is set to `remote: true`. This lets us use a view to render the new comment from `CommentsController.create` with minimal code and without doing anything special outside of writing _create.js.erb_ <small>_app/views/comments/create.js.erb_</small> ```html $('#new_comment').replaceWith('<%=j render 'comments/new', article: @article %>'); ``` <small>_app/views/comments/_comment.html.erb_</small> ```html <% cache comment do %> <div class="comment"> <p> <%= comment.text %> </p> </div> <% end %> ``` <small>_app/views/comments/_comments.html.erb_</small> ```html <%= render 'comments/new', article: article %> <section id="comments" data-article-id="<%= @article.id %>"> <%= render @article.comments %> </section> ``` <small>_app/views/comments/_new.html.erb_</small> ```html <%= form_for [ @article, Comment.new ], remote: true do |f| %> <%= f.text_area :text, size: '100x20' %><br> <%= f.submit 'Add comment' %> <% end %> ``` ## Conclusion ActionCable is a really useful tool. WebSockets have grown in popularity and use cases. It makes sense for Rails to establish a convention for generating and using them within the framework. #### Acknowledgements This article is based on the [video tutorial](https://www.youtube.com/watch?v=n0WUjGkDFS0) by DHH, his [actioncable-examples](https://github.com/rails/actioncable-examples), and the [public README](https://github.com/rails/rails/tree/master/actioncable) available with ActionCable.