According to DHHs Rails5 ActionCable chat example I'm going to create a further example with conversations and many messages in there:
rails g model conversation
class Conversation < ApplicationRecord
has_many :messages
end
rails g model message content:text conversation:references
view/conversations/show.html.erb
<h1>Conversation</h1>
<div id="messages">
<%= render @messages %>
</div>
<form>
<label>Say something:</label><br>
<input type="text" data-behavior="conversation_speaker">
</form>
view/messages/_message.html.erb
<div class="message">
<p><%= message.content %></p>
</div>
My questions is how to write a channel logic that every message related to its conversation gets written to the database:
First I recorded a conversation and a message on the console
Conversation.create
Message.create(conversation_id: '1', content: 'hello')
afterwards I created a Job
rails g job MessageBroadcast
class MessageBroadcastJob < ApplicationJob
queue_as :default
render_message(message)
def perform(data)
message = Message.create! content: data
ActionCable.server.broadcast 'conversation_channel', message: render_message(message)
end
private
def render_message(message)
ApplicationController.renderer.render(partial: 'messages/message',
locals: { message: message })
end
end
and a channel
rails g channel conversation speak
assets/javascripts/channels/conversation.coffee
App.conversation = App.cable.subscriptions.create "ConversationChannel",
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) ->
# Called when there's incoming data on the websocket for this channel
$('#messages').append data['message']
speak: ->
@perform 'speak'
$(document).on 'keypress', '[data-behavior~=conversation_speaker]', (event) ->
if event.keyCode is 13 # return = send
App.conversation.speak event.target.value
event.target.value = ""
event.preventDefault()
If I write:
channels/conversation_channel.rb
class ConversationChannel < ApplicationCable::Channel
def subscribed
stream_from "conversation_channel"
end
def speak
Message.create! content: data['message']
end
end
I get
Started GET "/cable/" [WebSocket] for ::1 at 2016-04-22 00:22:13 +0200
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: keep-a
live, Upgrade, HTTP_UPGRADE: websocket)
Started GET "/cable" for ::1 at 2016-04-22 00:22:13 +0200
Started GET "/cable/" [WebSocket] for ::1 at 2016-04-22 00:22:13 +0200
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: keep-a
live, Upgrade, HTTP_UPGRADE: websocket)
ConversationChannel is transmitting the subscription confirmation
ConversationChannel is streaming from conversation_channel
ConversationChannel is transmitting the subscription confirmation
ConversationChannel is streaming from conversation_channel
looks okay but if I enter some text in the textfield and hit return I get:
Could not execute command from {"command"=>"message",
"identifier"=>"{\"channel\":\"ConversationChannel\"}",
"data"=>"{\"action\":\"speak\"}"})
[NameError - undefined local variable or method `data' for #<ConversationChannel:0x00000008ad3100>]:
C:/Sites/ActionCable/app/channels/conversation_channel.rb:13:
in `speak' | C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/actioncable-5.0.0.beta3/lib/action_cable/channel/base.rb:253:
in `public_send' | C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/actioncable-5.0.0.beta3/lib/action_cable/channel/base.rb:253:
in `dispatch_action' | C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/actioncable-5.0.0.beta3/lib/action_cable/channel/base.rb:163:
in `perform_action' | C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/actioncable-5.0.0.beta3/lib/action_cable/connection/subscriptions.rb:49:
in `perform_action'
Any ideas?
Going from the client side to the server side, you must (first of all) make your speak
function accepts a message
parameter AND send that message to the server as a JSON object.
speak: (message) ->
@perform 'speak', message: message
Second, you have to define the parameters received by speak
function at channels/conversation_channel.rb. Thus you must redefine it as:
def speak(data)
Message.create! content: data['message']
end
Now your speak
method is receiving a data
parameter, which is a JSON with a message
property which contains the message sent to the server. It's being recorded to the database, but there is no answer to those subscribed to the channel.
Thus we must notify them redefining the method above as:
def speak(data)
Message.create! content: data['message']
ActionCable.server.broadcast 'conversation_channel', message: render_message(message)
end
private
def render_message(message)
ApplicationController.renderer.render(partial: 'messages/message',
locals: { message: message })
end
Now it's supposed to work. What you'll do in background is up to you ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With