Skip to content

Commit

Permalink
Merge branch 'main' into allow-dynamic-processor-options
Browse files Browse the repository at this point in the history
  • Loading branch information
xjunior committed Feb 15, 2024
2 parents 048e989 + ed76d0f commit 7666185
Show file tree
Hide file tree
Showing 164 changed files with 3,537 additions and 1,571 deletions.
File renamed without changes.
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// For format details, see https://aka.ms/devcontainer.json.
{
"name": "Rails project development",
"dockerComposeFile": "docker-compose.yml",
"dockerComposeFile": "compose.yaml",
"service": "rails",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/dist/
/doc/
/guides/output/
/preview/
Brewfile.lock.json
debug.log*
node_modules/
Expand Down
15 changes: 15 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require "net/http"
$:.unshift __dir__
require "tasks/release"
require "railties/lib/rails/api/task"
require "tools/preview_docs"

desc "Build gem files for all projects"
task build: "all:build"
Expand Down Expand Up @@ -50,6 +51,20 @@ else
Rails::API::StableTask.new("rdoc")
end

desc "Generate documentation for previewing"
task :preview_docs do
FileUtils.mkdir_p("preview")
PreviewDocs.new.render("preview")

require "guides/rails_guides"
Rake::Task[:rdoc].invoke

FileUtils.cp_r("doc/rdoc", "preview/api")
FileUtils.cp_r("guides/output", "preview/guides")

system("tar -czf preview.tar.gz preview")
end

desc "Bump all versions to match RAILS_VERSION"
task update_versions: "all:update_versions"

Expand Down
184 changes: 98 additions & 86 deletions actioncable/lib/action_cable/channel/base.rb
Original file line number Diff line number Diff line change
@@ -1,102 +1,112 @@
# frozen_string_literal: true

# :markup: markdown

require "set"
require "active_support/rescuable"
require "active_support/parameter_filter"

module ActionCable
module Channel
# = Action Cable \Channel \Base
# # Action Cable Channel Base
#
# 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.
# 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.
# 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.
# 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:
# 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
# class ChatChannel < ApplicationCable::Channel
# def subscribed
# @room = Chat::Room[params[:room_number]]
# end
#
# def speak(data)
# @room.speak data, user: current_user
# def speak(data)
# @room.speak data, user: current_user
# end
# 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.
# 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
# ## Action processing
#
# Unlike subclasses of ActionController::Base, channels do not follow a RESTful
# constraint form for their actions. Instead, Action Cable operates through a
# remote-procedure call model. You can declare any public method on the
# channel (optionally taking a <tt>data</tt> argument), and this method is
# automatically exposed as callable to the client.
# 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
# class AppearanceChannel < ApplicationCable::Channel
# def subscribed
# @connection_token = generate_connection_token
# end
#
# def appear(data)
# current_user.appear @connection_token, on: data['appearing_on']
# end
# def unsubscribed
# current_user.disappear @connection_token
# end
#
# def away
# current_user.away @connection_token
# end
# def appear(data)
# current_user.appear @connection_token, on: data['appearing_on']
# end
#
# private
# def generate_connection_token
# SecureRandom.hex(36)
# def away
# current_user.away @connection_token
# end
# end
#
# In this example, the subscribed and unsubscribed methods are not callable methods, as they
# were already declared in ActionCable::Channel::Base, but <tt>#appear</tt>
# and <tt>#away</tt> are. <tt>#generate_connection_token</tt> is also not
# callable, since it's a private method. You'll see that appear accepts a data
# parameter, which it then uses as part of its model call. <tt>#away</tt>
# does not, since it's simply a trigger action.
# private
# def generate_connection_token
# SecureRandom.hex(36)
# end
# end
#
# In this example, the subscribed and unsubscribed methods are not callable
# methods, as they were already declared in ActionCable::Channel::Base, but
# `#appear` and `#away` are. `#generate_connection_token` is also not callable,
# since it's 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, since it's
# simply a trigger action.
#
# Also note that in this example, <tt>current_user</tt> 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.
# 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.
#
# == Rejecting subscription requests
# ## Rejecting subscription requests
#
# A channel can reject a subscription request in the #subscribed callback by
# invoking the #reject method:
#
# class ChatChannel < ApplicationCable::Channel
# def subscribed
# @room = Chat::Room[params[:room_number]]
# reject unless current_user.can_access?(@room)
# class ChatChannel < ApplicationCable::Channel
# def subscribed
# @room = Chat::Room[params[:room_number]]
# reject unless current_user.can_access?(@room)
# end
# end
# end
#
# In this example, the subscription will be rejected if the
# <tt>current_user</tt> does not have access to the chat room. On the
# client-side, the <tt>Channel#rejected</tt> callback will get invoked when
# the server rejects the subscription request.
# In this example, the subscription will be rejected if the `current_user` does
# not have access to the chat room. On the client-side, the `Channel#rejected`
# callback will get invoked when the server rejects the subscription request.
class Base
include Callbacks
include PeriodicTimers
Expand All @@ -109,14 +119,13 @@ class Base
delegate :logger, to: :connection

class << self
# 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.
# 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
# * <tt>Set</tt> - A set of all methods that should be considered actions.
# #### Returns
# * `Set` - A set of all methods that should be considered actions.
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
Expand All @@ -130,9 +139,9 @@ def action_methods
end

private
# action_methods are cached and there is sometimes need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
# you run action_methods, they will be recalculated.
# action_methods are cached and there is sometimes need to refresh them.
# ::clear_action_methods! allows you to do that, so next time you run
# action_methods, they will be recalculated.
def clear_action_methods! # :doc:
@action_methods = nil
end
Expand Down Expand Up @@ -161,9 +170,9 @@ def initialize(connection, identifier, params = {})
delegate_connection_identifiers
end

# 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).
# 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).
def perform_action(data)
action = extract_action(data)

Expand All @@ -177,8 +186,8 @@ def perform_action(data)
end
end

# This method is called after subscription has been added to the connection
# and confirms or rejects the subscription.
# This method is called after subscription has been added to the connection and
# confirms or rejects the subscription.
def subscribe_to_channel
run_callbacks :subscribe do
subscribed
Expand All @@ -188,29 +197,32 @@ def subscribe_to_channel
ensure_confirmation_sent
end

# Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
# This method is not intended to be called directly by the user. Instead, override the #unsubscribed callback.
# Called by the cable connection when it's cut, so the channel has a chance to
# cleanup with callbacks. This method is not intended to be called directly by
# the user. Instead, override the #unsubscribed callback.
def unsubscribe_from_channel # :nodoc:
run_callbacks :unsubscribe do
unsubscribed
end
end

private
# Called once a consumer has become a subscriber of the channel. Usually the place to set up any streams
# you want this channel to be sending to the subscriber.
# Called once a consumer has become a subscriber of the channel. Usually the
# place to set up any streams you want this channel to be sending to the
# subscriber.
def subscribed # :doc:
# Override in subclasses
end

# Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking
# users as offline or the like.
# Called once a consumer has cut its cable connection. Can be used for cleaning
# up connections or marking users as offline or the like.
def unsubscribed # :doc:
# Override in subclasses
end

# Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
# the proper channel identifier marked as the recipient.
# Transmit a hash of data to the subscriber. The hash will automatically be
# wrapped in a JSON envelope with the proper channel identifier marked as the
# recipient.
def transmit(data, via: nil) # :doc:
logger.debug do
status = "#{self.class.name} transmitting #{data.inspect.truncate(300)}"
Expand Down
12 changes: 7 additions & 5 deletions actioncable/lib/action_cable/channel/broadcasting.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

# :markup: markdown

require "active_support/core_ext/object/to_param"

module ActionCable
Expand All @@ -8,17 +10,17 @@ module Broadcasting
extend ActiveSupport::Concern

module ClassMethods
# Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel.
# Broadcast a hash to a unique broadcasting for this `model` in this channel.
def broadcast_to(model, message)
ActionCable.server.broadcast(broadcasting_for(model), message)
end

# Returns a unique broadcasting identifier for this <tt>model</tt> in this channel:
# Returns a unique broadcasting identifier for this `model` in this channel:
#
# CommentsChannel.broadcasting_for("all") # => "comments:all"
# CommentsChannel.broadcasting_for("all") # => "comments:all"
#
# You can pass any object as a target (e.g. Active Record model), and it
# would be serialized into a string under the hood.
# You can pass any object as a target (e.g. Active Record model), and it would
# be serialized into a string under the hood.
def broadcasting_for(model)
serialize_broadcasting([ channel_name, model ])
end
Expand Down
Loading

0 comments on commit 7666185

Please sign in to comment.