Skip to content

Commit b2efc1f

Browse files
committed
feat: add redis and test sync adapter
1 parent 885f4ab commit b2efc1f

File tree

23 files changed

+528
-213
lines changed

23 files changed

+528
-213
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
/spec/dummy/log/*.log
99
/spec/dummy/storage/
1010
/spec/dummy/tmp/
11-
Gemfile.lock
1211
.rspec_status
12+
dump.rdb
13+
Gemfile.lock
1314

1415
# YARD docs
1516
doc/

examples/collaborative-text-editor/.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@
4040
/node_modules
4141

4242
# Redis
43-
dump.rdb
43+
/dump.rdb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
development:
2+
adapter: redis
3+
url: redis://localhost:6379/1
4+
5+
test:
6+
adapter: test
7+
8+
production:
9+
adapter: redis
10+
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>

gems/yrb-actioncable/Gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ gem "y-rb"
1414
group :development, :test do
1515
gem "foreman"
1616
gem "jsbundling-rails"
17-
gem "redis"
17+
gem "redis", "~> 5.0.6"
1818
gem "rspec"
1919
gem "rspec-rails"
2020
gem "rubocop"

gems/yrb-actioncable/lib/y/actioncable.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

3-
require "y/actioncable/config"
3+
require "y/actioncable/adapter/redis"
4+
require "y/actioncable/adapter/test"
5+
require "y/actioncable/configuration"
46
require "y/actioncable/engine"
57
require "y/actioncable/reliable"
68
require "y/actioncable/sync"
@@ -10,5 +12,8 @@
1012
module Y
1113
module Actioncable
1214
# Your code goes here...
15+
module_function def config # rubocop:disable Style/AccessModifierDeclarations
16+
@config ||= Y::Actioncable::Configuration.new
17+
end
1318
end
1419
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# frozen_string_literal: true
2+
3+
module Y
4+
module Actioncable
5+
module Adapter
6+
# A distributed Y::Actioncable coordinator for reliable messaging.
7+
class Redis
8+
# @attr_reader [::Redis] client The redis client
9+
attr_reader :client
10+
11+
# @param [Hash] config
12+
def initialize(config)
13+
@client = ::Redis.new(url: config[:url])
14+
end
15+
16+
# @param [String] key
17+
# @param [String] value
18+
# @param [Numeric] offset
19+
def add(key, value, offset = 0)
20+
client.zadd(key, offset.to_f, value)
21+
22+
nil
23+
end
24+
25+
# Append value to stream and return entry ID
26+
#
27+
# @param [String] key
28+
# @param [Hash] value
29+
# @return [String] The entry ID
30+
def append(key, value)
31+
client.xadd(key, value)
32+
end
33+
34+
# @param [String] key
35+
# @param [String] value
36+
def remove(key, value)
37+
client.zrem(key, value)
38+
39+
nil
40+
end
41+
42+
# Move item by setting offset
43+
#
44+
# @param [String] key
45+
# @param [String] value
46+
# @param [Numeric] offset
47+
def move(key, value, offset)
48+
client.zadd(
49+
key,
50+
offset.to_f,
51+
value,
52+
gt: true
53+
)
54+
55+
nil
56+
end
57+
58+
# Return the minimum value in the stream identified by key
59+
#
60+
# @param [String] key
61+
# @return [Numeric]
62+
def min(key)
63+
result = client.zrangebyscore(
64+
key,
65+
"-inf",
66+
"+inf",
67+
with_scores: true,
68+
limit: [0, 1]
69+
)
70+
71+
return 0 unless result
72+
return 0 unless result.size.positive?
73+
74+
result.first[1].to_i
75+
end
76+
77+
# Read values from stream starting (inclusive) from offset
78+
#
79+
# @param [String] key
80+
# @param [String] offset
81+
# @return [::Array<Object>]
82+
def read(key, offset = nil)
83+
offset ||= "-"
84+
85+
result = client.xrange(
86+
key,
87+
offset,
88+
"+"
89+
)
90+
91+
return [] unless result
92+
return [] unless result.size.positive?
93+
94+
result
95+
end
96+
97+
# Truncate the stream up until the given offset
98+
#
99+
# @param [String] key
100+
# @param [String] offset
101+
def truncate(key, offset)
102+
client.xtrim(key, offset, strategy: "MINID")
103+
104+
nil
105+
end
106+
end
107+
end
108+
end
109+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# frozen_string_literal: true
2+
3+
module Y
4+
module Actioncable
5+
module Adapter
6+
# A local Y::Actioncable coordinator for reliable messaging.
7+
class Test
8+
# @attr_reader [Hash] store
9+
attr_reader :store
10+
11+
def initialize
12+
@store = {}
13+
@last_ts = 0
14+
@counter = 0
15+
end
16+
17+
# @param [String] key
18+
# @param [String] value
19+
# @param [Numeric] offset
20+
def add(key, value, offset = 0)
21+
store[key] ||= {}
22+
store[key][value] = offset
23+
24+
nil
25+
end
26+
27+
# Append value to stream and return entry ID
28+
#
29+
# @param [String] key
30+
# @param [Hash] value
31+
# @return [String] The entry ID
32+
def append(key, value)
33+
curr_ts = Time.current.to_i
34+
if curr_ts == @last_ts
35+
@counter += 1
36+
else
37+
@counter = 0
38+
end
39+
@last_ts = curr_ts
40+
41+
entry_id = "#{Time.current.to_i}-#{@counter}"
42+
43+
store[key] ||= []
44+
store[key] << [entry_id, value]
45+
46+
entry_id
47+
end
48+
49+
# Return the minimum value in the stream identified by key
50+
#
51+
# @param [String] key
52+
# @return [Numeric]
53+
def min(key)
54+
store[key] ||= {}
55+
result = store[key].sort_by { |_k, v| -v }
56+
57+
return 0 unless result
58+
return 0 unless result.size.positive?
59+
60+
result.first[1].to_i
61+
end
62+
63+
# Move item by setting offset
64+
#
65+
# @param [String] key
66+
# @param [String] value
67+
# @param [Numeric] offset
68+
def move(key, value, offset)
69+
store[key] ||= {}
70+
store[key][value] = offset if store[key][value] && offset > store[key][value]
71+
72+
nil
73+
end
74+
75+
# Read values from stream starting (inclusive) from offset
76+
#
77+
# @param [String] key
78+
# @param [String, nil] offset
79+
# @return [::Array<Object>]
80+
def read(key, offset = nil)
81+
offset ||= "0-0"
82+
83+
store[key] ||= {}
84+
store[key].filter do |(entry_id, _)|
85+
ts1, counter1 = entry_id.split("-").map(&:to_i)
86+
ts2, counter2 = offset.split("-").map(&:to_i)
87+
88+
gte = false
89+
gte = true if ts1 >= ts2
90+
91+
gte = counter1 >= counter2 if ts1 == ts2
92+
93+
gte
94+
end
95+
end
96+
97+
# @param [String] key
98+
# @param [String] value
99+
def remove(key, value)
100+
store[key] ||= {}
101+
store[key].delete(value)
102+
103+
nil
104+
end
105+
106+
# Truncate the stream up until the given offset (exclusive)
107+
#
108+
# [1, 2, 3].truncate(2) = [2, 3]
109+
#
110+
# @param [String] key
111+
# @param [String] offset
112+
def truncate(key, offset)
113+
store[key] ||= {}
114+
store[key].filter! do |(entry_id, _)|
115+
ts1, counter1 = entry_id.split("-").map(&:to_i)
116+
ts2, counter2 = offset.split("-").map(&:to_i)
117+
118+
gte = false
119+
gte = true if ts1 >= ts2
120+
121+
gte = counter1 >= counter2 if ts1 == ts2
122+
123+
gte
124+
end
125+
126+
nil
127+
end
128+
end
129+
end
130+
end
131+
end

gems/yrb-actioncable/lib/y/actioncable/config.rb

-46
This file was deleted.

gems/yrb-actioncable/lib/y/actioncable/config/abstract_builder.rb

-29
This file was deleted.

0 commit comments

Comments
 (0)