How can I call a channel method in a rails controller?

谁都会走 提交于 2021-02-07 11:59:14

问题


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 methodsubscribe' for ActionCable:Module)`


ActionCable.msgs.subscribe(@convo.id)

error: NoMethodError (undefined methodmsgs' for ActionCable:Module):`


  App.msgs.subscribe(@convo.id)

error:NameError (uninitialized constant ConvosController::App):


MsgsChannel.subscribe(@convo.id)

error:NoMethodError (undefined methodsubscribe' for MsgsChannel:Class`


ActionCable.server.subscribe(@convo.id)

error:NoMethodError (undefined methodsubscribe' 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 Channels 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 Channels 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!