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,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "action_view"
4
+
3
5
  module CableReady
4
6
  module Identifiable
5
7
  def dom_id(record, prefix = nil)
@@ -9,9 +11,9 @@ module CableReady
9
11
 
10
12
  id = if record.respond_to?(:to_dom_id)
11
13
  record.to_dom_id
12
- elsif record.is_a?(ActiveRecord::Relation)
14
+ elsif ar_relation?(record)
13
15
  [prefix, record.model_name.plural].compact.join("_")
14
- elsif record.is_a?(ActiveRecord::Base)
16
+ elsif ar_base?(record)
15
17
  ActionView::RecordIdentifier.dom_id(record, prefix)
16
18
  else
17
19
  [prefix, record.to_s.strip].compact.join("_")
@@ -21,10 +23,26 @@ module CableReady
21
23
  end
22
24
 
23
25
  def identifiable?(obj)
24
- obj.respond_to?(:to_dom_selector) ||
26
+ (
27
+ obj.respond_to?(:to_dom_selector) ||
25
28
  obj.respond_to?(:to_dom_id) ||
26
- obj.is_a?(ActiveRecord::Relation) ||
27
- obj.is_a?(ActiveRecord::Base)
29
+ ar_relation?(obj) ||
30
+ ar_base?(obj)
31
+ )
32
+ end
33
+
34
+ private
35
+
36
+ def ar_relation?(obj)
37
+ return false if defined?(ActiveRecord).nil? || defined?(ActiveRecord::Relation).nil?
38
+
39
+ obj.is_a?(ActiveRecord::Relation)
40
+ end
41
+
42
+ def ar_base?(obj)
43
+ return false if defined?(ActiveRecord).nil? || defined?(ActiveRecord::Base).nil?
44
+
45
+ obj.is_a?(ActiveRecord::Base)
28
46
  end
29
47
  end
30
48
  end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  pin "morphdom", to: "https://ga.jspm.io/npm:morphdom@2.6.1/dist/morphdom.js", preload: true
