cable_ready 5.0.0.pre10 → 5.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +12 -12
- data/app/assets/javascripts/cable_ready.js +15 -11
- data/app/assets/javascripts/cable_ready.umd.js +15 -11
- data/app/jobs/{cable_ready_broadcast_job.rb → cable_ready/broadcast_job.rb} +1 -1
- data/app/models/concerns/cable_ready/updatable/collections_registry.rb +29 -5
- data/app/models/concerns/cable_ready/updatable/model_updatable_callbacks.rb +6 -4
- data/app/models/concerns/cable_ready/updatable.rb +49 -29
- data/lib/cable_ready/channel.rb +2 -2
- data/lib/cable_ready/config.rb +2 -1
- data/lib/cable_ready/engine.rb +20 -11
- data/lib/cable_ready/importmap.rb +1 -1
- data/lib/cable_ready/updatable/memory_cache_debounce_adapter.rb +22 -0
- data/lib/cable_ready/version.rb +1 -1
- data/lib/cable_ready.rb +2 -0
- data/lib/generators/cable_ready/templates/config/initializers/cable_ready.rb +5 -0
- data/lib/install/importmap.rb +1 -1
- data/lib/install/shakapacker.rb +4 -0
- data/lib/install/vite.rb +4 -0
- data/lib/install/webpacker.rb +12 -0
- data/lib/tasks/cable_ready/cable_ready.rake +6 -8
- data/package.json +13 -9
- data/rollup.config.mjs +0 -19
- data/yarn.lock +976 -122
- metadata +4 -8
- data/IMPLEMENTATION.md +0 -93
- data/app/assets/javascripts/cable_ready.min.js +0 -2
- data/app/assets/javascripts/cable_ready.min.js.map +0 -1
- data/app/assets/javascripts/cable_ready.umd.min.js +0 -2
- data/app/assets/javascripts/cable_ready.umd.min.js.map +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 727abefeeee7c2b5775ce2ca9b9e4521cf8172ba99f0a3ce4d6c0e4c49a77fae
|
4
|
+
data.tar.gz: f42905f8cc53e8effade6db00fa24d569c398b7f163e9a06aa19425c1bcdd888
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99bb047390e54d2143be5cfa9c7115203b0464d39aca3eab2d28486560043b26338f1d9f18b74507aa928afb87c7e08c93ecc475666730d6f1caae0eaf1486c2
|
7
|
+
data.tar.gz: 96e773ba9e75a9603a47850cd14784a47576fdb6b98b58d40c701deabc363342be1e626d55f845fa6186e57957f9cd52d9c67c79e72837d33a0d50be015630de
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cable_ready (5.0.0.
|
4
|
+
cable_ready (5.0.0.rc1)
|
5
5
|
actionpack (>= 5.2)
|
6
6
|
actionview (>= 5.2)
|
7
7
|
activesupport (>= 5.2)
|
@@ -121,7 +121,7 @@ GEM
|
|
121
121
|
pry-nav (1.0.0)
|
122
122
|
pry (>= 0.9.10, < 0.15)
|
123
123
|
racc (1.6.2)
|
124
|
-
rack (2.2.6.
|
124
|
+
rack (2.2.6.4)
|
125
125
|
rack-test (2.0.2)
|
126
126
|
rack (>= 1.3)
|
127
127
|
rails (6.1.7.2)
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
<p align="center">
|
2
|
-
<img src="https://raw.githubusercontent.com/stimulusreflex/cable_ready/
|
2
|
+
<img src="https://raw.githubusercontent.com/stimulusreflex/cable_ready/main/assets/cable-ready-logo-with-copy.svg" width="360" />
|
3
3
|
<h1 align="center">Welcome to CableReady 👋</h1>
|
4
4
|
<p align="center">
|
5
5
|
<a href="https://rubygems.org/gems/cable_ready">
|
@@ -11,16 +11,13 @@
|
|
11
11
|
<a href="https://www.npmjs.com/package/cable_ready">
|
12
12
|
<img alt="downloads" src="https://img.shields.io/npm/dm/cable_ready.svg?color=blue" target="_blank" />
|
13
13
|
</a>
|
14
|
-
<a href="https://github.com/stimulusreflex/cable_ready/blob/
|
14
|
+
<a href="https://github.com/stimulusreflex/cable_ready/blob/main/LICENSE">
|
15
15
|
<img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-brightgreen.svg" target="_blank" />
|
16
16
|
</a>
|
17
|
-
<a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/" target="_blank">
|
18
|
-
<img alt="Lines of Code" src="https://img.shields.io/badge/lines_of_code-1203-brightgreen.svg?style=flat" />
|
19
|
-
</a>
|
20
|
-
<br />
|
21
17
|
<a href="https://cableready.stimulusreflex.com" target="_blank">
|
22
18
|
<img alt="Documentation" src="https://img.shields.io/badge/documentation-yes-brightgreen.svg" />
|
23
19
|
</a>
|
20
|
+
<br />
|
24
21
|
<a href="#badge">
|
25
22
|
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
26
23
|
</a>
|
@@ -31,12 +28,15 @@
|
|
31
28
|
<img alt="JavaScript Code Style" src="https://img.shields.io/badge/JavaScript_Code_Style-prettier_standard-ff69b4.svg" />
|
32
29
|
</a>
|
33
30
|
<br />
|
34
|
-
<a target="_blank" rel="noopener noreferrer" href="https://github.com/stimulusreflex/cable_ready/workflows/
|
31
|
+
<a target="_blank" rel="noopener noreferrer" href="https://github.com/stimulusreflex/cable_ready/actions/workflows/prettier-standard.yml">
|
35
32
|
<img src="https://github.com/stimulusreflex/cable_ready/workflows/Prettier-Standard/badge.svg" alt="Prettier-Standard" style="max-width:100%;">
|
36
33
|
</a>
|
37
|
-
<a target="_blank" rel="noopener noreferrer" href="https://github.com/stimulusreflex/cable_ready/workflows/
|
34
|
+
<a target="_blank" rel="noopener noreferrer" href="https://github.com/stimulusreflex/cable_ready/actions/workflows/standardrb.yml">
|
38
35
|
<img src="https://github.com/stimulusreflex/cable_ready/workflows/StandardRB/badge.svg" alt="StandardRB" style="max-width:100%;">
|
39
36
|
</a>
|
37
|
+
<a target="_blank" rel="noopener noreferrer" href="https://github.com/stimulusreflex/cable_ready/actions/workflows/tests.yml">
|
38
|
+
<img src="https://github.com/stimulusreflex/cable_ready/workflows/Tests/badge.svg" alt="Tests" style="max-width:100%;">
|
39
|
+
</a>
|
40
40
|
</p>
|
41
41
|
</p>
|
42
42
|
<br />
|
@@ -49,7 +49,7 @@ to learn more about ActionCable before proceeding.
|
|
49
49
|
## 📚 Docs
|
50
50
|
|
51
51
|
- [Official Documentation](https://cableready.stimulusreflex.com)
|
52
|
-
- [Documentation Source Code](https://github.com/stimulusreflex/cable_ready/tree/
|
52
|
+
- [Documentation Source Code](https://github.com/stimulusreflex/cable_ready/tree/main/docs)
|
53
53
|
|
54
54
|
## 💙 Community
|
55
55
|
|
@@ -80,7 +80,7 @@ yarn add cable_ready
|
|
80
80
|
|
81
81
|
# ...
|
82
82
|
|
83
|
-
pin 'cable_ready', to: 'cable_ready.
|
83
|
+
pin 'cable_ready', to: 'cable_ready.js', preload: true
|
84
84
|
```
|
85
85
|
|
86
86
|
#### Rails Asset pipeline (Sprockets):
|
@@ -88,7 +88,7 @@ pin 'cable_ready', to: 'cable_ready.min.js', preload: true
|
|
88
88
|
```html+erb
|
89
89
|
<!-- app/views/layouts/application.html.erb -->
|
90
90
|
|
91
|
-
<%= javascript_include_tag "cable_ready.umd.
|
91
|
+
<%= javascript_include_tag "cable_ready.umd.js", "data-turbo-track": "reload" %>
|
92
92
|
```
|
93
93
|
|
94
94
|
Checkout the [documentation](https://cableready.stimulusreflex.com) to continue!
|
@@ -111,7 +111,7 @@ Please run `./bin/standardize` prior submitting pull requests.
|
|
111
111
|
1. Make sure that you run `yarn` and `bundle` to pick up the latest.
|
112
112
|
1. Bump version number at `lib/cable_ready/version.rb`. Pre-release versions use `.preN`
|
113
113
|
1. Run `rake build` and `yarn build`
|
114
|
-
1. Commit and push changes to
|
114
|
+
1. Commit and push changes to GitHub
|
115
115
|
1. Run `rake release`
|
116
116
|
1. Run `yarn publish --no-git-tag-version`
|
117
117
|
1. Yarn will prompt you for the new version. Pre-release versions use `-preN`
|
@@ -2,7 +2,7 @@ import morphdom from "morphdom";
|
|
2
2
|
|
3
3
|
var name = "cable_ready";
|
4
4
|
|
5
|
-
var version = "5.0.0-
|
5
|
+
var version = "5.0.0-rc1";
|
6
6
|
|
7
7
|
var description = "CableReady helps you create great real-time user experiences by making it simple to trigger client-side DOM changes from server-side Ruby.";
|
8
8
|
|
@@ -33,13 +33,14 @@ var umd = "./dist/cable_ready.umd.js";
|
|
33
33
|
var files = [ "dist/*", "javascript/*" ];
|
34
34
|
|
35
35
|
var scripts = {
|
36
|
-
lint: "yarn run
|
37
|
-
format: "yarn run prettier-standard
|
38
|
-
"prettier-standard:check": "yarn run prettier-standard --check ./javascript/**/*.js rollup.config.js",
|
39
|
-
"prettier-standard:format": "yarn run prettier-standard ./javascript/**/*.js rollup.config.js",
|
36
|
+
lint: "yarn run format --check",
|
37
|
+
format: "yarn run prettier-standard ./javascript/**/*.js rollup.config.mjs",
|
40
38
|
build: "yarn rollup -c",
|
41
39
|
watch: "yarn rollup -wc",
|
42
|
-
test: "web-test-runner javascript/test/**/*.test.js"
|
40
|
+
test: "web-test-runner javascript/test/**/*.test.js",
|
41
|
+
"docs:dev": "vitepress dev docs",
|
42
|
+
"docs:build": "vitepress build docs && cp ./docs/_redirects ./docs/.vitepress/dist",
|
43
|
+
"docs:preview": "vitepress preview docs"
|
43
44
|
};
|
44
45
|
|
45
46
|
var dependencies = {
|
@@ -53,10 +54,13 @@ var devDependencies = {
|
|
53
54
|
"@rollup/plugin-terser": "^0.4.0",
|
54
55
|
"@web/dev-server-esbuild": "^0.3.3",
|
55
56
|
"@web/dev-server-rollup": "^0.3.21",
|
56
|
-
"@web/test-runner": "^0.15.
|
57
|
+
"@web/test-runner": "^0.15.1",
|
57
58
|
"prettier-standard": "^16.4.1",
|
58
|
-
rollup: "^3.
|
59
|
-
sinon: "^15.0.
|
59
|
+
rollup: "^3.19.1",
|
60
|
+
sinon: "^15.0.2",
|
61
|
+
vite: "^4.1.4",
|
62
|
+
vitepress: "^1.0.0-alpha.56",
|
63
|
+
"vitepress-plugin-search": "^1.0.4-alpha.19"
|
60
64
|
};
|
61
65
|
|
62
66
|
var packageInfo = {
|
@@ -1057,7 +1061,7 @@ class UpdatesForElement extends SubscribingElement {
|
|
1057
1061
|
Log.cancel(this.lastUpdateTimestamp, "All elements filtered out");
|
1058
1062
|
return;
|
1059
1063
|
}
|
1060
|
-
// first updates-for element in the DOM *at any given moment* updates all of the others
|
1064
|
+
// first <cable-ready-updates-for> element in the DOM *at any given moment* updates all of the others
|
1061
1065
|
if (blocks[0].element !== this) {
|
1062
1066
|
Log.cancel(this.lastUpdateTimestamp, "Update already requested");
|
1063
1067
|
return;
|
@@ -1142,7 +1146,7 @@ class Block {
|
|
1142
1146
|
const selector = `turbo-frame#${frame.id}`;
|
1143
1147
|
const frameContent = frameTemplate.content.querySelector(selector);
|
1144
1148
|
const content = frameContent ? frameContent.innerHTML.trim() : "";
|
1145
|
-
|
1149
|
+
documentFragment.querySelector(selector).innerHTML = content;
|
1146
1150
|
resolve();
|
1147
1151
|
})))));
|
1148
1152
|
}
|
@@ -4,7 +4,7 @@
|
|
4
4
|
})(this, (function(exports, morphdom) {
|
5
5
|
"use strict";
|
6
6
|
var name = "cable_ready";
|
7
|
-
var version = "5.0.0-
|
7
|
+
var version = "5.0.0-rc1";
|
8
8
|
var description = "CableReady helps you create great real-time user experiences by making it simple to trigger client-side DOM changes from server-side Ruby.";
|
9
9
|
var keywords = [ "ruby", "rails", "websockets", "actioncable", "cable", "ssr", "stimulus_reflex", "client-side", "dom" ];
|
10
10
|
var homepage = "https://cableready.stimulusreflex.com";
|
@@ -20,13 +20,14 @@
|
|
20
20
|
var umd = "./dist/cable_ready.umd.js";
|
21
21
|
var files = [ "dist/*", "javascript/*" ];
|
22
22
|
var scripts = {
|
23
|
-
lint: "yarn run
|
24
|
-
format: "yarn run prettier-standard
|
25
|
-
"prettier-standard:check": "yarn run prettier-standard --check ./javascript/**/*.js rollup.config.js",
|
26
|
-
"prettier-standard:format": "yarn run prettier-standard ./javascript/**/*.js rollup.config.js",
|
23
|
+
lint: "yarn run format --check",
|
24
|
+
format: "yarn run prettier-standard ./javascript/**/*.js rollup.config.mjs",
|
27
25
|
build: "yarn rollup -c",
|
28
26
|
watch: "yarn rollup -wc",
|
29
|
-
test: "web-test-runner javascript/test/**/*.test.js"
|
27
|
+
test: "web-test-runner javascript/test/**/*.test.js",
|
28
|
+
"docs:dev": "vitepress dev docs",
|
29
|
+
"docs:build": "vitepress build docs && cp ./docs/_redirects ./docs/.vitepress/dist",
|
30
|
+
"docs:preview": "vitepress preview docs"
|
30
31
|
};
|
31
32
|
var dependencies = {
|
32
33
|
morphdom: "2.6.1"
|
@@ -38,10 +39,13 @@
|
|
38
39
|
"@rollup/plugin-terser": "^0.4.0",
|
39
40
|
"@web/dev-server-esbuild": "^0.3.3",
|
40
41
|
"@web/dev-server-rollup": "^0.3.21",
|
41
|
-
"@web/test-runner": "^0.15.
|
42
|
+
"@web/test-runner": "^0.15.1",
|
42
43
|
"prettier-standard": "^16.4.1",
|
43
|
-
rollup: "^3.
|
44
|
-
sinon: "^15.0.
|
44
|
+
rollup: "^3.19.1",
|
45
|
+
sinon: "^15.0.2",
|
46
|
+
vite: "^4.1.4",
|
47
|
+
vitepress: "^1.0.0-alpha.56",
|
48
|
+
"vitepress-plugin-search": "^1.0.4-alpha.19"
|
45
49
|
};
|
46
50
|
var packageInfo = {
|
47
51
|
name: name,
|
@@ -978,7 +982,7 @@
|
|
978
982
|
Log.cancel(this.lastUpdateTimestamp, "All elements filtered out");
|
979
983
|
return;
|
980
984
|
}
|
981
|
-
// first updates-for element in the DOM *at any given moment* updates all of the others
|
985
|
+
// first <cable-ready-updates-for> element in the DOM *at any given moment* updates all of the others
|
982
986
|
if (blocks[0].element !== this) {
|
983
987
|
Log.cancel(this.lastUpdateTimestamp, "Update already requested");
|
984
988
|
return;
|
@@ -1062,7 +1066,7 @@
|
|
1062
1066
|
const selector = `turbo-frame#${frame.id}`;
|
1063
1067
|
const frameContent = frameTemplate.content.querySelector(selector);
|
1064
1068
|
const content = frameContent ? frameContent.innerHTML.trim() : "";
|
1065
|
-
|
1069
|
+
documentFragment.querySelector(selector).innerHTML = content;
|
1066
1070
|
resolve();
|
1067
1071
|
})))));
|
1068
1072
|
}
|
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
module CableReady
|
4
4
|
module Updatable
|
5
|
+
Collection = Struct.new(
|
6
|
+
:klass,
|
7
|
+
:name,
|
8
|
+
:reflection,
|
9
|
+
:options,
|
10
|
+
:foreign_key,
|
11
|
+
:inverse_association,
|
12
|
+
:through_association,
|
13
|
+
:debounce_time,
|
14
|
+
keyword_init: true
|
15
|
+
)
|
16
|
+
|
5
17
|
class CollectionsRegistry
|
6
18
|
def initialize
|
7
19
|
@registered_collections = []
|
@@ -12,23 +24,35 @@ module CableReady
|
|
12
24
|
end
|
13
25
|
|
14
26
|
def broadcast_for!(model, operation)
|
15
|
-
@registered_collections.select { |c| c
|
27
|
+
@registered_collections.select { |c| c.options[:on].include?(operation) }
|
16
28
|
.each do |collection|
|
17
29
|
resource = find_resource_for_update(collection, model)
|
18
30
|
next if resource.nil?
|
19
31
|
|
20
|
-
collection
|
32
|
+
collection.klass.cable_ready_update_collection(resource, collection.name, model, debounce: collection.debounce_time) if collection.options[:if].call(resource)
|
21
33
|
end
|
22
34
|
end
|
23
35
|
|
24
36
|
private
|
25
37
|
|
26
38
|
def find_resource_for_update(collection, model)
|
27
|
-
|
39
|
+
collection.reflection ||= collection.klass.reflect_on_association(collection.name)
|
40
|
+
|
41
|
+
collection.foreign_key ||= collection.reflection&.foreign_key
|
42
|
+
|
43
|
+
# lazy load and store through and inverse associations
|
44
|
+
if collection.reflection&.through_reflection?
|
45
|
+
collection.inverse_association ||= collection.reflection&.through_reflection&.inverse_of&.name&.to_s
|
46
|
+
collection.through_association = collection.reflection&.through_reflection&.name&.to_s&.singularize
|
47
|
+
else
|
48
|
+
collection.inverse_association ||= collection.reflection&.inverse_of&.name&.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
raise ArgumentError, "Could not find inverse_of for #{collection.name}" unless collection.inverse_association
|
28
52
|
|
29
53
|
resource = model
|
30
|
-
resource = resource.send(collection
|
31
|
-
resource.send(collection
|
54
|
+
resource = resource.send(collection.through_association.underscore) if collection.through_association
|
55
|
+
resource.send(collection.inverse_association.underscore)
|
32
56
|
end
|
33
57
|
end
|
34
58
|
end
|
@@ -3,9 +3,10 @@
|
|
3
3
|
module CableReady
|
4
4
|
module Updatable
|
5
5
|
class ModelUpdatableCallbacks
|
6
|
-
def initialize(operation, enabled_operations = %i[create update destroy])
|
6
|
+
def initialize(operation, enabled_operations = %i[create update destroy], debounce: CableReady.config.updatable_debounce_time)
|
7
7
|
@operation = operation
|
8
8
|
@enabled_operations = enabled_operations
|
9
|
+
@debounce = debounce
|
9
10
|
end
|
10
11
|
|
11
12
|
def after_commit(model)
|
@@ -17,14 +18,15 @@ module CableReady
|
|
17
18
|
private
|
18
19
|
|
19
20
|
def broadcast_create(model)
|
20
|
-
model.class.send(:broadcast_updates, model.class, {})
|
21
|
+
model.class.send(:broadcast_updates, model.class, {debounce: @debounce})
|
21
22
|
end
|
22
23
|
alias_method :broadcast_destroy, :broadcast_create
|
23
24
|
|
24
25
|
def broadcast_update(model)
|
25
26
|
changeset = model.respond_to?(:previous_changes) ? {changed: model.previous_changes.keys} : {}
|
26
|
-
|
27
|
-
model.class.send(:broadcast_updates, model.
|
27
|
+
options = changeset.merge({debounce: @debounce})
|
28
|
+
model.class.send(:broadcast_updates, model.class, options.dup)
|
29
|
+
model.class.send(:broadcast_updates, model.to_global_id, options)
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -6,6 +6,8 @@ module CableReady
|
|
6
6
|
module Updatable
|
7
7
|
extend ::ActiveSupport::Concern
|
8
8
|
|
9
|
+
mattr_accessor :debounce_adapter, default: MemoryCacheDebounceAdapter.instance
|
10
|
+
|
9
11
|
included do |base|
|
10
12
|
if defined?(ActiveRecord) && base < ActiveRecord::Base
|
11
13
|
include ExtendHasMany
|
@@ -30,14 +32,15 @@ module CableReady
|
|
30
32
|
options = options.extract_options!
|
31
33
|
options = {
|
32
34
|
on: [:create, :update, :destroy],
|
33
|
-
if: -> { true }
|
35
|
+
if: -> { true },
|
36
|
+
debounce: CableReady.config.updatable_debounce_time
|
34
37
|
}.merge(options)
|
35
38
|
|
36
39
|
enabled_operations = Array(options[:on])
|
37
40
|
|
38
|
-
after_commit(ModelUpdatableCallbacks.new(:create, enabled_operations), {on: :create, if: options[:if]})
|
39
|
-
after_commit(ModelUpdatableCallbacks.new(:update, enabled_operations), {on: :update, if: options[:if]})
|
40
|
-
after_commit(ModelUpdatableCallbacks.new(:destroy, enabled_operations), {on: :destroy, if: options[:if]})
|
41
|
+
after_commit(ModelUpdatableCallbacks.new(:create, enabled_operations, debounce: options[:debounce]), {on: :create, if: options[:if]})
|
42
|
+
after_commit(ModelUpdatableCallbacks.new(:update, enabled_operations, debounce: options[:debounce]), {on: :update, if: options[:if]})
|
43
|
+
after_commit(ModelUpdatableCallbacks.new(:destroy, enabled_operations, debounce: options[:debounce]), {on: :destroy, if: options[:if]})
|
41
44
|
end
|
42
45
|
|
43
46
|
def self.skip_cable_ready_updates
|
@@ -52,6 +55,8 @@ module CableReady
|
|
52
55
|
private
|
53
56
|
|
54
57
|
module ClassMethods
|
58
|
+
include Compoundable
|
59
|
+
|
55
60
|
def has_many(name, scope = nil, **options, &extension)
|
56
61
|
option = if options.has_key?(:enable_updates)
|
57
62
|
warn "DEPRECATED: please use `enable_cable_ready_updates` instead. The `enable_updates` option will be removed from a future version of CableReady 5"
|
@@ -61,10 +66,11 @@ module CableReady
|
|
61
66
|
end
|
62
67
|
|
63
68
|
descendants = options.delete(:descendants)
|
69
|
+
debounce_time = options.delete(:debounce)
|
64
70
|
|
65
71
|
broadcast = option.present?
|
66
72
|
result = super
|
67
|
-
enrich_association_with_updates(name, option, descendants) if broadcast
|
73
|
+
enrich_association_with_updates(name, option, descendants, debounce: debounce_time) if broadcast
|
68
74
|
result
|
69
75
|
end
|
70
76
|
|
@@ -77,10 +83,11 @@ module CableReady
|
|
77
83
|
end
|
78
84
|
|
79
85
|
descendants = options.delete(:descendants)
|
86
|
+
debounce_time = options.delete(:debounce)
|
80
87
|
|
81
88
|
broadcast = option.present?
|
82
89
|
result = super
|
83
|
-
enrich_association_with_updates(name, option, descendants) if broadcast
|
90
|
+
enrich_association_with_updates(name, option, descendants, debounce: debounce_time) if broadcast
|
84
91
|
result
|
85
92
|
end
|
86
93
|
|
@@ -94,9 +101,11 @@ module CableReady
|
|
94
101
|
options.delete(:enable_cable_ready_updates)
|
95
102
|
end
|
96
103
|
|
104
|
+
debounce_time = options.delete(:debounce)
|
105
|
+
|
97
106
|
broadcast = option.present?
|
98
107
|
result = super
|
99
|
-
enrich_attachments_with_updates(name, option) if broadcast
|
108
|
+
enrich_attachments_with_updates(name, option, debounce: debounce_time) if broadcast
|
100
109
|
result
|
101
110
|
end
|
102
111
|
|
@@ -104,50 +113,45 @@ module CableReady
|
|
104
113
|
@cable_ready_collections ||= CollectionsRegistry.new
|
105
114
|
end
|
106
115
|
|
107
|
-
def cable_ready_update_collection(resource, name, model)
|
116
|
+
def cable_ready_update_collection(resource, name, model, debounce: CableReady.config.updatable_debounce_time)
|
108
117
|
identifier = resource.to_global_id.to_s + ":" + name.to_s
|
109
|
-
|
118
|
+
changeset = model.respond_to?(:previous_changes) ? {changed: model.previous_changes.keys} : {}
|
119
|
+
options = changeset.merge({debounce: debounce})
|
120
|
+
|
121
|
+
broadcast_updates(identifier, options)
|
110
122
|
end
|
111
123
|
|
112
|
-
def enrich_association_with_updates(name, option, descendants = nil)
|
124
|
+
def enrich_association_with_updates(name, option, descendants = nil, debounce: CableReady.config.updatable_debounce_time)
|
113
125
|
reflection = reflect_on_association(name)
|
114
126
|
|
115
|
-
inverse_of = reflection.inverse_of&.name&.to_s
|
116
|
-
through_association = nil
|
117
|
-
|
118
|
-
if reflection.through_reflection?
|
119
|
-
inverse_of = reflection.through_reflection.inverse_of&.name&.to_s
|
120
|
-
through_association = reflection.through_reflection.name.to_s.singularize
|
121
|
-
end
|
122
|
-
|
123
127
|
options = build_options(option)
|
124
128
|
|
125
129
|
[reflection.klass, *descendants&.map(&:to_s)&.map(&:constantize)].each do |klass|
|
126
130
|
klass.send(:include, CableReady::Updatable) unless klass.respond_to?(:cable_ready_collections)
|
127
|
-
klass.cable_ready_collections.register(
|
131
|
+
klass.cable_ready_collections.register(Collection.new(
|
128
132
|
klass: self,
|
129
|
-
foreign_key: reflection.foreign_key,
|
130
133
|
name: name,
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
134
|
+
options: options,
|
135
|
+
reflection: reflection,
|
136
|
+
debounce_time: debounce
|
137
|
+
))
|
135
138
|
end
|
136
139
|
end
|
137
140
|
|
138
|
-
def enrich_attachments_with_updates(name, option)
|
141
|
+
def enrich_attachments_with_updates(name, option, debounce: CableReady.config.updatable_debounce_time)
|
139
142
|
options = build_options(option)
|
140
143
|
|
141
144
|
ActiveStorage::Attachment.send(:include, CableReady::Updatable) unless ActiveStorage::Attachment.respond_to?(:cable_ready_collections)
|
142
145
|
|
143
|
-
ActiveStorage::Attachment.cable_ready_collections.register(
|
146
|
+
ActiveStorage::Attachment.cable_ready_collections.register(Collection.new(
|
144
147
|
klass: self,
|
145
148
|
foreign_key: "record_id",
|
146
149
|
name: name,
|
147
150
|
inverse_association: "record",
|
148
151
|
through_association: nil,
|
149
|
-
options: options
|
150
|
-
|
152
|
+
options: options,
|
153
|
+
debounce_time: debounce
|
154
|
+
))
|
151
155
|
end
|
152
156
|
|
153
157
|
def build_options(option)
|
@@ -180,7 +184,23 @@ module CableReady
|
|
180
184
|
def broadcast_updates(model_class, options)
|
181
185
|
return if skip_updates_classes.any? { |klass| klass >= self }
|
182
186
|
raise("ActionCable must be enabled to use Updatable") unless defined?(ActionCable)
|
183
|
-
|
187
|
+
|
188
|
+
debounce_time = options.delete(:debounce)
|
189
|
+
debounce_time ||= CableReady.config.updatable_debounce_time
|
190
|
+
|
191
|
+
if debounce_time.to_f > 0
|
192
|
+
key = compound([model_class, *options])
|
193
|
+
old_wait_until = CableReady::Updatable.debounce_adapter[key]
|
194
|
+
now = Time.now.to_f
|
195
|
+
|
196
|
+
if old_wait_until.nil? || old_wait_until < now
|
197
|
+
new_wait_until = now + debounce_time.to_f
|
198
|
+
CableReady::Updatable.debounce_adapter[key] = new_wait_until
|
199
|
+
ActionCable.server.broadcast(model_class, options)
|
200
|
+
end
|
201
|
+
else
|
202
|
+
ActionCable.server.broadcast(model_class, options)
|
203
|
+
end
|
184
204
|
end
|
185
205
|
|
186
206
|
def skip_updates_classes
|
data/lib/cable_ready/channel.rb
CHANGED
@@ -28,7 +28,7 @@ module CableReady
|
|
28
28
|
|
29
29
|
def broadcast_later(clear: true, queue: nil)
|
30
30
|
raise("Action Cable must be enabled to use broadcast_later") unless defined?(ActionCable)
|
31
|
-
|
31
|
+
CableReady::BroadcastJob
|
32
32
|
.set(queue: queue ? queue.to_sym : CableReady.config.broadcast_job_queue)
|
33
33
|
.perform_later(identifier: identifier, operations: operations_payload)
|
34
34
|
reset! if clear
|
@@ -36,7 +36,7 @@ module CableReady
|
|
36
36
|
|
37
37
|
def broadcast_later_to(model, clear: true, queue: nil)
|
38
38
|
raise("Action Cable must be enabled to use broadcast_later_to") unless defined?(ActionCable)
|
39
|
-
|
39
|
+
CableReady::BroadcastJob
|
40
40
|
.set(queue: queue ? queue.to_sym : CableReady.config.broadcast_job_queue)
|
41
41
|
.perform_later(identifier: identifier.name, operations: operations_payload, model: model)
|
42
42
|
reset! if clear
|
data/lib/cable_ready/config.rb
CHANGED
@@ -11,7 +11,7 @@ module CableReady
|
|
11
11
|
include Observable
|
12
12
|
include Singleton
|
13
13
|
|
14
|
-
attr_accessor :on_failed_sanity_checks, :broadcast_job_queue, :precompile_assets
|
14
|
+
attr_accessor :on_failed_sanity_checks, :broadcast_job_queue, :precompile_assets, :updatable_debounce_time
|
15
15
|
attr_writer :verifier_key
|
16
16
|
|
17
17
|
def on_new_version_available
|
@@ -28,6 +28,7 @@ module CableReady
|
|
28
28
|
@on_failed_sanity_checks = :exit
|
29
29
|
@broadcast_job_queue = :default
|
30
30
|
@precompile_assets = true
|
31
|
+
@updatable_debounce_time = 0.1.seconds
|
31
32
|
end
|
32
33
|
|
33
34
|
def observers
|
data/lib/cable_ready/engine.rb
CHANGED
@@ -12,31 +12,40 @@ module CableReady
|
|
12
12
|
# end
|
13
13
|
PRECOMPILE_ASSETS = %w[
|
14
14
|
cable_ready.js
|
15
|
-
cable_ready.min.js
|
16
|
-
cable_ready.min.js.map
|
17
15
|
cable_ready.umd.js
|
18
|
-
cable_ready.umd.min.js
|
19
|
-
cable_ready.umd.min.js.map
|
20
16
|
]
|
21
17
|
|
18
|
+
initializer "cable_ready.assets" do |app|
|
19
|
+
if app.config.respond_to?(:assets) && CableReady.config.precompile_assets
|
20
|
+
app.config.assets.precompile += PRECOMPILE_ASSETS
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
22
24
|
initializer "cable_ready.sanity_check" do
|
23
25
|
SanityChecker.check! unless Rails.env.production?
|
24
26
|
end
|
25
27
|
|
28
|
+
initializer "cable_ready.mimetype" do
|
29
|
+
Mime::Type.register "text/vnd.cable-ready.json", :cable_ready
|
30
|
+
end
|
31
|
+
|
26
32
|
initializer "cable_ready.renderer" do
|
27
33
|
ActiveSupport.on_load(:action_controller) do
|
28
34
|
ActionController::Renderers.add :operations do |operations, options|
|
35
|
+
warn "DEPRECATED: CableReady's `render operations:` call has been renamed to `render cable_ready:`. Please update your render call."
|
36
|
+
|
29
37
|
response.content_type ||= Mime[:cable_ready]
|
30
|
-
|
38
|
+
response.headers["X-Cable-Ready-Version"] = CableReady::VERSION
|
39
|
+
|
40
|
+
render json: operations.respond_to?(:dispatch) ? operations.dispatch : operations
|
31
41
|
end
|
32
42
|
|
33
|
-
|
34
|
-
|
35
|
-
|
43
|
+
ActionController::Renderers.add :cable_ready do |operations, options|
|
44
|
+
response.content_type ||= Mime[:cable_ready]
|
45
|
+
response.headers["X-Cable-Ready-Version"] = CableReady::VERSION
|
36
46
|
|
37
|
-
|
38
|
-
|
39
|
-
app.config.assets.precompile += PRECOMPILE_ASSETS
|
47
|
+
render json: operations.respond_to?(:dispatch) ? operations.dispatch : operations
|
48
|
+
end
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CableReady
|
2
|
+
module Updatable
|
3
|
+
class MemoryCacheDebounceAdapter
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
delegate_missing_to :@store
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
@store = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes, size: 8.megabytes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(key, value)
|
14
|
+
@store.write(key, value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key)
|
18
|
+
@store.read(key)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/cable_ready/version.rb
CHANGED
data/lib/cable_ready.rb
CHANGED
@@ -19,4 +19,9 @@ CableReady.configure do |config|
|
|
19
19
|
# Change the default Active Job queue used for broadcast_later and broadcast_later_to
|
20
20
|
#
|
21
21
|
# config.broadcast_job_queue = :default
|
22
|
+
|
23
|
+
# Specify a default debounce time for CableReady::Updatable callbacks
|
24
|
+
# Doing so is a best practice to avoid heavy ActionCable traffic
|
25
|
+
#
|
26
|
+
# config.updatable_debounce_time = 0.1.seconds
|
22
27
|
end
|