cable_ready 5.0.0.pre9 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +119 -100
  4. data/README.md +12 -15
  5. data/app/assets/javascripts/cable_ready.js +465 -155
  6. data/app/assets/javascripts/cable_ready.umd.js +449 -169
  7. data/app/channels/cable_ready/stream.rb +7 -5
  8. data/app/helpers/cable_ready/view_helper.rb +58 -0
  9. data/app/jobs/cable_ready/broadcast_job.rb +15 -0
  10. data/app/models/concerns/cable_ready/updatable/collection_updatable_callbacks.rb +2 -0
  11. data/app/models/concerns/cable_ready/updatable/collections_registry.rb +31 -5
  12. data/app/models/concerns/cable_ready/updatable/model_updatable_callbacks.rb +9 -4
  13. data/app/models/concerns/cable_ready/updatable.rb +107 -39
  14. data/app/models/concerns/extend_has_many.rb +2 -0
  15. data/cable_ready.gemspec +4 -6
  16. data/lib/cable_ready/broadcaster.rb +2 -0
  17. data/lib/cable_ready/cable_car.rb +2 -0
  18. data/lib/cable_ready/channel.rb +12 -4
  19. data/lib/cable_ready/channels.rb +3 -1
  20. data/lib/cable_ready/config.rb +17 -2
  21. data/lib/cable_ready/engine.rb +33 -14
  22. data/lib/cable_ready/identifiable.rb +23 -5
  23. data/lib/cable_ready/importmap.rb +3 -1
  24. data/lib/cable_ready/installer.rb +224 -0
  25. data/lib/cable_ready/operation_builder.rb +1 -1
  26. data/lib/cable_ready/sanity_checker.rb +1 -31
  27. data/lib/cable_ready/updatable/memory_cache_debounce_adapter.rb +22 -0
  28. data/lib/cable_ready/version.rb +1 -1
  29. data/lib/cable_ready.rb +5 -8
  30. data/lib/cable_ready_helper.rb +13 -0
  31. data/lib/generators/cable_ready/channel_generator.rb +51 -12
  32. data/lib/generators/cable_ready/templates/config/initializers/cable_ready.rb +15 -6
  33. data/lib/install/action_cable.rb +144 -0
  34. data/lib/install/broadcaster.rb +109 -0
  35. data/lib/install/bundle.rb +54 -0
  36. data/lib/install/compression.rb +51 -0
  37. data/lib/install/config.rb +39 -0
  38. data/lib/install/development.rb +34 -0
  39. data/lib/install/esbuild.rb +101 -0
  40. data/lib/install/importmap.rb +96 -0
  41. data/lib/install/initializers.rb +15 -0
  42. data/lib/install/mrujs.rb +121 -0
  43. data/lib/install/npm_packages.rb +13 -0
  44. data/lib/install/shakapacker.rb +65 -0
  45. data/lib/install/spring.rb +54 -0
  46. data/lib/install/updatable.rb +34 -0
  47. data/lib/install/vite.rb +66 -0
  48. data/lib/install/webpacker.rb +93 -0
  49. data/lib/install/yarn.rb +56 -0
  50. data/lib/tasks/cable_ready/cable_ready.rake +247 -0
  51. data/package.json +36 -22
  52. data/{rollup.config.js → rollup.config.mjs} +7 -25
  53. data/web-test-runner.config.mjs +12 -0
  54. data/yarn.lock +3058 -398
  55. metadata +39 -167
  56. data/IMPLEMENTATION.md +0 -93
  57. data/LATEST +0 -1
  58. data/app/assets/javascripts/cable_ready.min.js +0 -2
  59. data/app/assets/javascripts/cable_ready.min.js.map +0 -1
  60. data/app/assets/javascripts/cable_ready.umd.min.js +0 -2
  61. data/app/assets/javascripts/cable_ready.umd.min.js.map +0 -1
  62. data/app/helpers/cable_ready_helper.rb +0 -26
  63. data/app/jobs/cable_ready_broadcast_job.rb +0 -14
  64. data/lib/generators/cable_ready/helpers_generator.rb +0 -43
  65. data/lib/generators/cable_ready/initializer_generator.rb +0 -14
  66. data/test/dummy/app/channels/application_cable/channel.rb +0 -4
  67. data/test/dummy/app/channels/application_cable/connection.rb +0 -4
  68. data/test/dummy/app/controllers/application_controller.rb +0 -2
  69. data/test/dummy/app/helpers/application_helper.rb +0 -2
  70. data/test/dummy/app/jobs/application_job.rb +0 -7
  71. data/test/dummy/app/mailers/application_mailer.rb +0 -4
  72. data/test/dummy/app/models/application_record.rb +0 -3
  73. data/test/dummy/app/models/dugong.rb +0 -4
  74. data/test/dummy/app/models/global_idable_entity.rb +0 -16
  75. data/test/dummy/app/models/post.rb +0 -4
  76. data/test/dummy/app/models/section.rb +0 -6
  77. data/test/dummy/app/models/team.rb +0 -6
  78. data/test/dummy/app/models/topic.rb +0 -4
  79. data/test/dummy/app/models/user.rb +0 -7
  80. data/test/dummy/config/application.rb +0 -22
  81. data/test/dummy/config/boot.rb +0 -5
  82. data/test/dummy/config/environment.rb +0 -5
  83. data/test/dummy/config/environments/development.rb +0 -76
  84. data/test/dummy/config/environments/production.rb +0 -120
  85. data/test/dummy/config/environments/test.rb +0 -59
  86. data/test/dummy/config/initializers/application_controller_renderer.rb +0 -8
  87. data/test/dummy/config/initializers/assets.rb +0 -12
  88. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -8
  89. data/test/dummy/config/initializers/cable_ready.rb +0 -18
  90. data/test/dummy/config/initializers/content_security_policy.rb +0 -28
  91. data/test/dummy/config/initializers/cookies_serializer.rb +0 -5
  92. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -6
  93. data/test/dummy/config/initializers/inflections.rb +0 -16
  94. data/test/dummy/config/initializers/mime_types.rb +0 -4
  95. data/test/dummy/config/initializers/permissions_policy.rb +0 -11
  96. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  97. data/test/dummy/config/puma.rb +0 -43
  98. data/test/dummy/config/routes.rb +0 -3
  99. data/test/dummy/db/migrate/20210902154139_create_users.rb +0 -9
  100. data/test/dummy/db/migrate/20210902154153_create_posts.rb +0 -10
  101. data/test/dummy/db/migrate/20210904081930_create_topics.rb +0 -9
  102. data/test/dummy/db/migrate/20210904093607_create_sections.rb +0 -9
  103. data/test/dummy/db/migrate/20210913191735_create_teams.rb +0 -8
  104. data/test/dummy/db/migrate/20210913191759_add_team_reference_to_users.rb +0 -5
  105. data/test/dummy/db/migrate/20220329222959_create_dugongs.rb +0 -8
  106. data/test/dummy/db/migrate/20220329230221_create_active_storage_tables.active_storage.rb +0 -36
  107. data/test/dummy/db/schema.rb +0 -84
  108. data/test/dummy/test/models/dugong_test.rb +0 -7
  109. data/test/dummy/test/models/post_test.rb +0 -7
  110. data/test/dummy/test/models/section_test.rb +0 -7
  111. data/test/dummy/test/models/team_test.rb +0 -7
  112. data/test/dummy/test/models/topic_test.rb +0 -7
  113. data/test/dummy/test/models/user_test.rb +0 -7
  114. data/test/lib/cable_ready/cable_car_test.rb +0 -50
  115. data/test/lib/cable_ready/compoundable_test.rb +0 -26
  116. data/test/lib/cable_ready/helper_test.rb +0 -25
  117. data/test/lib/cable_ready/identifiable_test.rb +0 -69
  118. data/test/lib/cable_ready/operation_builder_test.rb +0 -189
  119. data/test/lib/cable_ready/updatable_test.rb +0 -135
  120. data/test/lib/generators/cable_ready/channel_generator_test.rb +0 -157
  121. data/test/support/generator_test_helpers.rb +0 -28
  122. data/test/test_helper.rb +0 -18
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CableReady
4
- class Stream < ActionCable::Channel::Base
5
- include CableReady::StreamIdentifier
4
+ if defined?(ActionCable)
5
+ class Stream < ActionCable::Channel::Base
6
+ include CableReady::StreamIdentifier
6
7
 