2
- pin "cable_ready", to: "cable_ready.min.js", preload: true
4
+ pin "cable_ready", to: "cable_ready.js", preload: true
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cable_ready/version"
4
+
5
+ ### general utilities
6
+
7
+ def fetch(step_path, file)
8
+ relative_path = step_path + file
9
+ location = template_src + relative_path
10
+
11
+ Pathname.new(location)
12
+ end
13
+
14
+ def complete_step(step)
15
+ create_file "tmp/cable_ready_installer/#{step}", verbose: false
16
+ end
17
+
18
+ def create_or_append(path, *args, &block)
19
+ FileUtils.touch(path)
20
+ append_file(path, *args, &block)
21
+ end
22
+
23
+ def current_template
24
+ ENV["LOCATION"].split("/").last.gsub(".rb", "")
25
+ end
26
+
27
+ def pack_path_missing?
28
+ return false unless pack_path.nil?
29
+ halt "#{friendly_pack_path} is missing. You need a valid application pack file to proceed."
30
+ end
31
+
32
+ def halt(message)
33
+ say "❌ #{message}", :red
34
+ create_file "tmp/cable_ready_installer/halt", verbose: false
35
+ end
36
+
37
+ def backup(path, delete: false)
38
+ if !path.exist?
39
+ yield
40
+ return
41
+ end
42
+
43
+ backup_path = Pathname.new("#{path}.bak")
44
+ old_path = path.relative_path_from(Rails.root).to_s
45
+ filename = path.to_path.split("/").last
46
+
47
+ if backup_path.exist?
48
+ if backup_path.read == path.read
49
+ path.delete if delete
50
+ yield
51
+ return
52
+ end
53
+ backup_path.delete
54
+ end
55
+
56
+ copy_file(path, backup_path, verbose: false)
57
+ path.delete if delete
58
+
59
+ yield
60
+
61
+ if path.read != backup_path.read
62
+ create_or_append(backups_path, "#{old_path}\n", verbose: false)
63
+ end
64
+ say "📦 #{old_path} backed up as #{filename}.bak"
65
+ end
66
+
67
+ def add_gem(name)
68
+ create_or_append(add_gem_list, "#{name}\n", verbose: false)
69
+ say "☑️ Added #{name} to the Gemfile"
70
+ end
71
+
72
+ def remove_gem(name)
73
+ create_or_append(remove_gem_list, "#{name}\n", verbose: false)
74
+ say "❎ Removed #{name} from Gemfile"
75
+ end
76
+
77
+ def add_package(name)
78
+ create_or_append(package_list, "#{name}\n", verbose: false)
79
+ say "☑️ Enqueued #{name} to be added to dependencies"
80
+ end
81
+
82
+ def add_dev_package(name)
83
+ create_or_append(dev_package_list, "#{name}\n", verbose: false)
84
+ say "☑️ Enqueued #{name} to be added to dev dependencies"
85
+ end
86
+
87
+ def drop_package(name)
88
+ create_or_append(drop_package_list, "#{name}\n", verbose: false)
89
+ say "❎ Enqueued #{name} to be removed from dependencies"
90
+ end
91
+
92
+ def gemfile_hash
93
+ Digest::MD5.hexdigest(gemfile_path.read)
94
+ end
95
+
96
+ ### memoized values
97
+
98
+ def cr_npm_version
99
+ @cr_npm_version ||= CableReady::VERSION.gsub(".pre", "-pre")
100
+ end
101
+
102
+ def package_json
103
+ @package_json ||= Rails.root.join("package.json")
104
+ end
105
+
106
+ def entrypoint
107
+ @entrypoint ||= File.read("tmp/cable_ready_installer/entrypoint")
108
+ end
109
+
110
+ def bundler
111
+ @bundler ||= File.read("tmp/cable_ready_installer/bundler")
112
+ end
113
+
114
+ def config_path
115
+ @config_path ||= Rails.root.join(entrypoint, "config")
116
+ end
117
+
118
+ def importmap_path
119
+ @importmap_path ||= Rails.root.join("config/importmap.rb")
120
+ end
121
+
122
+ def friendly_importmap_path
123
+ @friendly_importmap_path ||= importmap_path.relative_path_from(Rails.root).to_s
124
+ end
125
+
126
+ def pack
127
+ @pack ||= pack_path.read
128
+ end
129
+
130
+ def friendly_pack_path
131
+ @friendly_pack_path ||= pack_path.relative_path_from(Rails.root).to_s
132
+ end
133
+
134
+ def pack_path
135
+ @pack_path ||= [
136
+ Rails.root.join(entrypoint, "application.js"),
137
+ Rails.root.join(entrypoint, "packs/application.js"),
138
+ Rails.root.join(entrypoint, "entrypoints/application.js")
139
+ ].find(&:exist?)
140
+ end
141
+
142
+ def package_list
143
+ @package_list ||= Rails.root.join("tmp/cable_ready_installer/npm_package_list")
144
+ end
145
+
146
+ def dev_package_list
147
+ @dev_package_list ||= Rails.root.join("tmp/cable_ready_installer/npm_dev_package_list")
148
+ end
149
+
150
+ def drop_package_list
151
+ @drop_package_list ||= Rails.root.join("tmp/cable_ready_installer/drop_npm_package_list")
152
+ end
153
+
154
+ def template_src
155
+ @template_src ||= File.read("tmp/cable_ready_installer/template_src")
156
+ end
157
+
158
+ def controllers_path
159
+ @controllers_path ||= Rails.root.join(entrypoint, "controllers")
160
+ end
161
+
162
+ def gemfile_path
163
+ @gemfile_path ||= Rails.root.join("Gemfile")
164
+ end
165
+
166
+ def gemfile
167
+ @gemfile ||= gemfile_path.read
168
+ end
169
+
170
+ def prefix
171
+ # standard:disable Style/RedundantStringEscape
172
+ @prefix ||= {
173
+ "vite" => "..\/",
174
+ "webpacker" => "",
175
+ "shakapacker" => "",
176
+ "importmap" => "",
177
+ "esbuild" => ".\/"
178
+ }[bundler]
179
+ # standard:enable Style/RedundantStringEscape
180
+ end
181
+
182
+ def application_record_path
183
+ @application_record_path ||= Rails.root.join("app/models/application_record.rb")
184
+ end
185
+
186
+ def action_cable_initializer_path
187
+ @action_cable_initializer_path ||= Rails.root.join("config/initializers/action_cable.rb")
188
+ end
189
+
190
+ def action_cable_initializer_working_path
191
+ @action_cable_initializer_working_path ||= Rails.root.join(working, "action_cable.rb")
192
+ end
193
+
194
+ def development_path
195
+ @development_path ||= Rails.root.join("config/environments/development.rb")
196
+ end
197
+
198
+ def development_working_path
199
+ @development_working_path ||= Rails.root.join(working, "development.rb")
200
+ end
201
+
202
+ def backups_path
203
+ @backups_path ||= Rails.root.join("tmp/cable_ready_installer/backups")
204
+ end
205
+
206
+ def add_gem_list
207
+ @add_gem_list ||= Rails.root.join("tmp/cable_ready_installer/add_gem_list")
208
+ end
209
+
210
+ def remove_gem_list
211
+ @remove_gem_list ||= Rails.root.join("tmp/cable_ready_installer/remove_gem_list")
212
+ end
213
+
214
+ def options_path
215
+ @options_path ||= Rails.root.join("tmp/cable_ready_installer/options")
216
+ end
217
+
218
+ def options
219
+ @options ||= YAML.safe_load(File.read(options_path))
220
+ end
221
+
222
+ def working
223
+ @working ||= Rails.root.join("tmp/cable_ready_installer/working")
224
+ end
@@ -64,7 +64,7 @@ module CableReady
64
64
  rescue JSON::ParserError
