3
votes

Remote iOS client successfully connects to me, send subscribe command (it works fine), but on "unsubscribe" command I get next error:

Unsubscribing from channel: {"channel":"Assessor::StationChannel", "station_id": 1}
Could not execute command from {"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"Assessor::StationChannel\", \"station_id\": 1}"}) [NoMethodError - undefined method `unsubscribe_from_channel' for nil:NilClass]: /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/subscriptions.rb:44:in `remove_subscription' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/subscriptions.rb:40:in `remove' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/subscriptions.rb:16:in `execute_command' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/base.rb:88:in `dispatch_websocket_message' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/server/worker.rb:58:in `block in invoke' 

Subscribe message format:

{"command": "subscribe", "identifier": "{\"channel\":\"Assessor::StationChannel\", \"station_id\": 1}"} 

Unsubscribe message format:

{"command": "unsubscribe", "identifier": "{\"channel\":\"Assessor::StationChannel\", \"station_id\": 1}"} 

I cannot reproduce this problem on localhost, so maybe somebody can help me?

4
Can you format your error message and commands to eliminate scrolling.zhon
Did you ever figure out what the proper way to cleanup and disconnect a connection? In context of a mobile client/iosRyan Romanchuk

4 Answers

3
votes

I saw a similar error. I was trying to unsubscribe via the client (JS). I eventually figured out it was because the javascript to .remove(subscription) takes the subscription and not the subscription identifier.

This is how I got it to work without error. Perhaps it will help you find out why you are getting the error from the server side.

subscription = App.cable.subscriptions.subscriptions[0]
App.cable.subscriptions.remove(subscription);

(Note, I'm just pulling the first subscription from the array, TODO: Search for the subscription I want to remove)

Here is the bug I was seeing and how I eventually found the source code/answer. I ran these from the webclient console:

App.cable.subscriptions.create({channel: "RoomChannel", room_id: 2})    

That line works and I get a "... is transmitting the subscription confirmation" on stdout for rails s

App.cable.subscriptions.remove({channel: "RoomChannel", room_id: 2})

That line blows up, yells at my kids, and insults my wife which looks like:

[NoMethodError - undefined method `unsubscribe_from_channel' for nil:NilClass]: /usr/local/lib64/ruby/gems/2.3.0/gems/actioncable-5.0.0.1/lib/action_cable/connection/subscriptions.rb:44:in `remove_subscription'

I also noted the following line before the crash.

Unsubscribing from channel: 

The code to produce that is: logger.info "Unsubscribing from channel: #{data['identifier']}". Which means it wasn't finding the data['identifier']. So I started debugging and I see that line 88 of base.rb in actioncable only gets {"command":"unsubscribe"} and not something like {"command":"unsubscribe", "identifier":" (channel name here)}

Which brought me to action_cable.js. (I would have started here, but I hate JS.). Here was my problem: function(subscription). I was sending the identifier and not the subscription object.

Subscriptions.prototype.remove = function(subscription) {
  this.forget(subscription);
  if (!this.findAll(subscription.identifier).length) {
    this.sendCommand(subscription, "unsubscribe");
  }
  return subscription;
};
1
votes

App.cable.subscriptions.create({channel: "RoomChannel", room_id: 2}) returns a subscription object you have to pass that into the remove function

var subscription = App.cable.subscriptions.create({channel: "RoomChannel", room_id: 2});

Then later

App.cable.subscriptions.remove(subscription);

0
votes

After several tries I eventually figured it out. Late reply but this worked for me hope it does for you.

App["identifier"].disconnected() #e.g  App["chat_4"].disconnect()
=> YourChannel stopped streaming from chat_4 #rails terminal

Above is the line to stop the streaming and below is how you subscribe to the channel

App['chat' + id] = App.cable.subscriptions.create({channel: 
     'YourChannel', chat_id: id}, {

     disconnected: function () {
       App.cable.subscriptions.remove(this)
     },
 )}
0
votes

I know this is an old question, but I suffered from the same issue. None of the answers above worked for me and in fact, at least in Rails 5.0.1, they are incorrect. It is a frontend issue all right, but here's why it doesn't matter whether you call App.yourChannelName.unsubscribe() or App.yourChannelName.remove(App.yourChannelName)

Eg. if you have something like this (example code is in coffeescript, so ignore the lack of vars and other stuff from vanilla JS):

App.yourChannel = App.cable.subscriptions.create({channel: 'YourChannel', id: id})
...
// do stuff, execute callbacks, whatnot
...
// try to execute unsubscribe
App.yourChannel.unsubscribe()

The .unsubscribe() is a method on Subscription prototype which only does return this.consumer.subscriptions.remove(this)

The this.consumer.subscriptions returns the instance of your subscription, in example above, it would be App.yourChannel and calls .remove method with the instance of Subscription - ie. with App.yourChannel

So App.yourChannel.unsubscribe() is the same as calling App.cable.subscriptions.remove(App.yourChannel) (or whichever variable you choose to store the instance of Subscription in.

I have also been seeing the same error as OP, except that in my case, it was caused by App.yourChannel.unsubscribe() being called two times - the first time it was called immediately after I received specific data via the channel and the second time was due to a custom cleanup being run in a specific scenario before the App.yourChannel was re-subscribed.

So if you see a similar error, I suggest you look at the server logs. You'll probably see something like

Registered connection (some-id) <-- initial subscription
YourChannel is transmitting the subscription confirmation
...
// other stuff while user is subscribed to the channel
...
Unsubscribing from channel: {"channel":"YourChannel","id":"some-id"} <-- initial unsubscribe call
YourChannel stopped streaming from your_channel_some-id
// some other requests potentially, perhaps some DB queries
...
// there are no requests to subscribe to the channel with the some-id, eg. you won't see this
// Registered connection (some-id)
// YourChannel is transmitting the subscription confirmation

Unsubscribing from channel: {"channel":"YourChannel","id":"some-id"} <-- duplicated unsubscribe call
Could not execute command from {"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"YourChannel\",\"id\":\"some-id\"}"}) [NoMethodError - undefined method `unsubscribe_from_channel' for nil:NilClass]:

Basically, the user subscribes, unsubscribes, then tries to unsubscribe again (even though they are not subscribed to that channel anymore)