7
- def subscribed
8
- locator = verified_stream_identifier(params[:identifier])
9
- locator.present? ? stream_from(locator) : reject
8
+ def subscribed
9
+ locator = verified_stream_identifier(params[:identifier])
10
+ locator.present? ? stream_from(locator) : reject
11
+ end
10
12
  end
11
13
  end
12
14
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CableReady
4
+ module ViewHelper
5
+ include CableReady::Compoundable
6
+ include CableReady::StreamIdentifier
7
+
8
+ def stream_from(...)
9
+ warn "DEPRECATED: please use `cable_ready_stream_from` instead. The `stream_from` view helper will be removed from a future version of CableReady 5"
10
+
11
+ cable_ready_stream_from(...)
12
+ end
13
+
14
+ def updates_for(...)
15
+ warn "DEPRECATED: please use `cable_ready_updates_for` instead. The `updates_for` view helper will be removed from a future version of CableReady 5"
16
+
17
+ cable_ready_updates_for(...)
18
+ end
19
+
20
+ def updates_for_if(...)
21
+ warn "DEPRECATED: please use `cable_ready_updates_for_if` instead. The `updates_for_if` view helper will be removed from a future version of CableReady 5"
22
+
23
+ cable_ready_updates_for_if(...)
24
+ end
25
+
26
+ def cable_ready_stream_from(*keys, html_options: {})
27
+ tag.cable_ready_stream_from(**build_options(*keys, html_options))
28
+ end
29
+
30
+ def cable_ready_updates_for(*keys, url: nil, debounce: nil, only: nil, ignore_inner_updates: false, html_options: {}, &block)
31
+ options = build_options(*keys, html_options)
32
+ options[:url] = url if url
33
+ options[:debounce] = debounce if debounce
34
+ options[:only] = only if only
35
+ options[:"ignore-inner-updates"] = "" if ignore_inner_updates
36
+ tag.cable_ready_updates_for(**options) { capture(&block) }
37
+ end
38
+
39
+ def cable_ready_updates_for_if(condition, *keys, **options, &block)
40
+ if condition
41
+ cable_ready_updates_for(*keys, **options, &block)
42
+ else
43
+ capture(&block)
44
+ end
45
+ end
46
+
47
+ def cable_car
48
+ CableReady::CableCar.instance
49
+ end
50
+
51
+ private
52
+
53
+ def build_options(*keys, html_options)
54
+ keys.select!(&:itself)
55
+ {identifier: signed_stream_identifier(compound(keys))}.merge(html_options)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(ActiveJob::Base)
4
+ class CableReady::BroadcastJob < ActiveJob::Base
5
+ include CableReady::Broadcaster
6
+
7
+ def perform(identifier:, operations:, model: nil)
8
+ if model.present?
9
+ cable_ready[identifier.safe_constantize].apply!(operations).broadcast_to(model)
10
+ else
11
+ cable_ready[identifier].apply!(operations).broadcast
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CableReady
2
4
  module Updatable