65
65
  {}
66
66
  end
67
- @enqueued_operations.push(operations)
67
+ @enqueued_operations.concat(Array.wrap(operations))
68
68
  self
69
69
  end
70
70
 
@@ -10,8 +10,7 @@ class CableReady::SanityChecker
10
10
  return if called_by_generate_config?
11
11
  return if called_by_rake?
12
12
 
13
- instance = new
14
- instance.check_new_version_available
13
+ new
15
14
  end
16
15
 
17
16
  private
@@ -25,37 +24,8 @@ class CableReady::SanityChecker
25
24
  end
26
25
  end
27
26
 
28
- def check_new_version_available
29
- return if CableReady.config.on_new_version_available == :ignore
30
- return if Rails.env.development? == false
31
- return if using_preview_release?
32
- begin
33
- latest_version = URI.open("https://raw.githubusercontent.com/stimulusreflex/cable_ready/master/LATEST", open_timeout: 1, read_timeout: 1).read.strip
34
- if latest_version != CableReady::VERSION
35
- puts <<~WARN
36
-
37
- 👉 There is a new version of CableReady available!
38
- Current: #{CableReady::VERSION} Latest: #{latest_version}
39
-
40
- If you upgrade, it is very important that you update BOTH Gemfile and package.json
41
- Then, run `bundle install && yarn install` to update to #{latest_version}.
42
-
43
- WARN
44
- exit if CableReady.config.on_new_version_available == :exit
45
- end
46
- rescue
47
- puts "👉 CableReady #{CableReady::VERSION} update check skipped: connection timeout"
48
- end
49
- end
50
-
51
27
  private
52
28
 
53
- def using_preview_release?
54
- preview = CableReady::VERSION.match?(LATEST_VERSION_FORMAT) == false
55
- puts "👉 CableReady #{CableReady::VERSION} update check skipped: pre-release build" if preview
56
- preview
57
- end
58
-
59
29
  def initializer_missing?
60
30
  File.exist?(Rails.root.join("config", "initializers", "cable_ready.rb")) == false
61
31
  end
@@ -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.pre9"
4
+ VERSION = "5.0.0.rc1"
5
5
  end
