Class: ActionCable::Channel::Base

Inherits:
Object
  • Object
show all
Includes:
Broadcasting, Callbacks, Naming, PeriodicTimers, Streams
Defined in:
lib/action_cable/channel/base.rb

Overview

The channel provides the basic structure of grouping behavior into logical units when communicating over the websocket connection. You can think of a channel like a form of controller, but one that's capable of pushing content to the subscriber in addition to simply responding to the subscriber's direct requests.

Channel instances are long-lived. A channel object will be instantiated when the cable consumer becomes a subscriber, and then lives until the consumer disconnects. This may be seconds, minutes, hours, or even days. That means you have to take special care not to do anything silly in a channel that would balloon its memory footprint or whatever. The references are forever, so they won't be released as is normally the case with a controller instance that gets thrown away after every request.

Long-lived channels (and connections) also mean you're responsible for ensuring that the data is fresh. If you hold a reference to a user record, but the name is changed while that reference is held, you may be sending stale data if you don't take precautions to avoid it.

The upside of long-lived channel instances is that you can use instance variables to keep reference to objects that future subscriber requests can interact with. Here's a quick example:

class ChatChannel < ApplicationCable::Channel
  def subscribed
    @room = Chat::Room[params[:room_number]]
  end

  def speak(data)
    @room.speak data, user: current_user
  end
end

The #speak action simply uses the Chat::Room object that was created when the channel was first subscribed to by the consumer when that subscriber wants to say something in the room.

Action processing

Unlike Action Controllers, channels do not follow a REST constraint form for its actions. It's an remote-procedure call model. You can declare any public method on the channel (optionally taking a data argument), and this method is automatically exposed as callable to the client.

Example:

class AppearanceChannel < ApplicationCable::Channel
  def subscribed
    @connection_token = generate_connection_token
  end

  def unsubscribed
    current_user.disappear @connection_token
  end

  def appear(data)
    current_user.appear @connection_token, on: data['appearing_on']
  end

  def away
    current_user.away @connection_token
  end

  private
    def generate_connection_token
      SecureRandom.hex(36)
    end
end

In this example, subscribed/unsubscribed are not callable methods, as they were already declared in ActionCable::Channel::Base, but #appear/away are. #generate_connection_token is also not callable as its a private method. You'll see that appear accepts a data parameter, which it then uses as part of its model call. #away does not, it's simply a trigger action.

Also note that in this example, current_user is available because it was marked as an identifying attribute on the connection. All such identifiers will automatically create a delegation method of the same name on the channel instance.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Streams

#stop_all_streams, #stream_for, #stream_from

Constructor Details

#initialize(connection, identifier, params = {}) ⇒ Base

Returns a new instance of Base


116
117
118
119
120
121
122
123
# File 'lib/action_cable/channel/base.rb', line 116

def initialize(connection, identifier, params = {})
  @connection = connection
  @identifier = identifier
  @params     = params

  delegate_connection_identifiers
  subscribe_to_channel
end

Instance Attribute Details

#connectionObject (readonly)

Returns the value of attribute connection


77
78
79
# File 'lib/action_cable/channel/base.rb', line 77

def connection
  @connection
end

#paramsObject (readonly)

Returns the value of attribute params


77
78
79
# File 'lib/action_cable/channel/base.rb', line 77

def params
  @params
end

Class Method Details

.action_methodsObject

A list of method names that should be considered actions. This includes all public instance methods on a channel, less any internal methods (defined on Base), adding back in any methods that are internal, but still exist on the class itself.

Returns

  • Set - A set of all methods that should be considered actions.


89
90
91
92
93
94
95
96
97
98
99
# File 'lib/action_cable/channel/base.rb', line 89

def action_methods
  @action_methods ||= begin
    # All public instance methods of this class, including ancestors
    methods = (public_instance_methods(true) -
      # Except for public instance methods of Base and its ancestors
      ActionCable::Channel::Base.public_instance_methods(true) +
      # Be sure to include shadowed public instance methods of this class
      public_instance_methods(false)).uniq.map(&:to_s)
    methods.to_set
  end
end

Instance Method Details

#perform_action(data) ⇒ Object

Extract the action name from the passed data and process it via the channel. The process will ensure that the action requested is a public method on the channel declared by the user (so not one of the callbacks like #subscribed).


128
129
130
131
132
133
134
135
136
# File 'lib/action_cable/channel/base.rb', line 128

def perform_action(data)
  action = extract_action(data)

  if processable_action?(action)
    dispatch_action(action, data)
  else
    logger.error "Unable to process #{action_signature(action, data)}"
  end
end

#unsubscribe_from_channelObject

Called by the cable connection when its cut so the channel has a chance to cleanup with callbacks. This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.


140
141
142
143
# File 'lib/action_cable/channel/base.rb', line 140

def unsubscribe_from_channel
  run_unsubscribe_callbacks
  logger.info "#{self.class.name} unsubscribed"
end