3
5
  class CollectionUpdatableCallbacks
@@ -1,5 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CableReady
2
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
+
3
17
  class CollectionsRegistry
4
18
  def initialize
5
19
  @registered_collections = []
@@ -10,23 +24,35 @@ module CableReady
10
24
  end
11
25
 
12
26
  def broadcast_for!(model, operation)
13
- @registered_collections.select { |c| c[:options][:on].include?(operation) }
27
+ @registered_collections.select { |c| c.options[:on].include?(operation) }
14
28
  .each do |collection|
15
29
  resource = find_resource_for_update(collection, model)
16
30
  next if resource.nil?
17
31
 
18
- 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)
19
33
  end
20
34
  end
21
35
 
22
36
  private
23
37
 
24
38
  def find_resource_for_update(collection, model)
25
- 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
26
52
 
27
53
  resource = model
28
- resource = resource.send(collection[:through_association].underscore) if collection[:through_association]
29
- 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)
30
56
  end
31
57
  end
32
58
  end
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CableReady
2
4
  module Updatable
3
5
  class ModelUpdatableCallbacks
4
- 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)
5
7
  @operation = operation
6
8
  @enabled_operations = enabled_operations
9
+ @debounce = debounce
7
10
  end
8
11
 
9
12
  def after_commit(model)
@@ -15,13 +18,15 @@ module CableReady
15
18
  private
16
19
 
17
20
  def broadcast_create(model)
18
- model.class.send(:broadcast_updates, model.class, {})
21
+ model.class.send(:broadcast_updates, model.class, {debounce: @debounce})
19
22
  end
20
23
  alias_method :broadcast_destroy, :broadcast_create
21
24
 
22
25
  def broadcast_update(model)