data/lib/cable_ready.rb CHANGED
@@ -1,14 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open-uri"
4
- require "active_record"
5
- require "action_view"
6
- require "active_support/all"
7
- require "thread/local"
8
- require "monitor"
9
- require "observer"
10
- require "singleton"
11
- require "cable_ready/version"
4
+ require "active_support/message_verifier"
12
5
  require "cable_ready/identifiable"
13
6
  require "cable_ready/operation_builder"
14
7
  require "cable_ready/config"
@@ -20,8 +13,12 @@ require "cable_ready/channel"
20
13
  require "cable_ready/channels"
21
14
  require "cable_ready/cable_car"
22
15
  require "cable_ready/stream_identifier"
16
+ require "cable_ready/version"
17
+ require "cable_ready_helper"
23
18
 
24
19
  module CableReady
20
+ autoload :MemoryCacheDebounceAdapter, "cable_ready/updatable/memory_cache_debounce_adapter"
21
+
25
22
  class << self
26
23
  def config
27
24
  CableReady::Config.instance
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: remove me once CableReady 5.0 was released
4
+
5
+ require_relative "../app/helpers/cable_ready/view_helper"
6
+
7
+ module CableReadyHelper
8
+ def self.included(base)
9
+ warn "NOTICE: `CableReadyHelper` was renamed to `CableReady::ViewHelper`. Please update your include accordingly."
10
+
11
+ base.include(::CableReady::ViewHelper)
12
+ end
13
+ end
@@ -7,32 +7,71 @@ class CableReady::ChannelGenerator < Rails::Generators::NamedBase
7
7
  class_option :stream_for, type: :string
8
8
  class_option :stimulus, type: :boolean
9
9
 
10
+ def destroy_not_supported
11
+ if behavior == :revoke
12
+ puts "Sorry, we don't support destroying generated channels.\nDelete the Action Cable channel class, as well as any corresponding JavaScript classes."
13
+ exit
14
+ end
15
+ end
16
+
10
17
  def check_options
11
- raise "Can't specify --stream-from and --stream-for at the same time" if options.key?(:stream_from) && options.key?(:stream_for)
18
+ if options.key?(:stream_from) && options.key?(:stream_for)
19
+ puts "Can't specify --stream-from and --stream-for at the same time"
20
+ exit
21
+ end
12
22
  end
13
23
 
14
24
  def create_channel
15
- generate "channel", file_name
25
+ generate "channel", file_name, "--skip"
16
26
  end
17
27
 
18
28
  def enhance_channels
29
+ @entrypoint = [
30
+ "app/javascript",
31
+ "app/frontend"
32
+ ].find { |path| File.exist?(Rails.root.join(path)) } || "app/javascript"
33
+ puts "Where do JavaScript files live in your app? Our best guess is: \e[1m#{@entrypoint}\e[22m 🤔"
34
+ puts "Press enter to accept this, or type a different path."
35
+ print "> "
36
+ input = Rails.env.test? ? "tmp/app/javascript" : $stdin.gets.chomp
37
+ @entrypoint = input unless input.blank?
38
+ @js_channel = "#{@entrypoint}/channels/#{file_name}_channel.js"
39
+
19
40
  if using_broadcast_to?
20
- gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_for #{resource}.find(params[:id])\n"
21
- template "app/javascript/controllers/%file_name%_controller.js" if using_stimulus?
22
- else
23
- prepend_to_file "app/javascript/channels/#{file_name}_channel.js", "import CableReady from 'cable_ready'\n"
24
- inject_into_file "app/javascript/channels/#{file_name}_channel.js", after: "// Called when there's incoming data on the websocket for this channel\n" do
25
- <<-JS
26
- if (data.cableReady) CableReady.perform(data.operations)
27
- JS
41
+ if using_stimulus?
42
+ template("#{@entrypoint}/controllers/%file_name%_controller.js")
43
+ Rails.root.join(@js_channel).delete
44
+ else
45
+ gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_for #{resource}.find(params[:id])\n", verbose: false
46
+ gsub_file @js_channel, /"#{resource}Channel"/, verbose: false do
47
+ <<-JS
48
+
49
+ {
50
+ channel: "#{resource}Channel",
51
+ id: 1
52
+ }
53
+ JS
54
+ end
55
+ doctor_javascript_channel_class
56
+ puts "\nDon't forget to update the id in the channel subscription: #{@js_channel}\nIt's currently set to 1; you'll want to change that to a dynamic value based on something in your DOM."
28
57
  end
