I have created a WebSocket messaging feature using Rails 5 ActionCable. It works great when I use a DIV as the container. But when I changed it to use a UL and LI instead; it stopped working. I cant seem to debug this; but it looks like the received:
event is not being trigger on the client. The send_message works...and is saved in the database, its just new messages do not appear on the page.
TL;DR; bit at the bottom of the question.
UPDATE
I have managed to diagnose the problem, it works again when i remove the lines (from the _message.html.erb partial) that check against the current_user message.user == current_user
- but why?
Help would be greatly appreciated. Here is (I think) all the relevant code:
/app/assets/javascripts/channels/conversations.coffee
jQuery(document).on 'turbolinks:load', ->
messages = $('#messages')
if $('#messages').length > 0
messages_to_bottom = -> messages.scrollTop(messages.prop("scrollHeight"))
messages_to_bottom()
App.global_chat = App.cable.subscriptions.create {
channel: "ConversationsChannel"
conversation_id: messages.data('conversation-id')
},
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
messages.append data['message']
messages_to_bottom()
send_message: (message, conversation_id) ->
@perform 'send_message', message: message, conversation_id: conversation_id
$('#new_message').submit (e) ->
$this = $(this)
textarea = $this.find('#message_body')
if $.trim(textarea.val()).length > 1
App.global_chat.send_message textarea.val(), messages.data('conversation-id')
textarea.val('')
e.preventDefault()
return false
/app/channels/conversations_channel.rb
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class ConversationsChannel < ApplicationCable::Channel
def subscribed
stream_from "conversations_#{params['conversation_id']}_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def send_message(data)
current_user.messages.create!(body: data['message'], conversation_id: data['conversation_id'], user: current_user)
end
end
/app/jobs/message_broadcast_job.rb
class MessageBroadcastJob < ApplicationJob
queue_as :default
def perform(message)
ActionCable.server.broadcast "conversations_#{message.conversation.id}_channel",
message: render_message(message)
end
private
def render_message(message)
MessagesController.render partial: 'messages/message', locals: {message: message}
end
end
/app/views/conversations/show.html.erb
...
<div id="messages" data-conversation-id="<%= @conversation.id %>">
<ul class="list-unstyled media-block messages-list" data-conversation-id="<%= @conversation.id %>">
<%= render @conversation.messages %>
</ul>
</div>
...
<div class="row">
<%= form_for @message, url: '#' do |f| %>
<%= hidden_field_tag 'conversation_id', @conversation.id %>
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, class: 'form-control' %>
<small class="text-muted">From 2 to 1000 characters</small>
</div>
<%= f.submit "Post", class: 'btn btn-primary btn-lg' %>
<% end %>
</div>
...
/app/views/messages/_message.html.erb
<% if message.body %>
<li class="mar-btm message" data-id="<%= message.id %>">
<div class="<%= message.user == current_user ? 'media-right' : 'media-left' %>">
<img src="<%= message.user.avatar.url %>" class="img-circle img-sm" alt="Profile Picture">
</div>
<div class="media-body pad-hor
<% if message.user == current_user %>speech-right
<% end %>">
<div class="speech">
<h4><%= message.user.full_name %></h4>
<%= message.body %>
<p class="speech-time">
<i class="demo-pli-clock icon-fw"></i>
<span class="timestamp" data-value="<%= message.created_at %>"><%= time_ago_in_words message.created_at %>
ago</span>
</p>
</div>
</div>
</li>
<% end %>
TL;DR;
As I said, previously i was using a container div: <div id="messages" data-conversation-id="<%= @conversation.id %>">
and each message was in a child DIV instead of a LI element, and it was working. I cant figure out what I broke (or why it matters).
When I post a new message it is send and saved to the database, but it doesn't get sent to the browser. The logs look like this though:
[ActionCable] [an...ey@gmail.com] ConversationsChannel#send_message({"message"=>"23r23r2", "conversation_id"=>5})
[ActionCable] [an...ey@gmail.com] (0.1ms) BEGIN
[ActionCable] [an...ey@gmail.com] SQL (1.1ms) INSERT INTO "messages" ("conversation_id", "user_id", "body", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["conversation_id", 5], ["user_id", 7], ["body", "23r23r2"], ["created_at", 2017-03-01 01:16:25 UTC], ["updated_at", 2017-03-01 01:16:25 UTC]]
[ActionCable] [an...ey@gmail.com] (0.9ms) COMMIT
[ActionCable] [an...ey@gmail.com] [ActiveJob] Enqueued MessageBroadcastJob (Job ID: a464e290-f23f-4cd4-a355-e364a43782e9) to Async(default) with arguments: #<GlobalID:0x007ff678cf74e0 @uri=#<URI::GID gid://itpo/Message/172>>
Message Load (0.6ms) SELECT "messages".* FROM "messages" WHERE "messages"."id" = $1 LIMIT $2 [["id", 172], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [a464e290-f23f-4cd4-a355-e364a43782e9] Performing MessageBroadcastJob from Async(default) with arguments: #<GlobalID:0x007ff679dcc400 @uri=#<URI::GID gid://itpo/Message/172>>
[ActiveJob] [MessageBroadcastJob] [a464e290-f23f-4cd4-a355-e364a43782e9] Conversation Load (0.3ms) SELECT "conversations".* FROM "conversations" WHERE "conversations"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [a464e290-f23f-4cd4-a355-e364a43782e9] User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 LIMIT $2 [["id", 7], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [a464e290-f23f-4cd4-a355-e364a43782e9] Rendered messages/_message.html.erb (16.6ms)
[ActiveJob] [MessageBroadcastJob] [a464e290-f23f-4cd4-a355-e364a43782e9] Performed MessageBroadcastJob from Async(default) in 28.37ms
And since the last line of the log says Performed MessageBroadcastJob from Async(default)
it leads me to believe that the problem doesn't exist on the server side, and that perhaps my coffee script is broken somehow.
Maybe the way you insert new elements into a UL is different to just appending HTML onto a DIV?
Please help.