问题
I have an ActionCable method that subscribes the user. If a new convo is started, I want to subscribe the user to the new channel as well. I can't figure out the proper syntax for calling a channel method in a controller.
UPDATE: The issue is that the messages are appended to the chatbox when sent, but when the first message, is sent, the websocket connection is not established yet, and therefore it looks to the user as if the message was not sent (because it's not being appended).
channel/msgs_channel.rb
class MsgsChannel < ApplicationCable::Channel
#This function subscribes the user to all existing convos
def subscribed
@convos = Convo.where("sender_id = ? OR recipient_id = ?", current_user, current_user)
@convos.each do |convo|
stream_from "msg_channel_#{convo.id}"
end
end
#This is a new function I wrote to subscribe the user to a new convo that is started during their session.
def subscribe(convo_id)
stream_from "msg_channel_#{convo_id}"
end
end
In my convos controller, create method, I have tried several things:
convos_controller.rb
def create
@convo = Convo.create!({sender_id: @sender_id, recipient_id: @recipient_id})
ActionCable.server.subscribe(@convo.id)
end
ActionCable.subscribe(@convo.id)
error:
NoMethodError (undefined method
subscribe' for ActionCable:Module)`
ActionCable.msgs.subscribe(@convo.id)
error:
NoMethodError (undefined method
msgs' for ActionCable:Module):`
App.msgs.subscribe(@convo.id)
error:NameError (uninitialized constant ConvosController::App):
MsgsChannel.subscribe(@convo.id)
error:NoMethodError (undefined method
subscribe' for MsgsChannel:Class`
ActionCable.server.subscribe(@convo.id)
error:NoMethodError (undefined method
subscribe' for #):`
回答1:
Retrieve a Specific Channel Instance from a Controller
Channel
instances are associated with the user's Connection
. The following will work to get a hash of the Channel
s for a Connection
:
conn = ActionCable.server.connections.first { |c| c.current_user == user_id }
# subs is a hash where the keys are json identifiers and the values are Channels
subs = conn.subscriptions.instance_variable_get("@subscriptions")
(If Connection#current_user
does not exist then follow the instructions at the top of section 3.1.1 from the rails ActionCable overview to add it.)
You can then get a Channel
based on whatever criteria you want. For instance you could search by type which in your case would be MsgsChannel
. The json key in subs
could also be used but might be more complicated.
Once you have this instance you can then call whatever method on it you want to (in your case MsgsChannel#subscribe
).
Concerns About This Approach
A Channel
just encapsulates server side work that will be kicked off in response to messages from the client side of the app. Having the server call a Channel
method effectively amounts to having the server pretend that a consumer sent a message that it didn't send.
I would recommend instead of doing this putting whatever logic that you want to use both from a Controller
and a Channel
into a method in a shared location. The Controller
and Channel
can then directly call this method as needed.
That being said there is one painful caveat to this which is that you need the Channel
to call stream_from
. Unfortunately managing streams is specific to Channel
s even though fundamentally it is just updating pubsub information for the Server
.
I think that stream management should really just require the appropriate connection and the Server
with its pubsub. If that were the case you wouldn't need to worry about calling Channel
methods from the server side of an application.
You should be able to effectively run stream_for
more directly if you get a Channel
, any Channel
(you could use subs
from the top of this answer as well) and call stream_for
on it
conn = ActionCable.server.connections.first { |c| c.current_user == user_id }
MsgsChannel.new(conn, "made up id").stream_from "your_stream_name"
I haven't tried this myself but it looks like it should work.
回答2:
So you should not be subscribing the user to the channel in the controller on create. It should be based on when they visit the page. You should change where users are connected by adding in a js/coffe file to do this for you based on who is connected. A good example/tutorial for this when I was learn was this video here.
What the video leaves out is how to connect to an individual conversation. So I struggled with it and found a way to grab the conversation id from the url, probably not the best way but it works. Hope this helps
conversation_id = parseInt(document.URL.match(new RegExp("conversations/"+ "(.*)"))[1]) # gets the conversation id from the url of the page
App.conversation = App.cable.subscriptions.create { channel: "ConversationChannel", conversation_id: 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']
speak: (message) ->
@perform 'speak', message: message
来源:https://stackoverflow.com/questions/42679739/how-can-i-call-a-channel-method-in-a-rails-controller