Group Chat with ActionCable: Part 4 Discussion
As for message relay jobs matter for this particular subject of this ActionCable example, do you recommend putting down for its current_user's id or name in these kind of scenarios when it comes to logging down for the Message job queue and delivering it to Sidekiq?
Hi Chris! Thank you for your lessons!=)
<% if chatroom.chatroom_users.find_by_user_id(current_user) %>
<%= form_for [@chatroom, Message.new], remote: true do |f| %>
<%= f.text_area :body, rows: 1, class: 'form-control', autofocus: true %>
<% end %>
<% else %>
<%= link_to "Join", chatroom_chatroom_users_path(@chatroom), method: :post, remote: true %>
<% end %>
Thanks
Hi, Chris! Great tutorial, thank you very much!
I'm trying to follow it to create a Chat Api. Is it possible? I would receive the messages through an Android app, pass it to my Api and then send the new messages to Android again as json. Could you give me any clues about what do I have to change so far? (I am already not implementing all the views).
Hey Diane,
Two different approaches you could use:
1. Keep it simple and go with a Turbolinks-Android hybrid app where your chat is via the webview on the app. This is simpler and easier to setup.
2. You could do a native Android connection directly to the websocket. For this you'd basically use a standard websocket library for Android and then make the small tweaks necessary to setup the consumer just like the Javascript does. You'd basically be porting that JS into Android code. Not entirely sure on all the details or if someone else has done this already, but that's the rough outline for a native approach.
Chris, how can you have you redis working without configuring and running the server in dev env? For me it raises "`rescue in establish_connection': Error connecting to Redis on localhost:6379 (Errno::ECONNREFUSED) (Redis::CannotConnectError)`" which is logical since the redis server is down.
I have my redis server always running on my laptop on the default port so it is always ready to go. Homebrew comes with instructions on how to start it when you login. If you follow those it will set it up the same way I have.
For those getting error about "Request origin not allowed" and "Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)"
If you are running rails server with a specific IP address please add "config.action_cable.allowed_request_origins" to your 'development.rb' file and point it to the IP address you are using
Great point Rick! That's probably a common problem for anyone trying this. Thanks for sharing that. π
I am surprised no one is getting the error messages I am getting while doing this part.
When I add remote: true i get " ActionController::InvalidAuthenticityToken
in MessagesController#create" error. (to solve it i must add authenticity_token: true).
When I remove the redirect_to @chatroom i get another error:
MessagesController#create is missing a template for this request format and variant.
request.formats: ["text/html"]
request.variant: [] even if I created a messages/create.js.erb file in my views.
I am on a mac using ruby 2.3.0 with rails 5.0.1 via rvm. Could this be the evil source of my problems?
You might continue on a bit, I ended up refactoring this to use ActionCable to send messages over instead of an AJAX form because you might as well be using it two ways.
I'm not sure what's up with your authentication token, but it does sound like your server side is looking for an HTML response, not a JS response, even though if it was an AJAX request, it should. Sounds like something's not quite right with that somehow. I'm not entirely sure off the top of my head though.
Same error for me. Will just press on and hope things work when we addd ActionCable.
Had the same error, running on Rails 5.2, needed to replace form_for with form_with for the message form
<%= form_with model: [@chatroom, Message.new], :html => { :id => 'new_message'} do |f| %>
Also Rails UJS in 5.2 doesn't intercept a submit triggered by javascript, so I needed to use the Rails.fire() method to submit the form in my keypress handler
$(document).on "turbolinks:load", ->
$("#new_message").on "keypress", (e) ->
if e && e.keyCode == 13
e.preventDefault()
Rails.fire(this, 'submit')
Hey Chris, I 've got an error when I click on the button "join" => "The action 'show' could not be found for ChatroomUsersController";
But I did exactly what you did in the last videos... I have the same link_to than yours; An idea what's going on? thanks a lot
Hey Brice, you might check your code because the Join button I believe needs to submit a POST request and your error is looking for the show action which implies it sent a GET request instead. The Join button should create a POST request in order to create the record to set you as a user inside the channel, so it should take you to the ChatroomUsersController's create with that POST. Make sure that link has a "method: :post" option on it?
Hello Chris, just a quick question regarding the best practice with jobs: I learned in the past that passing only the id parameter to the job with sidekiq or else, then find the instance inside the job is the best practice. Since you pass the complete instance, I'm wondering if Rails do it for us automagically with ActiveJob? Thank you
Ideally it's better to pass in only the ID. If the record was deleted before the job runs, you can safely quit if you can't find the record assuming it was deleted.
Now I believe that if you pass in an ActiveRecord job to ActiveJob, it will transform it to a GlobalID (https://github.com/rails/gl... / http://edgeguides.rubyonrai... which is a representation of the ID and class name so that you don't have to specifically pass the ID.
If you use Sidekiq, etc directly without ActiveJob it is going to try using that object which is why they recommend using the ID specifically.
ActiveJob + Global ID should look up the record once the job starts (you should see this in the logs) which will make running those jobs safe.
Hey Chris, i've been following the videos for the group chat and so far they're awesome (as are all the videos of yours that i've watched).
I've run into a bit of a stale point though, in your video around 15:31 we're logging the message data to the console, but seems it's not working for me, nothing is printing to the chrome console.
my rails server output is showing:
[ActionCable] [User 2] ChatroomsChannel is streaming from chatrooms:2
Started POST "/chatrooms/1/messages" for ::1 at 2016-10-16 14:45:53 +0100
Processing by MessagesController#create as JS
Parameters: {"utf8"=>"β", "message"=>{"body"=>"show me"}, "chatroom_id"=>"1"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 2], ["LIMIT", 1]]
Chatroom Load (0.1ms) SELECT "chatrooms".* FROM "chatrooms" WHERE "chatrooms"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.0ms) begin transaction
SQL (0.4ms) INSERT INTO "messages" ("chatroom_id", "user_id", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["chatroom_id", 1], ["user_id", 2], ["body", "show me"], ["created_at", 2016-10-16 13:45:53 UTC], ["updated_at", 2016-10-16 13:45:53 UTC]]
(7.4ms) commit transaction
[ActiveJob] Enqueued MessageRelayJob (Job ID: a83d7365-8e56-4ea6-a03f-db172f5b1c62) to Async(default) with arguments: #<globalid:0x007ff34bd10358 @uri="#<URI::GID" gid:="" slack-clone-rails5="" message="" 16="">>
Message Load (0.3ms) SELECT "messages".* FROM "messages" WHERE "messages"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Rendering messages/create.js.erb
[ActiveJob] [MessageRelayJob] [a83d7365-8e56-4ea6-a03f-db172f5b1c62] Performing MessageRelayJob from Async(default) with arguments: #<globalid:0x007ff34c393810 @uri="#<URI::GID" gid:="" slack-clone-rails5="" message="" 16="">>
Rendered messages/create.js.erb (0.6ms)
Completed 200 OK in 113ms (Views: 26.6ms | ActiveRecord: 8.0ms)
[ActiveJob] [MessageRelayJob] [a83d7365-8e56-4ea6-a03f-db172f5b1c62] Chatroom Load (0.1ms) SELECT "chatrooms".* FROM "chatrooms" WHERE "chatrooms"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
[ActiveJob] [MessageRelayJob] [a83d7365-8e56-4ea6-a03f-db172f5b1c62] Performed MessageRelayJob from Async(default) in 31.33ms
************
Which seems all good, just wondered if you had any input why this wouldn't be showing the console.log data
App.chatrooms = App.cable.subscriptions.create "ChatroomsChannel",
connected: ->
disconnected: ->
received: (data) ->
console.log data
message_relay_job:
class MessageRelayJob < ApplicationJob
queue_as :default
def perform(message)
ActionCable.server.broadcast "chatrooms:#{message.chatroom.id}", {
message: MessageController.render(message),
chatroom_id: message.chatroom.id
}
end
end
chatrooms_channel:
class ChatroomsChannel < ApplicationCable::Channel
def subscribed
current_user.chatrooms.each do |chatroom|
stream_from "chatrooms:#{chatroom.id}"
end
end
def unsubscribed
stop_all_streams
end
end
not sure what else you would need to check? if you need my repo it's here
https://github.com/ggomersa...
Hope you can help :)
Hard to say off the top of my head. You might just need to put in some more debugging lines to make sure that everything is connecting correctly. Make sure that 1) the JS connects to the websocket by watching the Chrome network logs 2) Make sure that your server side background job is executing by printing out in your logs 3) Make sure your user is connected to the channel so their websocket streams from it correctly.
Probably something small isn't connected right and it should be an easy fix once you figure out what it is. Also here's the link to the final app code for this that might be of help: https://github.com/gorails-...
Hi Chris,
At 5:59, in the connection.rb file you add:
protected
def find_verified_user
if current_user = env['warden'].user
current_user
else
reject_unauthorized_connection
end
end
If I'm not using devise, is "env['warden'].user" just the devise way of authenticate_user ? Is this just to make sure the current_user is the logged_in user? Want to make sure I'm implementing correctly.
Thanks!
Exactly, you can just replace that with however you find the current user normally. π
Just offering a solution here for some error messages I kept getting. The server error I got was:
Started GET "/cable" for ::1 at 2017-02-09 16:35:06 -0800
ActionController::RoutingError (No route matches [GET] "/cable"):
I added to my routes.rb :
Rails.application.routes.draw do
mount ActionCable.server, at: '/cable'
...
...
end
Not sure why in this repo example action cable works without it, but in my app once I added this everything worked. I'm using Rails '5.0.1' for reference.
Hmm weird, they changed that to be the default URL for Rails in 5 I believe so it shouldn't be necessary. The project I'm using in 5.0.1 doesn't have it and it finds ActionCable just fine. Not sure what's up there.
Hey Chris, Can you elaborate on the difference between what you use chatrooms_channel.rb and chatrooms.coffee for?
My interpretation so far: Chatroom_channel.rb is saying "These are the things we want to listen to" and chatrooms.coffee is where we say "Ok, those things we're listening to, if some new things happen, do these actions".
Hello Chris,
My problem is that my channel is broadcasting but on the client side(the coffee script) didn't received anything, I did add an alert if it was connected and it is connected though, any idea why this happening?
great tuto :)
i now want to manage rights so that the author of a comment can edit it. any ideas?
Hey @excid3:disqus awesome series. My messages get saved to the db and in the response I can also see that turbolinks. But the message does not get added in the front end. I have to refresh the page in order to see it on the front end side. Any thoughts?
In your show.html.erb, change the form_for line to this:
<%= form_with model: [@chatroom, Message.new], :html => { :id => 'chatroom_form' } do |f| %>
In your create.js.erb change the reset line to this:
$("#chatroom_form")[0].reset()
And if you are still doing the area thing (I didn't like that style personally) you need to change the keypress line to this:
$("#message_body").on "keypress", (e) ->
I've figure out the problem. a trick.
after use form_with, you must change the code in this video.
my code in chatrooms.coffee:
document.addEventListener 'turbolinks:load', ->
document.getElementById("new_message").addEventListener 'keypress', (e) ->
if e && e.keyCode == 13
e.preventDefault()
url = this.action
data = new FormData(this)
Rails.ajax({
type: "POST"
url: url
data: data
dataType: "json"
})
Do you have a repo on github with a working Rails 5.2 version, tried these updates and it is still trying to redirect.
Hi Chris,
Question: Two errors that I'm hoping you can assist with:
- https://gyazo.com/19aaab916790ec3c1a41476cc621398b It says the redis gem is not loaded. But it is. I found some posts on StackOverflow that says the redis gem 4.0.2 doesn't work with ActionCable, so you needed to use 3.3. But I installed 3.3 and still no success. Is there perhaps some type of additional setup? I did make sure to change the cable.yml as follows:
development:
adapter: redis
url: redis://localhost:6379/1
test:
adapter: async
production:
adapter: redis
url: redis://localhost:6379/1
Anything obvious that I am missing, or forgetting to do?
- This may be relate to the above question. In console, when I type a message, it disappears properly. And posts when I refresh the page. But it's not showing up in the other console as an object, as the video suggests it should. Thoughts?
Thanks very much!
-Monroe
Update: Chris helped me out. Here's the thing that was covered in another video, but not made clear in this video: installing the redis gem alone is not enough: YOU MUST ALSO INSTALL REDIS TO YOUR COMPUTER AND LAUNCH IT. It's a server, just like postgresql. And it needs to be running. So if you have the same problems I was having, it may be because you didn't have redis actually installed, and it wasn't running.
Another thing I learned: in a model, if you have any associations through another, you must first declare the 'another' on the line above the 'through' association, or you'll get an error.
Thanks Chris!
TIP: One more thing: and if the redis server isn't running, and you try to go to a page where the redis server isn't running, the rails server will perhaps crash. Therefore, it seems best to launch the redis server prior to launching the rails server, so you don't get an error if you end up heading to a page that requires the redis server.
TIP: The coffeescript files ARE indent dependent. With ruby (and I also believe generic javascript), whitespace doesn't matter. However, we learned that with these coffeescript/javascript files, if you have even ONE indent wrong, it may mess everything up. Be CERTAIN that the indentation is correct in the coffeescript files. We had the most annoying issue where the messageRelayJob kept firing multiple times, posting blank messages. The solution? One tiny indent needed to move one space to the left.
Be VERY meticulous with your whitespace in your coffeescript files! :D