cable_room 0.7.0.beta1 → 0.7.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -1
- data/lib/cable_room/host/runner.rb +8 -2
- data/lib/cable_room/host.rb +9 -4
- data/lib/cable_room/placement.rb +4 -2
- data/lib/cable_room/room/base.rb +1 -1
- data/lib/cable_room/room/snapshotting.rb +4 -0
- data/lib/cable_room/room_harness.rb +168 -0
- data/lib/cable_room/room_member.rb +15 -0
- data/lib/cable_room/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 230a3cec91f557f72ae459ed02fdf93ca4cc72d33a09a1e886ba503d27d665df
|
|
4
|
+
data.tar.gz: 4bc92906a5db6b957265f09b7fb3b596e587208fa01057890ad273ed1d9d0a94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0268892f4951a1c869caf8e5ab8d2218e4e06def83df3d5b32a3ae21b768678175668276dc47f4cb4a8fb3abcff42bc28092989aa9385c1df345e5c677de33af'
|
|
7
|
+
data.tar.gz: 186b3001f84e157c856164b22aff29984a7ec8471bc2e4ea28ac9c830a26a638ceae8852b3a5301f43eb27e26d4e0294f294543637353dbe04999dd91d71f70c
|
data/README.md
CHANGED
|
@@ -218,7 +218,8 @@ Timer bodies run on the Room's thread, so they're serialized against message han
|
|
|
218
218
|
When a host shuts down on purpose, it can move its Rooms to another host instead of closing them.
|
|
219
219
|
Members don't notice: their streams stay open, and the new host picks up where the old one left
|
|
220
220
|
off. The gem carries what it owns — every port with its token, tags, user, and last-seen time; the
|
|
221
|
-
user map;
|
|
221
|
+
user map; each reaper's deadline; and the Room's tenant, if `join_room` was given one (see
|
|
222
|
+
[Multi-tenancy](#multi-tenancy)). A Room's own state moves through two optional hooks:
|
|
222
223
|
|
|
223
224
|
```ruby
|
|
224
225
|
class QuizRoom < CableRoom::Room::Base
|
|
@@ -534,6 +535,7 @@ end
|
|
|
534
535
|
| `as:` | The user identity. Defaults to `current_user` when the channel has one. |
|
|
535
536
|
| `tags:` | Tags this port carries, for policies and targeted broadcasts. |
|
|
536
537
|
| `extra:` | Extra metadata, readable on the Room's `PortClient`. |
|
|
538
|
+
| `tenant:` | This Room's tenant, for a multi-tenant app. Defaults to the ambient tenant right here (see [Multi-tenancy](#multi-tenancy)). |
|
|
537
539
|
| `forward:` | Pipe every Room message straight to the websocket. |
|
|
538
540
|
| `on_joined:` | The Room acknowledged this port. |
|
|
539
541
|
| `on_message:` | Any message from the Room. |
|
|
@@ -555,6 +557,70 @@ request on the bus, and one of the Rooms hosts starts the Room (see
|
|
|
555
557
|
Room acknowledges the port, so a lost request costs at most one ping interval, and if the Room's
|
|
556
558
|
host process dies the next ping from any member brings it back somewhere else.
|
|
557
559
|
|
|
560
|
+
### Multi-tenancy.
|
|
561
|
+
|
|
562
|
+
A Room's own work runs on its Host's worker pool, not on the joining member's request thread, so
|
|
563
|
+
there's no Rack middleware and no request to derive an app's tenant from once the Room is up. Pass
|
|
564
|
+
`tenant:` to `join_room` for exactly this — whatever it's given rides along on the provision
|
|
565
|
+
request, comes back out as `Runner#tenant` (and `Room#tenant`), and survives a host migration
|
|
566
|
+
(it's part of what a Room's snapshot carries, alongside its ports and users; see
|
|
567
|
+
[Migration hooks](#migration-hooks)). Leave it out and it defaults to whatever
|
|
568
|
+
`Apartment::Tenant.current` returns right where `join_room` is called — the member's own request
|
|
569
|
+
thread, the last place with real request context before the Room's work moves to a Host thread:
|
|
570
|
+
|
|
571
|
+
```ruby
|
|
572
|
+
join_room(QuizRoom, params[:quiz_id], create: true) # tenant: Apartment::Tenant.current, captured here
|
|
573
|
+
join_room(QuizRoom, params[:quiz_id], create: true, tenant: "some-other-org") # explicit override
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
cable_room doesn't depend on Apartment (or anything else) to use this — `tenant` is just a value it
|
|
577
|
+
carries around for you, the same way `key` or `extra` are. **It never switches anything itself.**
|
|
578
|
+
Whatever ends up on `Runner#tenant` still has to actually be applied before a Room's DB calls run,
|
|
579
|
+
the same way a request's tenant has to be applied before a controller action's do.
|
|
580
|
+
|
|
581
|
+
If you're wiring this up yourself, here's the shape of that hook:
|
|
582
|
+
|
|
583
|
+
```ruby
|
|
584
|
+
# Lazily switch a worker thread to the right tenant only when it's about to touch the DB.
|
|
585
|
+
# Actively switching up front checks out a connection and runs SET search_path even for a
|
|
586
|
+
# message that never queries anything, so this only stages the tenant (no DB call) and lets
|
|
587
|
+
# the connection pool's own checkout hook apply it the moment a connection is actually acquired.
|
|
588
|
+
ActionCable::Server::Worker.set_callback :work, :around do |_, blk|
|
|
589
|
+
Thread.current[:cable_tenant] = {
|
|
590
|
+
adapter: Apartment::Tenant.adapter,
|
|
591
|
+
tenant: connection.tenant,
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
# If this thread already holds a connection from earlier work, release it so the checkout
|
|
595
|
+
# hook below gets a fresh checkout to apply the schema to.
|
|
596
|
+
pool = Apartment.connection_class.connection_pool
|
|
597
|
+
pool.release_connection if pool.active_connection?
|
|
598
|
+
|
|
599
|
+
Apartment::Tenant.adapter.instance_variable_set(:@current, connection.tenant)
|
|
600
|
+
|
|
601
|
+
blk.call
|
|
602
|
+
ensure
|
|
603
|
+
Thread.current[:cable_tenant] = nil
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
ActiveSupport.on_load(:active_record) do
|
|
607
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback :checkout, :after do |conn|
|
|
608
|
+
next unless (ct = Thread.current[:cable_tenant]).present?
|
|
609
|
+
next unless Apartment::Tenant.adapter.is_a?(Apartment::Adapters::PostgresqlSchemaAdapter)
|
|
610
|
+
next unless conn.pool == Apartment.connection_class.connection_pool
|
|
611
|
+
|
|
612
|
+
adapter = ct[:adapter]
|
|
613
|
+
adapter.instance_variable_set(:@current, ct[:tenant])
|
|
614
|
+
conn.schema_search_path = adapter.send :full_search_path
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
`connection` here is whatever `Worker#work(connection)` was handed — a real
|
|
620
|
+
`ActionCable::Connection::Base` for ordinary channel work, a `Host::Runner` for Room work. Either
|
|
621
|
+
way it's read fresh on every unit of work, so a Room only ever sees its own tenant even though many
|
|
622
|
+
Rooms for many orgs share the same small pool of Host worker threads.
|
|
623
|
+
|
|
558
624
|
### The hello handshake.
|
|
559
625
|
|
|
560
626
|
A membership doesn't announce itself to the Room when the channel subscribes. Stream subscriptions
|
|
@@ -25,7 +25,7 @@ module CableRoom
|
|
|
25
25
|
|
|
26
26
|
delegate :worker_pool, :inbound, to: :host
|
|
27
27
|
|
|
28
|
-
def initialize(host, room_class, key, lock_info, watchdog_interval:, lock_duration:)
|
|
28
|
+
def initialize(host, room_class, key, lock_info, watchdog_interval:, lock_duration:, tenant: nil)
|
|
29
29
|
@host = host
|
|
30
30
|
@room_class = room_class
|
|
31
31
|
@key = key
|
|
@@ -48,7 +48,13 @@ module CableRoom
|
|
|
48
48
|
@handlers = {} # stream => the room's handler, so a message can be fed in by hand (see `inject`)
|
|
49
49
|
@periodic_timers = []
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
# Whatever the member's provision request said (RoomMembership defaults that to the
|
|
52
|
+
# ambient tenant at join_room's own call site -- see RoomMembership#initialize) or, for a
|
|
53
|
+
# migrated room, whatever its snapshot carried forward. Nothing here parses it out of
|
|
54
|
+
# `key` or guesses from ambient state itself: a Host thread has no request of its own, so
|
|
55
|
+
# anything read here would be leftover from whatever this thread ran last, not this room's
|
|
56
|
+
# tenant.
|
|
57
|
+
@tenant = tenant
|
|
52
58
|
|
|
53
59
|
@logger = ActionCable::Connection::TaggedLoggerProxy.new(
|
|
54
60
|
host.logger,
|
data/lib/cable_room/host.rb
CHANGED
|
@@ -147,7 +147,7 @@ module CableRoom
|
|
|
147
147
|
# made after the lock is won, so it can't be raced. `handoff_only: true` (a handoff request)
|
|
148
148
|
# starts nothing when there is no snapshot to adopt — it has expired, or the old host took
|
|
149
149
|
# the room back — and lets the lock go.
|
|
150
|
-
def ensure_room(room_class, key = nil, handoff_only: false)
|
|
150
|
+
def ensure_room(room_class, key = nil, handoff_only: false, tenant: nil)
|
|
151
151
|
lock_key = room_class.room_port_key(key)
|
|
152
152
|
lock_info = CableRoom.lock_manager.lock(lock_key, room_class::LOCK_DURATION.in_milliseconds)
|
|
153
153
|
return false unless lock_info
|
|
@@ -167,6 +167,7 @@ module CableRoom
|
|
|
167
167
|
lock_info,
|
|
168
168
|
watchdog_interval: room_class::WATCH_DOG_INTERVAL,
|
|
169
169
|
lock_duration: room_class::LOCK_DURATION,
|
|
170
|
+
tenant: tenant,
|
|
170
171
|
)
|
|
171
172
|
|
|
172
173
|
true
|
|
@@ -176,9 +177,12 @@ module CableRoom
|
|
|
176
177
|
end
|
|
177
178
|
|
|
178
179
|
# Start running a room here. The caller (`ensure_room`) has already taken the room's Redlock;
|
|
179
|
-
# the runner renews it from now on and releases it when the room dies.
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
# the runner renews it from now on and releases it when the room dies. `tenant:` comes from the
|
|
181
|
+
# member's provision request (see RoomMembership#initialize / #maybe_provision_room and
|
|
182
|
+
# Placement#claim), already defaulted there to the ambient tenant if the app didn't pass one
|
|
183
|
+
# explicitly -- nil past that point just means no Apartment multi-tenancy is in play.
|
|
184
|
+
def start_room(room_class, key, lock_info, watchdog_interval:, lock_duration:, tenant: nil)
|
|
185
|
+
runner = Runner.new(self, room_class, key, lock_info, watchdog_interval:, lock_duration:, tenant:)
|
|
182
186
|
runner.start!
|
|
183
187
|
runner
|
|
184
188
|
end
|
|
@@ -215,6 +219,7 @@ module CableRoom
|
|
|
215
219
|
lock_info,
|
|
216
220
|
watchdog_interval: room_class::WATCH_DOG_INTERVAL,
|
|
217
221
|
lock_duration: room_class::LOCK_DURATION,
|
|
222
|
+
tenant: snapshot["tenant"],
|
|
218
223
|
)
|
|
219
224
|
runner.restore!(snapshot, hold_inbound: hold_inbound)
|
|
220
225
|
runner
|
data/lib/cable_room/placement.rb
CHANGED
|
@@ -181,10 +181,12 @@ module CableRoom
|
|
|
181
181
|
# `handoff: true` (with a `reason`) is a draining host offering a room to its peers. The
|
|
182
182
|
# race is the same — the lock decides — but the winner rebuilds the room from the offered
|
|
183
183
|
# snapshot and replays what the old host relayed instead of starting it from scratch (see
|
|
184
|
-
# CableRoom::Migration.adopt). A plain request starts the room fresh.
|
|
184
|
+
# CableRoom::Migration.adopt). A plain request starts the room fresh. A handoff request
|
|
185
|
+
# carries no `tenant` of its own -- the snapshot it adopts from carries it instead (see
|
|
186
|
+
# Room::Snapshotting#_snapshot), so `ensure_room` only needs it for the fresh-start path.
|
|
185
187
|
handoff = request["handoff"] == true
|
|
186
188
|
notification[:handoff] = handoff
|
|
187
|
-
notification[:claimed] = host.ensure_room(room_class, room_key, handoff_only: handoff)
|
|
189
|
+
notification[:claimed] = host.ensure_room(room_class, room_key, handoff_only: handoff, tenant: request["tenant"])
|
|
188
190
|
end
|
|
189
191
|
|
|
190
192
|
if claimed
|
data/lib/cable_room/room/base.rb
CHANGED
|
@@ -39,6 +39,10 @@ module CableRoom
|
|
|
39
39
|
version: Snapshot::VERSION,
|
|
40
40
|
room_class: self.class.name,
|
|
41
41
|
key: Snapshot.serialize_argument(key),
|
|
42
|
+
# Carried forward so the host that adopts this room (Host#restore_room) gets the same
|
|
43
|
+
# tenant this one was given at provisioning, instead of falling back to a guess (see
|
|
44
|
+
# Runner#initialize). nil for a room that was never given one either.
|
|
45
|
+
tenant: @runner.tenant,
|
|
42
46
|
port_clients: _snapshot_port_clients,
|
|
43
47
|
user_state: _snapshot_user_state,
|
|
44
48
|
reaper_state: _snapshot_reaper_state,
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require "cable_room"
|
|
2
|
+
require "active_support/tagged_logging"
|
|
3
|
+
|
|
4
|
+
module CableRoom
|
|
5
|
+
# Builds a Room without a Host, a live pubsub subscription, or a Redis lock, so the parts of a
|
|
6
|
+
# Room that are pure logic (authorization, port scoping, callbacks, reaping) can be tested
|
|
7
|
+
# directly and synchronously. Anything that needs real message delivery belongs in an e2e spec.
|
|
8
|
+
#
|
|
9
|
+
# Not loaded by `require "cable_room"` -- an app (or this gem's own suite) opts in with
|
|
10
|
+
# `require "cable_room/room_harness"`, typically from spec_helper.
|
|
11
|
+
#
|
|
12
|
+
# Stands in for CableRoom::Host::Runner.
|
|
13
|
+
class StubRoomRunner
|
|
14
|
+
attr_reader :logger, :state, :shutdown_reason, :watchdog_pings, :tenant
|
|
15
|
+
|
|
16
|
+
def initialize(tenant: nil)
|
|
17
|
+
@logger = ActiveSupport::TaggedLogging.new(Logger.new(IO::NULL))
|
|
18
|
+
@state = :started
|
|
19
|
+
@watchdog_pings = 0
|
|
20
|
+
@timers = []
|
|
21
|
+
@tenant = tenant
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ping_watchdog
|
|
25
|
+
@watchdog_pings += 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Record streams rather than subscribing, so startup can run without a live pubsub. There is
|
|
29
|
+
# nothing to wait for, so `on_live` runs at once.
|
|
30
|
+
def subscribe(stream, on_live: nil, &blk)
|
|
31
|
+
streams[stream] = blk
|
|
32
|
+
on_live&.call
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def unsubscribe(stream)
|
|
36
|
+
streams.delete(stream)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def streams
|
|
40
|
+
@streams ||= {}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Feed a message to whatever the room streamed from this port, the way pubsub would
|
|
44
|
+
def deliver_to_stream(stream, message)
|
|
45
|
+
streams.fetch(stream).call(message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initiate_shutdown(reason)
|
|
49
|
+
@shutdown_reason = reason
|
|
50
|
+
@state = :shutting_down
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def stop!
|
|
54
|
+
@state = :dead
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Run inline so specs stay synchronous
|
|
58
|
+
def post_work(**_kwargs, &blk)
|
|
59
|
+
blk.call
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Record timers rather than scheduling them, so specs can fire them on demand
|
|
63
|
+
def start_periodic_timer(callback, every:)
|
|
64
|
+
timer = StubTimer.new(callback, every)
|
|
65
|
+
@timers << timer
|
|
66
|
+
timer
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def timers
|
|
70
|
+
@timers.reject(&:shutdown?)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class StubTimer
|
|
74
|
+
attr_reader :interval
|
|
75
|
+
|
|
76
|
+
def initialize(callback, interval)
|
|
77
|
+
@callback = callback
|
|
78
|
+
@interval = interval
|
|
79
|
+
@shutdown = false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def fire!
|
|
83
|
+
@callback.call
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def shutdown
|
|
87
|
+
@shutdown = true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def shutdown?
|
|
91
|
+
@shutdown
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
module RoomHarness
|
|
97
|
+
# A fully-initialized Room backed by a StubRoomRunner. Pass `started: true` to run the
|
|
98
|
+
# startup callbacks (which is what registers reaper timers and the inbound stream). Pass
|
|
99
|
+
# `tenant:` the way a member's provision request would (see RoomMembership#initialize) for a
|
|
100
|
+
# Room whose logic needs Apartment scoped correctly -- this switches into it for the duration
|
|
101
|
+
# of startup, the way PandaPal's ActionCable::Server::Worker hook does in production for a
|
|
102
|
+
# real Runner, so a Room can't tell the difference from here.
|
|
103
|
+
def build_room(room_class, key: "harness-key", started: false, tenant: nil)
|
|
104
|
+
runner = StubRoomRunner.new(tenant: tenant)
|
|
105
|
+
room = room_class.allocate
|
|
106
|
+
room.send(:initialize, runner, key)
|
|
107
|
+
runner.define_singleton_method(:room) { room }
|
|
108
|
+
with_room_tenant(runner) { room.send(:_startup) } if started
|
|
109
|
+
room
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# A Room rebuilt from `snapshot` on a fresh StubRoomRunner, the way Host#restore_room would
|
|
113
|
+
# build it: gem state restored, then the startup chain with `restore_state` in place of
|
|
114
|
+
# `startup`. Pass the class explicitly so a spec reads as "restore a FooRoom", even though the
|
|
115
|
+
# snapshot names it too. `tenant` comes off the snapshot itself, same as Host#restore_room.
|
|
116
|
+
def restore_room(room_class, snapshot)
|
|
117
|
+
runner = StubRoomRunner.new(tenant: snapshot["tenant"])
|
|
118
|
+
room = room_class.allocate
|
|
119
|
+
room.send(:initialize, runner, Snapshot.key_for(snapshot))
|
|
120
|
+
runner.define_singleton_method(:room) { room }
|
|
121
|
+
with_room_tenant(runner) { room.send(:_restore, snapshot) }
|
|
122
|
+
room
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def stub_runner_for(room)
|
|
126
|
+
room.instance_variable_get(:@runner)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Push a message through the same callback chain the real stream handler uses.
|
|
130
|
+
# Returns false when authorization halted the chain, otherwise :handled.
|
|
131
|
+
def deliver(room, message)
|
|
132
|
+
message = message.deep_stringify_keys if message.is_a?(Hash)
|
|
133
|
+
Room.current_message = message
|
|
134
|
+
with_room_tenant(stub_runner_for(room)) do
|
|
135
|
+
room.send(:run_callbacks, :receive_message) do
|
|
136
|
+
room.send(:handle_received_message, message)
|
|
137
|
+
:handled
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
ensure
|
|
141
|
+
Room.current_message = nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Register a port on the room the way a real RoomMembership would, returning its token.
|
|
145
|
+
def connect_port(room, token: SecureRandom.hex(16), tags: [], as: nil)
|
|
146
|
+
message = { type: 'port_connected', mtok: token, tags: tags }
|
|
147
|
+
message[:extra] = ::ActiveJob::Arguments.serialize([{ as: as }]) if as
|
|
148
|
+
deliver(room, message)
|
|
149
|
+
token
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def port_client(room, token)
|
|
153
|
+
room.send(:resolve_client_port, token)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
# Stands in for PandaPal's ActionCable::Server::Worker hook, which is what actually switches
|
|
159
|
+
# Apartment before a Host thread's DB calls in production. A Room without a `tenant` (a
|
|
160
|
+
# single-tenant app, or a spec that doesn't need one) gets no-op behavior; the gem itself
|
|
161
|
+
# stays Apartment-optional either way.
|
|
162
|
+
def with_room_tenant(runner, &blk)
|
|
163
|
+
return blk.call unless runner.tenant && defined?(Apartment)
|
|
164
|
+
|
|
165
|
+
Apartment::Tenant.switch(runner.tenant, &blk)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -148,6 +148,7 @@ module CableRoom
|
|
|
148
148
|
on_left: nil,
|
|
149
149
|
tags: [],
|
|
150
150
|
extra: nil,
|
|
151
|
+
tenant: nil,
|
|
151
152
|
&preconfigure
|
|
152
153
|
)
|
|
153
154
|
@mutex = Monitor.new
|
|
@@ -161,6 +162,14 @@ module CableRoom
|
|
|
161
162
|
@room_key = room_key
|
|
162
163
|
@allow_create = create
|
|
163
164
|
|
|
165
|
+
# Defaults to the ambient tenant right here, at join_room's own call site -- the member's
|
|
166
|
+
# own request thread, the last point before this room's work moves to a Host thread with no
|
|
167
|
+
# request of its own to derive one from (see Runner#initialize). An app only needs to pass
|
|
168
|
+
# `tenant:` explicitly to ask for something other than "whatever tenant this join is
|
|
169
|
+
# happening under", which is the common case. nil isn't a real tenant either way, so it
|
|
170
|
+
# doubles as "not given".
|
|
171
|
+
@tenant = tenant || (Apartment::Tenant.current if defined?(Apartment))
|
|
172
|
+
|
|
164
173
|
@on_room_opened = on_room_opened
|
|
165
174
|
@on_joined = on_joined
|
|
166
175
|
@on_message = on_message
|
|
@@ -230,6 +239,7 @@ module CableRoom
|
|
|
230
239
|
"extra" => serialized_extra,
|
|
231
240
|
"create" => @allow_create,
|
|
232
241
|
"hello_received" => @hello_received,
|
|
242
|
+
"tenant" => @tenant,
|
|
233
243
|
}
|
|
234
244
|
end
|
|
235
245
|
|
|
@@ -446,6 +456,7 @@ module CableRoom
|
|
|
446
456
|
@serialized_extra = record["extra"]
|
|
447
457
|
@allow_create = record["create"] == true
|
|
448
458
|
@hello_received = record["hello_received"] == true
|
|
459
|
+
@tenant = record["tenant"]
|
|
449
460
|
end
|
|
450
461
|
|
|
451
462
|
def transmit_port_connected
|
|
@@ -491,6 +502,10 @@ module CableRoom
|
|
|
491
502
|
# Keys travel the way `extra` does, so a record key arrives on the host as the record
|
|
492
503
|
room_key: ::ActiveJob::Arguments.serialize([@room_key]),
|
|
493
504
|
requested_at: Time.current,
|
|
505
|
+
# Whatever `join_room` was given for `tenant:` (nil for a single-tenant app). The Host has
|
|
506
|
+
# no request of its own to derive this from, so a multi-tenant app has to hand it over
|
|
507
|
+
# explicitly here rather than have the Runner guess at ambient state (see Runner#initialize).
|
|
508
|
+
tenant: @tenant,
|
|
494
509
|
}
|
|
495
510
|
|
|
496
511
|
ActiveSupport::Notifications.instrument(
|
data/lib/cable_room/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cable_room
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.0.
|
|
4
|
+
version: 0.7.0.beta2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ethan Knapp
|
|
@@ -140,6 +140,7 @@ files:
|
|
|
140
140
|
- lib/cable_room/room/snapshotting.rb
|
|
141
141
|
- lib/cable_room/room/threading.rb
|
|
142
142
|
- lib/cable_room/room/user_management.rb
|
|
143
|
+
- lib/cable_room/room_harness.rb
|
|
143
144
|
- lib/cable_room/room_member.rb
|
|
144
145
|
- lib/cable_room/room_proxy_channel.rb
|
|
145
146
|
- lib/cable_room/snapshot.rb
|