23
- model.class.send(:broadcast_updates, model.class, {})
24
- model.class.send(:broadcast_updates, model.to_global_id, model.respond_to?(:previous_changes) ? {changed: model.previous_changes.keys} : {})
26
+ changeset = model.respond_to?(:previous_changes) ? {changed: model.previous_changes.keys} : {}
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)
25
30
  end
26
31
  end
27
32
  end
@@ -1,32 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/concern"
4
+
3
5
  module CableReady
4
6
  module Updatable
5
7
  extend ::ActiveSupport::Concern
6
8
 
9
+ mattr_accessor :debounce_adapter, default: MemoryCacheDebounceAdapter.instance
10
+
7
11
  included do |base|
8
- if base < ActiveRecord::Base
12
+ if defined?(ActiveRecord) && base < ActiveRecord::Base
9
13
  include ExtendHasMany
10
14
 
11
15
  after_commit CollectionUpdatableCallbacks.new(:create), on: :create
12
16
  after_commit CollectionUpdatableCallbacks.new(:update), on: :update
13
17
  after_commit CollectionUpdatableCallbacks.new(:destroy), on: :destroy
14
18
 
15
- def self.enable_updates(*options)
19
+ def self.enable_updates(...)
20
+ warn "DEPRECATED: please use `enable_cable_ready_updates` instead. The `enable_updates` class method will be removed from a future version of CableReady 5"
21
+
22
+ enable_cable_ready_updates(...)
23
+ end
24
+
25
+ def self.skip_updates(...)
26
+ warn "DEPRECATED: please use `skip_cable_ready_updates` instead. The `skip_updates` class method will be removed from a future version of CableReady 5"
27
+
28
+ skip_cable_ready_updates(...)
29
+ end
30
+
31
+ def self.enable_cable_ready_updates(*options)
16
32
  options = options.extract_options!
17
33
  options = {
18
34
  on: [:create, :update, :destroy],
19
- if: -> { true }
35
+ if: -> { true },
36
+ debounce: CableReady.config.updatable_debounce_time
20
37
  }.merge(options)
21
38
 
22
39
  enabled_operations = Array(options[:on])
23
40
 
24
- after_commit(ModelUpdatableCallbacks.new(:create, enabled_operations), {on: :create, if: options[:if]})
25
- after_commit(ModelUpdatableCallbacks.new(:update, enabled_operations), {on: :update, if: options[:if]})
26
- 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]})
27
44
  end
28
45
 
29
- def self.skip_updates
46
+ def self.skip_cable_ready_updates
30
47
  skip_updates_classes.push(self)
31
48
  yield
32
49
  ensure
@@ -38,19 +55,57 @@ module CableReady
38
55
  private
39
56
 
40
57
  module ClassMethods
58
+ include Compoundable
59
+
41
60
  def has_many(name, scope = nil, **options, &extension)
42
- option = options.delete(:enable_updates)
61
+ option = if options.has_key?(:enable_updates)
62
+ warn "DEPRECATED: please use `enable_cable_ready_updates` instead. The `enable_updates` option will be removed from a future version of CableReady 5"
63
+ options.delete(:enable_updates)
64
+ else
65
+ options.delete(:enable_cable_ready_updates)
66
+ end
67
+
68
+ descendants = options.delete(:descendants)
69
+ debounce_time = options.delete(:debounce)
70
+
71
+ broadcast = option.present?
72
+ result = super
73
+ enrich_association_with_updates(name, option, descendants, debounce: debounce_time) if broadcast
74
+ result
75
+ end
76
+
77
+ def has_one(name, scope = nil, **options, &extension)
78
+ option = if options.has_key?(:enable_updates)
79
+ warn "DEPRECATED: please use `enable_cable_ready_updates` instead. The `enable_updates` option will be removed from a future version of CableReady 5"
80
+ options.delete(:enable_updates)
81
+ else
82
+ options.delete(:enable_cable_ready_updates)
83
+ end
84
+
85
+ descendants = options.delete(:descendants)
86
+ debounce_time = options.delete(:debounce)
87
+
43
88
  broadcast = option.present?
44
89
  result = super
45
- enrich_association_with_updates(name, option) if broadcast
90
+ enrich_association_with_updates(name, option, descendants, debounce: debounce_time) if broadcast
46
91
  result
47
92
  end
48
93
 
49
94
  def has_many_attached(name, **options)
