cable_ready 5.0.0.pre10 → 5.0.0.rc1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7240cc96726bfeff504657e69096170f4971cd94357bb0fcf0431d14db26c953
4
- data.tar.gz: b32acb97a059ee03bab8c8a973917d934e208a4aaf44015a2fe082fe5d42086f
3
+ metadata.gz: 727abefeeee7c2b5775ce2ca9b9e4521cf8172ba99f0a3ce4d6c0e4c49a77fae
4
+ data.tar.gz: f42905f8cc53e8effade6db00fa24d569c398b7f163e9a06aa19425c1bcdd888
5
5
  SHA512:
6
- metadata.gz: 8d641446ac2e71ce6cb901e38c37851886e10a0076c796429ca8476cfde68094a1e7ad096eba8c058d24b9acd9125f81f7b85c7cf0385e6d898931185629ccfb
7
- data.tar.gz: ac2d450ccd867b52c154053337f80d56af0221eee3a4ddd0e9597d93c8defb0a67b28efc34c14cf454241875303e43a01ecfe51bf13f2180e23e5e2df23e3a94
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.pre10)
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.2)
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/master/assets/cable-ready-logo-with-copy.svg" width="360" />
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/master/LICENSE">
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/Prettier-Standard/badge.svg">
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/StandardRB/badge.svg">
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/master/docs)
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.min.js', preload: true
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.min.js", "data-turbo-track": "reload" %>
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 github
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-pre10";
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 prettier-standard:check",
37
- format: "yarn run prettier-standard:format",
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.0",
57
+ "@web/test-runner": "^0.15.1",
57
58
  "prettier-standard": "^16.4.1",
58
- rollup: "^3.15.0",
59
- sinon: "^15.0.1"
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
- docFragment.querySelector(selector).innerHTML = content;
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-pre10";
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 prettier-standard:check",
24
- format: "yarn run prettier-standard:format",
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.0",
42
+ "@web/test-runner": "^0.15.1",
42
43
  "prettier-standard": "^16.4.1",
43
- rollup: "^3.15.0",
44
- sinon: "^15.0.1"
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
- docFragment.querySelector(selector).innerHTML = content;
1069
+ documentFragment.querySelector(selector).innerHTML = content;
1066
1070
  resolve();
1067
1071
  })))));
1068
1072
  }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  if defined?(ActiveJob::Base)
4
- class CableReadyBroadcastJob < ActiveJob::Base
4
+ class CableReady::BroadcastJob < ActiveJob::Base
5
5
  include CableReady::Broadcaster
6
6
 
7
7
  def perform(identifier:, operations:, model: nil)
@@ -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[:options][:on].include?(operation) }
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[:klass].cable_ready_update_collection(resource, collection[:name], model) if collection[:options][:if].call(resource)
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
- raise ArgumentError, "Could not find inverse_of for #{collection[:name]}" unless collection[:inverse_association]
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[:through_association].underscore) if collection[:through_association]
31
- resource.send(collection[:inverse_association].underscore)
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
- model.class.send(:broadcast_updates, model.class, changeset)
27
- model.class.send(:broadcast_updates, model.to_global_id, changeset)
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
- broadcast_updates(identifier, model.respond_to?(:previous_changes) ? {changed: model.previous_changes.keys} : {})
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
- inverse_association: inverse_of,
132
- through_association: through_association,
133
- options: options
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
- ActionCable.server.broadcast(model_class, options)
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
@@ -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
- CableReadyBroadcastJob
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
- CableReadyBroadcastJob
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
@@ -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
@@ -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
- render json: operations.dispatch
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
- Mime::Type.register "application/vnd.cable-ready.json", :cable_ready
34
- end
35
- end
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
- initializer "cable_ready.assets" do |app|
38
- if app.config.respond_to?(:assets) && CableReady.config.precompile_assets
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
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  pin "morphdom", to: "https://ga.jspm.io/npm:morphdom@2.6.1/dist/morphdom.js", preload: true
4
- pin "cable_ready", to: "cable_ready.min.js", preload: true
4
+ pin "cable_ready", to: "cable_ready.js", preload: true
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CableReady
4
- VERSION = "5.0.0.pre10"
4
+ VERSION = "5.0.0.rc1"
5
5
  end
data/lib/cable_ready.rb CHANGED
@@ -17,6 +17,8 @@ require "cable_ready/version"
17
17
  require "cable_ready_helper"
18
18
 
19
19
  module CableReady
20
+ autoload :MemoryCacheDebounceAdapter, "cable_ready/updatable/memory_cache_debounce_adapter"
21
+
20
22
  class << self
21
23
  def config
22
24
  CableReady::Config.instance
@@ -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