29
-
30
- gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_from \"#{identifier}\"\n"
58
+ else
59
+ gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_from \"#{identifier}\"\n", verbose: false
60
+ doctor_javascript_channel_class
31
61
  end
32
62
  end
33
63
 
34
64
  private
35
65
 
66
+ def doctor_javascript_channel_class
67
+ prepend_to_file @js_channel, "import CableReady from 'cable_ready'\n", verbose: false
68
+ inject_into_file @js_channel, after: "// Called when there's incoming data on the websocket for this channel\n", verbose: false do
69
+ <<-JS
70
+ if (data.cableReady) CableReady.perform(data.operations)
71
+ JS
72
+ end
73
+ end
74
+
36
75
  def option_given?
37
76
  options.key?(:stream_from) || options.key?(:stream_for)
38
77
  end
@@ -3,16 +3,25 @@
3
3
  CableReady.configure do |config|
4
4
  # Enable/disable exiting / warning when the sanity checks fail options:
5
5
  # `:exit` or `:warn` or `:ignore`
6
-
6
+ #
7
7
  # config.on_failed_sanity_checks = :exit
8
8
 
9
- # Enable/disable exiting / warning when there's a new CableReady release
10
- # `:exit` or `:warn` or `:ignore`
11
-
12
- # config.on_new_version_available = :ignore
9
+ # Enable/disable assets compilation
10
+ # `true` or `false`
11
+ #
12
+ # config.precompile_assets = true
13
13
 
14
14
  # Define your own custom operations
15
15
  # https://cableready.stimulusreflex.com/customization#custom-operations
16
-
16
+ #
17
17
  # config.add_operation_name :jazz_hands
18
+
19
+ # Change the default Active Job queue used for broadcast_later and broadcast_later_to
20
+ #
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
18
27
  end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cable_ready/installer"