50
- option = options.delete(:enable_updates)
95
+ raise("ActiveStorage must be enabled to use has_many_attached") unless defined?(ActiveStorage)
96
+
97
+ option = if options.has_key?(:enable_updates)
98
+ warn "DEPRECATED: please use `enable_cable_ready_updates` instead. The `enable_updates` option will be removed from a future version of CableReady 5"
99
+ options.delete(:enable_updates)
100
+ else
101
+ options.delete(:enable_cable_ready_updates)
102
+ end
103
+
104
+ debounce_time = options.delete(:debounce)
105
+
51
106
  broadcast = option.present?
52
107
  result = super
53
- enrich_attachments_with_updates(name, option) if broadcast
108
+ enrich_attachments_with_updates(name, option, debounce: debounce_time) if broadcast
54
109
  result
55
110
  end
56
111
 
@@ -58,49 +113,45 @@ module CableReady
58
113
  @cable_ready_collections ||= CollectionsRegistry.new
59
114
  end
60
115
 
61
- def cable_ready_update_collection(resource, name, model)
116
+ def cable_ready_update_collection(resource, name, model, debounce: CableReady.config.updatable_debounce_time)
62
117
  identifier = resource.to_global_id.to_s + ":" + name.to_s
63
- 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)
64
122
  end
65
123
 
66
- def enrich_association_with_updates(name, option)
124
+ def enrich_association_with_updates(name, option, descendants = nil, debounce: CableReady.config.updatable_debounce_time)
67
125
  reflection = reflect_on_association(name)
68
126
 
69
- inverse_of = reflection.inverse_of&.name&.to_s
70
- through_association = nil
71
-
72
- if reflection.through_reflection?
73
- inverse_of = reflection.through_reflection.inverse_of&.name&.to_s
74
- through_association = reflection.through_reflection.name.to_s.singularize
75
- end
76
-
77
127
  options = build_options(option)
78
128
 
79
- reflection.klass.send(:include, CableReady::Updatable) unless reflection.klass.respond_to?(:cable_ready_collections)
80
-
81
- reflection.klass.cable_ready_collections.register({
82
- klass: self,
83
- foreign_key: reflection.foreign_key,
84
- name: name,
85
- inverse_association: inverse_of,
86
- through_association: through_association,
87
- options: options
88
- })
129
+ [reflection.klass, *descendants&.map(&:to_s)&.map(&:constantize)].each do |klass|
130
+ klass.send(:include, CableReady::Updatable) unless klass.respond_to?(:cable_ready_collections)
131
+ klass.cable_ready_collections.register(Collection.new(
132
+ klass: self,
133
+ name: name,
134
+ options: options,
135
+ reflection: reflection,
136
+ debounce_time: debounce
137
+ ))
138
+ end
89
139
  end
90
140
 
91
- def enrich_attachments_with_updates(name, option)
141
+ def enrich_attachments_with_updates(name, option, debounce: CableReady.config.updatable_debounce_time)
92
142
  options = build_options(option)
93
143
 
94
144
  ActiveStorage::Attachment.send(:include, CableReady::Updatable) unless ActiveStorage::Attachment.respond_to?(:cable_ready_collections)
95
145
 
96
- ActiveStorage::Attachment.cable_ready_collections.register({
146
+ ActiveStorage::Attachment.cable_ready_collections.register(Collection.new(
97
147
  klass: self,
98
148
  foreign_key: "record_id",
99
149
  name: name,
100
150
  inverse_association: "record",
101
151
  through_association: nil,
102
- options: options
103
- })
152
+ options: options,
153
+ debounce_time: debounce
154
+ ))
104
155
  end
105
156
 
106
157
  def build_options(option)
@@ -111,7 +162,7 @@ module CableReady
111
162
 
112
163
  case option
113
164
  when TrueClass
114
- # proceed!
165
+ # proceed!
115
166
  when FalseClass
116
167
  options[:on] = []
117
168
  when Array
@@ -124,7 +175,7 @@ module CableReady
124
175
  when Proc
125
176
  options[:if] = option
126
177
  else
127
- raise ArgumentError, "Invalid enable_updates option #{option}"
178
+ raise ArgumentError, "Invalid enable_cable_ready_updates option #{option}"
128
179
  end
129
180
 
130
181
  options
@@ -132,7 +183,24 @@ module CableReady
132
183
 
133
184
  def broadcast_updates(model_class, options)
