solid_cable 2.0.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +134 -2
- data/app/jobs/solid_cable/trim_job.rb +19 -1
- data/app/models/solid_cable/message.rb +18 -1
- data/app/models/solid_cable/record.rb +8 -0
- data/lib/action_cable/subscription_adapter/solid_cable.rb +3 -3
- data/lib/generators/solid_cable/install/templates/config/cable.yml +4 -0
- data/lib/generators/solid_cable/install/templates/db/cable_schema.rb +5 -4
- data/lib/generators/solid_cable/update/USAGE +8 -0
- data/lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb +14 -0
- data/lib/generators/solid_cable/update/update_generator.rb +15 -0
- data/lib/solid_cable/version.rb +1 -1
- data/lib/solid_cable.rb +22 -1
- data/lib/tasks/solid_cable_tasks.rake +5 -1
- metadata +49 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f06792b582165dee33117bc0433cf27e61d3d9e932765bced3d8cdb4891d0ba9
|
4
|
+
data.tar.gz: 81d50418ae04e5f99b82d57d13ec52fc293ec12a2bbd34f7d0b1f6cfcbaa0a9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f79b6a5e853f86aba526fc5963039d4888659315d08604b72e202b167912e0b26ba8120c53a6b0f4cf9c41cbdc30f53c96f3714597e0b1e4c00363fd69d9bba9
|
7
|
+
data.tar.gz: 5106fd2c0d1f12c3cfd854bb55a1ec239eb85bd51282c2fe9905f973f041ead14798da8466a9abd19385e09f478a1ca596db639f1a2d77e8836bcad35e603d30
|
data/README.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
Solid Cable is a database-backed Action Cable adapter that keeps messages in a table and continously polls for updates. This makes it possible to drop the common dependency on Redis, if it isn't needed for any other purpose. Despite polling, the performance of Solid Cable is comparable to Redis in most situations. And in all circumstances, it makes it easier to deploy Rails when Redis is no longer a required dependency for Action Cable functionality.
|
4
4
|
|
5
|
+
> [!NOTE]
|
6
|
+
> Solid Cable is primarily targeted at MySQL and SQLite but is tested to work
|
7
|
+
> with PostgreSQL. PostgreSQL has its own Action Cable adapter which utilizes
|
8
|
+
> the NOTIFY command for better performance. However, the PostgreSQL adapter
|
9
|
+
> does have an 8kb limit on its payload, so if you find yourself broadcasting
|
10
|
+
> large payloads, Solid Cable will work without a hitch.
|
5
11
|
|
6
12
|
## Installation
|
7
13
|
|
@@ -67,14 +73,140 @@ The options are:
|
|
67
73
|
- `message_retention` - sets the retention time for messages kept in the database. Used as the cut-off when trimming is performed. (Defaults to 1.day)
|
68
74
|
- `autotrim` - sets wether you want Solid Cable to handle autotrimming messages. (Defaults to true)
|
69
75
|
- `silence_polling` - whether to silence Active Record logs emitted when polling (Defaults to true)
|
76
|
+
- `use_skip_locked` - whether to use `FOR UPDATE SKIP LOCKED` when performing trimming. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential. (Defaults to true)
|
77
|
+
- `trim_batch_size` - the batch size to use when deleting old records (default: `100`)
|
78
|
+
|
70
79
|
|
71
80
|
## Trimming
|
72
81
|
|
73
|
-
Messages are autotrimmed based upon the `message_retention` setting to determine how long messages are to be kept around. If no `message_retention` is given or parsing fails, it defaults to `1.day`. Messages are trimmed when a
|
82
|
+
Messages are autotrimmed based upon the `message_retention` setting to determine how long messages are to be kept around. If no `message_retention` is given or parsing fails, it defaults to `1.day`. Messages are trimmed when a messsage is broadcast.
|
74
83
|
|
75
|
-
Autotrimming can negatively impact performance depending on your workload because it is doing a delete on
|
84
|
+
Autotrimming can negatively impact performance slightly depending on your workload because it is potentially doing a delete on broadcast. If
|
76
85
|
you would prefer, you can disable autotrimming by setting `autotrim: false` and you can manually enqueue the job later, `SolidCable::TrimJob.perform_later`, or run it on a recurring interval out of band.
|
77
86
|
|
87
|
+
|
88
|
+
## Upgrading
|
89
|
+
|
90
|
+
If you have already installed Solid Cable < 3 and are upgrading to version 3,
|
91
|
+
run `solid_cable:update` to install a new migration.
|
92
|
+
|
93
|
+
|
94
|
+
## Benchmarks
|
95
|
+
|
96
|
+
Inside the `bench` directory there is a minimal Rails app that is used to benchmark.
|
97
|
+
You are welcome to update the config/deploy.yml file to point to your own server
|
98
|
+
if you want to deploy the app to your own server and run benchmarks.
|
99
|
+
|
100
|
+
To benchmark we use [k6](https://k6.io). Most of the setup was gotten from this
|
101
|
+
[article](https://evilmartians.com/chronicles/real-time-stress-anycable-k6-websockets-and-yabeda).
|
102
|
+
1. Install k6
|
103
|
+
1. Install xk6-cable by running `xk6 build --with
|
104
|
+
github.com/anycable/xk6-cable`. This will output a custom k6 binary.
|
105
|
+
1. Run the load test with `./k6 run loadtest.js`
|
106
|
+
- This script takes a variety of ENV variables:
|
107
|
+
- WS_URL: The url to send websocket connections
|
108
|
+
- MAX: The number of virtual users to hit the server with
|
109
|
+
- TIME: The duration of the load test
|
110
|
+
- MESSAGES_NUM: The number of messages each VU will send to the server
|
111
|
+
|
112
|
+
|
113
|
+
#### Results
|
114
|
+
|
115
|
+
Our loadtest is run on a Hetzner CCX13, with a MESSAGES_NUM of 5, and a TIME of 90.
|
116
|
+
|
117
|
+
##### SQLite
|
118
|
+
|
119
|
+
With a polling interval of 0.1 seconds and autotrimming enabled.
|
120
|
+
|
121
|
+
100 VUs
|
122
|
+
```
|
123
|
+
rtt..................: avg=135.82ms min=50ms med=138ms max=357ms p(90)=174ms p(95)=195ms
|
124
|
+
ws_connecting........: avg=205.81ms min=149.35ms med=199.01ms max=509.48ms p(90)=254.04ms p(95)=261.77ms
|
125
|
+
```
|
126
|
+
250 VUs
|
127
|
+
```
|
128
|
+
rtt..................: avg=146.24ms min=50ms med=144ms max=435ms p(90)=209ms p(95)=234.04ms
|
129
|
+
ws_connecting........: avg=222.15ms min=146.47ms med=208.57ms max=1.3s p(90)=263.6ms p(95)=284.18ms
|
130
|
+
```
|
131
|
+
500 VUs
|
132
|
+
```
|
133
|
+
rtt..................: avg=271.79ms min=48ms med=205ms max=1.15s p(90)=558ms p(95)=660ms
|
134
|
+
ws_connecting........: avg=248.81ms min=145.89ms med=221.89ms max=1.38s p(90)=290.41ms p(95)=322.2ms
|
135
|
+
```
|
136
|
+
750 VUs
|
137
|
+
```
|
138
|
+
rtt..................: avg=548.27ms min=51ms med=438ms max=5.19s p(90)=1.18s p(95)=1.29s
|
139
|
+
ws_connecting........: avg=266.37ms min=144.06ms med=224.93ms max=2.33s p(90)=298ms p(95)=342.87ms
|
140
|
+
```
|
141
|
+
|
142
|
+
With trimming disabled
|
143
|
+
|
144
|
+
250 VUs
|
145
|
+
```
|
146
|
+
rtt..................: avg=139.47ms min=48ms med=142ms max=807ms p(90)=189ms p(95)=214ms
|
147
|
+
ws_connecting........: avg=212.58ms min=146.19ms med=196.25ms max=1.25s p(90)=255.74ms p(95)=272.44ms
|
148
|
+
```
|
149
|
+
|
150
|
+
With a polling interval of 0.01 seconds it becomes comparable to Redis
|
151
|
+
|
152
|
+
250 VUs
|
153
|
+
```
|
154
|
+
rtt..................: avg=84.22ms min=43ms med=69ms max=416ms p(90)=137ms p(95)=150ms
|
155
|
+
ws_connecting........: avg=219.37ms min=144.71ms med=200.77ms max=2.17s p(90)=265.23ms p(95)=290.83ms
|
156
|
+
```
|
157
|
+
|
158
|
+
##### Redis
|
159
|
+
|
160
|
+
This instance was hosted on the same machine.
|
161
|
+
|
162
|
+
100 VUs
|
163
|
+
```
|
164
|
+
rtt..................: avg=68.95ms min=41ms med=56ms max=6.23s p(90)=114ms p(95)=129ms
|
165
|
+
ws_connecting........: avg=211.09ms min=153.23ms med=195.69ms max=1.44s p(90)=258.1ms p(95)=272.23ms
|
166
|
+
```
|
167
|
+
250 VUs
|
168
|
+
```
|
169
|
+
rtt..................: avg=69.32ms min=40ms med=56ms max=645ms p(90)=119ms p(95)=135ms
|
170
|
+
ws_connecting........: avg=212.95ms min=142.92ms med=196.31ms max=1.25s p(90)=260.25ms p(95)=273.49ms
|
171
|
+
```
|
172
|
+
500 VUs
|
173
|
+
```
|
174
|
+
rtt..................: avg=87.5ms min=40ms med=67ms max=839ms p(90)=149ms p(95)=176ms
|
175
|
+
ws_connecting........: avg=242.62ms min=142.03ms med=213.76ms max=2.34s p(90)=291.25ms p(95)=324.04ms
|
176
|
+
```
|
177
|
+
750 VUs
|
178
|
+
```
|
179
|
+
rtt..................: avg=162.54ms min=39ms med=123ms max=2.26s p(90)=343.1ms p(95)=438ms
|
180
|
+
ws_connecting........: avg=353.08ms min=143ms med=264.15ms max=2.73s p(90)=541.36ms p(95)=1.15s
|
181
|
+
```
|
182
|
+
|
183
|
+
|
184
|
+
##### MySQL
|
185
|
+
|
186
|
+
With a polling interval of 0.1 seconds and autotrimming enabled. This instance
|
187
|
+
was also hosted on the same machine.
|
188
|
+
|
189
|
+
100 VUs
|
190
|
+
```
|
191
|
+
rtt..................: avg=136.02ms min=51ms med=137ms max=877ms p(90)=168.1ms p(95)=198ms
|
192
|
+
ws_connecting........: avg=207.76ms min=151.93ms med=196.74ms max=1.21s p(90)=249.91ms p(95)=260.37ms
|
193
|
+
```
|
194
|
+
250 VUs
|
195
|
+
```
|
196
|
+
rtt..................: avg=159.33ms min=51ms med=149ms max=559ms p(90)=236ms p(95)=263ms
|
197
|
+
ws_connecting........: avg=232.38ms min=151.6ms med=218.09ms max=1.38s p(90)=287.99ms p(95)=324.6ms
|
198
|
+
```
|
199
|
+
500 VUs
|
200
|
+
```
|
201
|
+
rtt..................: avg=441.07ms min=51ms med=312ms max=2.29s p(90)=931ms p(95)=1.07s
|
202
|
+
ws_connecting........: avg=256.73ms min=152.23ms med=231.02ms max=2.31s p(90)=305.69ms p(95)=340.83ms
|
203
|
+
```
|
204
|
+
750 VUs
|
205
|
+
```
|
206
|
+
rtt..................: avg=822.08ms min=51ms med=732ms max=5.05s p(90)=1.76s p(95)=1.97s
|
207
|
+
ws_connecting........: avg=278.08ms min=146.66ms med=236.35ms max=2.37s p(90)=318.17ms p(95)=374.98ms
|
208
|
+
```
|
209
|
+
|
78
210
|
## License
|
79
211
|
|
80
212
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -3,7 +3,25 @@
|
|
3
3
|
module SolidCable
|
4
4
|
class TrimJob < ActiveJob::Base
|
5
5
|
def perform
|
6
|
-
|
6
|
+
return unless trim?
|
7
|
+
|
8
|
+
::SolidCable::Message.transaction do
|
9
|
+
ids = ::SolidCable::Message.trimmable.non_blocking_lock.
|
10
|
+
limit(trim_batch_size).pluck(:id)
|
11
|
+
::SolidCable::Message.where(id: ids).delete_all
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def trim_batch_size
|
18
|
+
::SolidCable.trim_batch_size
|
19
|
+
end
|
20
|
+
|
21
|
+
def trim?
|
22
|
+
expires_per_write = (1 / trim_batch_size.to_f) * ::SolidCable.trim_chance
|
23
|
+
|
24
|
+
rand < (expires_per_write - expires_per_write.floor)
|
7
25
|
end
|
8
26
|
end
|
9
27
|
end
|
@@ -6,7 +6,24 @@ module SolidCable
|
|
6
6
|
where(created_at: ..::SolidCable.message_retention.ago)
|
7
7
|
}
|
8
8
|
scope :broadcastable, lambda { |channels, last_id|
|
9
|
-
where(
|
9
|
+
where(channel_hash: channel_hashes_for(channels)).
|
10
|
+
where(id: (last_id + 1)..).order(:id)
|
10
11
|
}
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def broadcast(channel, payload)
|
15
|
+
insert({ channel:, payload:, channel_hash: channel_hash_for(channel) })
|
16
|
+
end
|
17
|
+
|
18
|
+
def channel_hashes_for(channels)
|
19
|
+
channels.map { |channel| channel_hash_for(channel) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Need to unpack this as a signed integer since Postgresql and SQLite
|
23
|
+
# don't support unsigned integers
|
24
|
+
def channel_hash_for(channel)
|
25
|
+
Digest::SHA256.digest(channel.to_s).unpack1("q>")
|
26
|
+
end
|
27
|
+
end
|
11
28
|
end
|
12
29
|
end
|
@@ -5,5 +5,13 @@ module SolidCable
|
|
5
5
|
self.abstract_class = true
|
6
6
|
|
7
7
|
connects_to(**SolidCable.connects_to) if SolidCable.connects_to.present?
|
8
|
+
|
9
|
+
def self.non_blocking_lock
|
10
|
+
if SolidCable.use_skip_locked
|
11
|
+
lock(Arel.sql("FOR UPDATE SKIP LOCKED"))
|
12
|
+
else
|
13
|
+
lock
|
14
|
+
end
|
15
|
+
end
|
8
16
|
end
|
9
17
|
end
|
@@ -15,7 +15,9 @@ module ActionCable
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def broadcast(channel, payload)
|
18
|
-
::SolidCable::Message.
|
18
|
+
::SolidCable::Message.broadcast(channel, payload)
|
19
|
+
|
20
|
+
::SolidCable::TrimJob.perform_now if ::SolidCable.autotrim?
|
19
21
|
end
|
20
22
|
|
21
23
|
def subscribe(channel, callback, success_callback = nil)
|
@@ -68,8 +70,6 @@ module ActionCable
|
|
68
70
|
|
69
71
|
def remove_channel(channel)
|
70
72
|
channels.delete(channel)
|
71
|
-
|
72
|
-
::SolidCable::TrimJob.perform_now if ::SolidCable.autotrim?
|
73
73
|
end
|
74
74
|
|
75
75
|
def invoke_callback(*)
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# Async adapter only works within the same process, so for manually triggering cable updates from a console,
|
2
|
+
# and seeing results in the browser, you must do so from the web console (running inside the dev process),
|
3
|
+
# not a terminal started via bin/rails console! Add "console" to any action or "<%= console %>" in any view
|
4
|
+
# to make the web console appear.
|
1
5
|
development:
|
2
6
|
adapter: async
|
3
7
|
|
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
ActiveRecord::Schema[7.1].define(version: 1) do
|
4
4
|
create_table "solid_cable_messages", force: :cascade do |t|
|
5
|
-
t.
|
6
|
-
t.
|
5
|
+
t.binary "channel", limit: 1024, null: false
|
6
|
+
t.binary "payload", limit: 536870912, null: false
|
7
7
|
t.datetime "created_at", null: false
|
8
8
|
t.datetime "updated_at", null: false
|
9
|
-
t.
|
10
|
-
|
9
|
+
t.integer "channel_hash", limit: 8, null: false
|
10
|
+
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
|
11
|
+
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
|
11
12
|
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
|
12
13
|
end
|
13
14
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateCompactChannel < ActiveRecord::Migration[7.2]
|
4
|
+
def change
|
5
|
+
change_column :solid_cable_messages, :channel, :binary, limit: 1024, null: false
|
6
|
+
add_column :solid_cable_messages, :channel_hash, :integer, limit: 8, if_not_exists: true
|
7
|
+
add_index :solid_cable_messages, :channel_hash, if_not_exists: true
|
8
|
+
change_column :solid_cable_messages, :payload, :binary, limit: 536_870_912, null: false
|
9
|
+
|
10
|
+
SolidCable::Message.find_each do |msg|
|
11
|
+
msg.update(channel_hash: SolidCable::Message.channel_hash_for(msg.channel))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
class SolidCable::UpdateGenerator < Rails::Generators::Base
|
7
|
+
include ActiveRecord::Generators::Migration
|
8
|
+
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
10
|
+
|
11
|
+
def copy_files
|
12
|
+
migration_template "db/migrate/create_compact_channel.rb",
|
13
|
+
"db/cable_migrate/create_compact_channel.rb"
|
14
|
+
end
|
15
|
+
end
|
data/lib/solid_cable/version.rb
CHANGED
data/lib/solid_cable.rb
CHANGED
@@ -11,7 +11,7 @@ module SolidCable
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def silence_polling?
|
14
|
-
|
14
|
+
cable_config.silence_polling != false
|
15
15
|
end
|
16
16
|
|
17
17
|
def polling_interval
|
@@ -26,6 +26,27 @@ module SolidCable
|
|
26
26
|
cable_config.autotrim != false
|
27
27
|
end
|
28
28
|
|
29
|
+
def trim_batch_size
|
30
|
+
if (size = cable_config.trim_batch_size.to_i) < 1
|
31
|
+
100
|
32
|
+
else
|
33
|
+
size
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def use_skip_locked
|
38
|
+
cable_config.use_skip_locked != false
|
39
|
+
end
|
40
|
+
|
41
|
+
# For every write that we do, we attempt to delete trim_chance times as
|
42
|
+
# many records. This ensures there is downward pressure on the cache size
|
43
|
+
# while there is valid data to delete. Read this as 'every time the trim job
|
44
|
+
# runs theres a trim_multiplier chance this trims'. Adjust number to make it
|
45
|
+
# more or less likely to trim.
|
46
|
+
def trim_chance
|
47
|
+
10
|
48
|
+
end
|
49
|
+
|
29
50
|
private
|
30
51
|
|
31
52
|
def cable_config
|
@@ -3,6 +3,10 @@
|
|
3
3
|
desc "Copy over the schema and set cable adapter for Solid Cable"
|
4
4
|
namespace :solid_cable do
|
5
5
|
task :install do
|
6
|
-
Rails::Command.invoke :generate, ["solid_cable:install"]
|
6
|
+
Rails::Command.invoke :generate, [ "solid_cable:install" ]
|
7
|
+
end
|
8
|
+
|
9
|
+
task :update do
|
10
|
+
Rails::Command.invoke :generate, [ "solid_cable:update" ]
|
7
11
|
end
|
8
12
|
end
|
metadata
CHANGED
@@ -1,17 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_cable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Pezza
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activejob
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: actioncable
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '7.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '7.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: railties
|
15
57
|
requirement: !ruby/object:Gem::Requirement
|
16
58
|
requirements:
|
17
59
|
- - ">="
|
@@ -42,6 +84,9 @@ files:
|
|
42
84
|
- lib/generators/solid_cable/install/install_generator.rb
|
43
85
|
- lib/generators/solid_cable/install/templates/config/cable.yml
|
44
86
|
- lib/generators/solid_cable/install/templates/db/cable_schema.rb
|
87
|
+
- lib/generators/solid_cable/update/USAGE
|
88
|
+
- lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb
|
89
|
+
- lib/generators/solid_cable/update/update_generator.rb
|
45
90
|
- lib/solid_cable.rb
|
46
91
|
- lib/solid_cable/engine.rb
|
47
92
|
- lib/solid_cable/railtie.rb
|
@@ -52,7 +97,7 @@ licenses:
|
|
52
97
|
- MIT
|
53
98
|
metadata:
|
54
99
|
homepage_uri: http://github.com/npezza93/solid_cable
|
55
|
-
source_code_uri: http://github.com/
|
100
|
+
source_code_uri: http://github.com/rails/solid_cable
|
56
101
|
rubygems_mfa_required: 'true'
|
57
102
|
post_install_message:
|
58
103
|
rdoc_options: []
|