4
+
5
+ # verify that Action Cable is installed
6
+ if defined?(ActionCable::Engine)
7
+ say "✅ ActionCable::Engine is loaded and in scope"
8
+ else
9
+ halt "ActionCable::Engine is not loaded, please add or uncomment `require \"action_cable/engine\"` to your `config/application.rb`"
10
+ return
11
+ end
12
+
13
+ return if pack_path_missing?
14
+
15
+ # verify that the Action Cable pubsub config is created
16
+ cable_config = Rails.root.join("config/cable.yml")
17
+
18
+ if cable_config.exist?
19
+ say "✅ config/cable.yml is present"
20
+ else
21
+ inside "config" do
22
+ template "cable.yml"
23
+ end
24
+ end
25
+
26
+ # verify that the Action Cable pubsub is set to use redis in development
27
+ yaml = YAML.safe_load(cable_config.read)
28
+ app_name = Rails.application.class.module_parent.name.underscore
29
+
30
+ if yaml["development"]["adapter"] == "redis"
31
+ say "✅ config/cable.yml is configured to use the redis adapter in development"
32
+ elsif yaml["development"]["adapter"] == "async"
33
+ yaml["development"] = {
34
+ "adapter" => "redis",
35
+ "url" => "<%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>",
36
+ "channel_prefix" => "#{app_name}_development"
37
+ }
38
+ backup(cable_config) do
39
+ cable_config.write(yaml.to_yaml)
40
+ end
41
+ say "✅ config/cable.yml was updated to use the redis adapter in development"
42
+ else
43
+ say "🤷 config/cable.yml should use the redis adapter - or something like it - in development. You have something else specified, and we trust that you know what you're doing."
44
+ end
45
+
46
+ if Rails::VERSION::MAJOR >= 7
47
+ add_gem "redis@~> 5"
48
+ else
49
+ add_gem "redis@~> 4"
50
+ end
51
+
52
+ # install action-cable-redis-backport gem if using Action Cable < 7.1
53
+ unless ActionCable::VERSION::MAJOR >= 7 && ActionCable::VERSION::MINOR >= 1
54
+ if !gemfile.match?(/gem ['"]action-cable-redis-backport['"]/)
55
+ add_gem "action-cable-redis-backport@~> 1"
56
+ end
57
+ end
58
+
59
+ # verify that the Action Cable channels folder and consumer class is available
60
+ step_path = "/app/javascript/channels/"
61
+ channels_path = Rails.root.join(entrypoint, "channels")
62
+ consumer_src = fetch(step_path, "consumer.js.tt")
63
+ consumer_path = channels_path / "consumer.js"
64
+ index_src = fetch(step_path, "index.js.#{bundler}.tt")
65
+ index_path = channels_path / "index.js"
66
+ friendly_index_path = index_path.relative_path_from(Rails.root).to_s
67
+
68
+ empty_directory channels_path unless channels_path.exist?
69
+
70
+ copy_file(consumer_src, consumer_path) unless consumer_path.exist?
71
+
72
+ if index_path.exist?
73
+ if index_path.read == index_src.read
74
+ say "✅ #{friendly_index_path} is present"
75
+ else
76
+ backup(index_path) do
77
+ copy_file(index_src, index_path, verbose: false)
78
+ end
79
+ say "✅ #{friendly_index_path} has been created"
80
+ end
81
+ else
82
+ copy_file(index_src, index_path)
83
+ end
84
+
85
+ # import Action Cable channels into application pack
86
+ channels_pattern = /import ['"](\.\.\/|\.\/)?channels['"]/
87
+ channels_commented_pattern = /\s*\/\/\s*#{channels_pattern}/
88
+ channel_import = "import \"#{prefix}channels\"\n"
89
+
90
+ if pack.match?(channels_pattern)
91
+ if pack.match?(channels_commented_pattern)
92
+ proceed = if options.key? "uncomment"
93
+ options["uncomment"]
94
+ else
95
+ !no?("✨ Action Cable seems to be commented out in your application.js. Do you want to uncomment it? (Y/n)")
96
+ end
97
+
98
+ if proceed
99
+ # uncomment_lines only works with Ruby comments 🙄
100
+ lines = pack_path.readlines
101
+ matches = lines.select { |line| line =~ channels_commented_pattern }
102
+ lines[lines.index(matches.last).to_i] = channel_import
103
+ pack_path.write lines.join
104
+ say "✅ channels imported in #{friendly_pack_path}"
105
+ else
106
+ say "🤷 your Action Cable channels are not being imported in your application.js. We trust that you have a reason for this."
107
+ end
108
+ else
109
+ say "✅ channels imported in #{friendly_pack_path}"
110
+ end
111
+ else
112
+ lines = pack_path.readlines
113
+ matches = lines.select { |line| line =~ /^import / }
114
+ lines.insert lines.index(matches.last).to_i + 1, channel_import
115
+ pack_path.write lines.join
116
+ say "✅ channels imported in #{friendly_pack_path}"
117
+ end
118
+
119
+ # create working copy of Action Cable initializer in tmp
120
+ if action_cable_initializer_path.exist?
121
+ FileUtils.cp(action_cable_initializer_path, action_cable_initializer_working_path)
122
+ else
123
+ # create Action Cable initializer if it doesn't already exist
124
+ create_file(action_cable_initializer_working_path, verbose: false) do
125
+ <<~RUBY
126
+ # frozen_string_literal: true
127
+
128
+ RUBY
129
+ end
130
+ say "✅ Action Cable initializer created"
131
+ end
132
+
133
+ # silence notoriously chatty Action Cable logs
134
+ if !action_cable_initializer_working_path.read.match?(/^[^#]*ActionCable.server.config.logger/)
135
+ append_file(action_cable_initializer_working_path, verbose: false) do
136
+ <<~RUBY
137
+ ActionCable.server.config.logger = Logger.new(nil)
138
+
139
+ RUBY
140
+ end
141
+ say "✅ Action Cable logger silenced for performance and legibility"
142
+ end
143
+
144
+ complete_step :action_cable