134
185
  return if skip_updates_classes.any? { |klass| klass >= self }
135
- ActionCable.server.broadcast(model_class, options)
186
+ raise("ActionCable must be enabled to use Updatable") unless defined?(ActionCable)
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
136
204
  end
137
205
 
138
206
  def skip_updates_classes
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/concern"
4
+
3
5
  module ExtendHasMany
4
6
  extend ::ActiveSupport::Concern
5
7
 
data/cable_ready.gemspec CHANGED
@@ -12,31 +12,29 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = "Out-of-Band Server Triggered DOM Operations"
13
13
 
14
14
  gem.files = Dir[
15
- "lib/**/*.rb",
15
+ "lib/**/*.{rb,rake}",
16
16
  "app/**/*.rb",
17
17
  "app/assets/javascripts/*",
18
18
  "bin/*",
19
19
  "[A-Z]*"
20
20
  ]
21
21
 
22
- gem.test_files = Dir["test/**/*.rb"]
22
+ gem.required_ruby_version = ">= 2.7.0"
23
23
 
24
24
  rails_version = ">= 5.2"
25
- gem.add_dependency "actioncable", rails_version
25
+
26
26
  gem.add_dependency "actionpack", rails_version
27
27
  gem.add_dependency "actionview", rails_version
28
- gem.add_dependency "activerecord", rails_version
29
28
  gem.add_dependency "activesupport", rails_version
30
29
  gem.add_dependency "railties", rails_version
31
-
32
30
  gem.add_dependency "thread-local", ">= 1.1.0"
33
31
 
34
32
  gem.add_development_dependency "magic_frozen_string_literal"
35
33
  gem.add_development_dependency "mocha"
36
34
  gem.add_development_dependency "pry"
37
35
  gem.add_development_dependency "pry-nav"
38
- gem.add_development_dependency "rails", rails_version
39
36
  gem.add_development_dependency "rake"
40
37
  gem.add_development_dependency "sqlite3"
38
+ gem.add_development_dependency "standard", "1.19.1"
41
39
  gem.add_development_dependency "standardrb"
42
40
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/concern"
4
+
3
5
  module CableReady
4
6
  module Broadcaster
5
7
  include Identifiable
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "thread/local"
4
+
3
5
  module CableReady
4
6
  class CableCar < OperationBuilder
5
7
  extend Thread::Local
@@ -5,6 +5,7 @@ module CableReady
5
5
  attr_reader :identifier
6
6
 
7
7
  def broadcast(clear: true)
8
+ raise("Action Cable must be enabled to use broadcast") unless defined?(ActionCable)
8
9
  clients_received = ActionCable.server.broadcast identifier, {
9
10
  "cableReady" => true,
10
11
  "operations" => operations_payload,
@@ -15,6 +16,7 @@ module CableReady
15
16
  end
16
17
 
17
18
  def broadcast_to(model, clear: true)
19
+ raise("Action Cable must be enabled to use broadcast_to") unless defined?(ActionCable)
18
20
  clients_received = identifier.broadcast_to model, {
19
21
  "cableReady" => true,
20
22
  "operations" => operations_payload,
@@ -24,13 +26,19 @@ module CableReady
24
26
  clients_received
25
27
  end
26
28
 
27
- def broadcast_later(clear: true)
28
- CableReadyBroadcastJob.perform_later(identifier: identifier, operations: operations_payload)
29
+ def broadcast_later(clear: true, queue: nil)
30
+ raise("Action Cable must be enabled to use broadcast_later") unless defined?(ActionCable)
31
+ CableReady::BroadcastJob
32
+ .set(queue: queue ? queue.to_sym : CableReady.config.broadcast_job_queue)
33
+ .perform_later(identifier: identifier, operations: operations_payload)
29
34
  reset! if clear
30
35
  end
31
36
 
32
- def broadcast_later_to(model, clear: true)
33
- CableReadyBroadcastJob.perform_later(identifier: identifier.name, operations: operations_payload, model: model)
37
+ def broadcast_later_to(model, clear: true, queue: nil)
38
+ raise("Action Cable must be enabled to use broadcast_later_to") unless defined?(ActionCable)
39
+ CableReady::BroadcastJob
40
+ .set(queue: queue ? queue.to_sym : CableReady.config.broadcast_job_queue)
41
+ .perform_later(identifier: identifier.name, operations: operations_payload, model: model)
34
42
  reset! if clear
35
43
  end
36
44
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "thread/local"
4
+
3
5
  module CableReady
4
6
  # This class is a thread local singleton: CableReady::Channels.instance
5
7
  # SEE: https://github.com/socketry/thread-local/tree/master/guides/getting-started
@@ -13,7 +15,7 @@ module CableReady
13
15
 
14
16
  def [](*keys)
15
17
  keys.select!(&:itself)
16
- identifier = keys.many? || (keys.one? && keys.first.respond_to?(:to_global_id)) ? compound(keys) : keys.pop
18
+ identifier = (keys.many? || (keys.one? && keys.first.respond_to?(:to_global_id))) ? compound(keys) : keys.pop
17
19
  @channels[identifier] ||= CableReady::Channel.new(identifier)
18
20
  end
19
21
 
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "monitor"
4
+ require "observer"
5
+ require "singleton"
6
+
3
7
  module CableReady
4
8
  # This class is a process level singleton shared by all threads: CableReady::Config.instance
5
9
  class Config
@@ -7,14 +11,24 @@ module CableReady
7
11
  include Observable
8
12
  include Singleton
9
13
 
10
- attr_accessor :on_failed_sanity_checks, :on_new_version_available
14
+ attr_accessor :on_failed_sanity_checks, :broadcast_job_queue, :precompile_assets, :updatable_debounce_time
11
15
  attr_writer :verifier_key
12
16
 
17
+ def on_new_version_available
18
+ warn "NOTICE: The `config.on_new_version_available` option has been removed from the CableReady initializer. You can safely remove this option from your initializer."
19
+ end
20
+
21
+ def on_new_version_available=(_)
22
+ warn "NOTICE: The `config.on_new_version_available` option has been removed from the CableReady initializer. You can safely remove this option from your initializer."
23
+ end
24
+
13
25
  def initialize
14
26
  super
15
27
  @operation_names = Set.new(default_operation_names)
16
28
  @on_failed_sanity_checks = :exit
17
- @on_new_version_available = :ignore
29
+ @broadcast_job_queue = :default
30
+ @precompile_assets = true
31
+ @updatable_debounce_time = 0.1.seconds
18
32
  end
19
33
 
20
34
  def observers
@@ -72,6 +86,7 @@ module CableReady
72
86
  set_storage_item
73
87
  set_style
74
88
  set_styles
89
+ set_title
75
90
  set_value
76
91
  text_content
77
92
  ]).freeze
@@ -1,32 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails/engine"
2
4
 
3
5
  module CableReady
4
6
  class Engine < Rails::Engine
7
+ # If you don't want to precompile CableReady's assets (eg. because you're using webpack),
8
+ # you can do this in an initializer:
9
+ #
10
+ # config.after_initialize do
11
+ # config.assets.precompile -= CableReady::Engine::PRECOMPILE_ASSETS
12
+ # end
13
+ PRECOMPILE_ASSETS = %w[
14
+ cable_ready.js
15
+ cable_ready.umd.js
16
+ ]
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
+
5
24
  initializer "cable_ready.sanity_check" do
6
25
  SanityChecker.check! unless Rails.env.production?
7
26
  end
8
27
 
28
+ initializer "cable_ready.mimetype" do
29
+ Mime::Type.register "text/vnd.cable-ready.json", :cable_ready
30
+ end
31
+
9
32
  initializer "cable_ready.renderer" do
10
33
  ActiveSupport.on_load(:action_controller) do
11
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
+
12
37
  response.content_type ||= Mime[:cable_ready]
13
- render json: operations.dispatch
38
+ response.headers["X-Cable-Ready-Version"] = CableReady::VERSION
39
+
40
+ render json: operations.respond_to?(:dispatch) ? operations.dispatch : operations
14
41
  end
15
42
 
16
- Mime::Type.register "application/vnd.cable-ready.json", :cable_ready
17
- end
18
- 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
19
46
 
20
- initializer "cable_ready.assets" do |app|
21
- if app.config.respond_to?(:assets)
22
- app.config.assets.precompile += %w[
23
- cable_ready.js
24
- cable_ready.min.js
25
- cable_ready.min.js.map
26
- cable_ready.umd.js
27
- cable_ready.umd.min.js
28
- cable_ready.umd.min.js.map
29
- ]
47
+ render json: operations.respond_to?(:dispatch) ? operations.dispatch : operations
48
+ end
30
49
  end
31
50
  end
32
51