turbo-rails 2.0.5 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -75,8 +75,15 @@ module Turbo::Streams::Broadcasts
75
75
  end
76
76
 
77
77
  def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
78
- Turbo::Streams::ActionBroadcastJob.perform_later \
79
- stream_name_from(streamables), action: action, target: target, targets: targets, attributes: attributes, **rendering
78
+ streamables.flatten!
79
+ streamables.compact_blank!
80
+
81
+ if streamables.present?
82
+ target = convert_to_turbo_stream_dom_id(target)
83
+ targets = convert_to_turbo_stream_dom_id(targets, include_selector: true)
84
+ Turbo::Streams::ActionBroadcastJob.perform_later \
85
+ stream_name_from(streamables), action: action, target: target, targets: targets, attributes: attributes, **rendering
86
+ end
80
87
  end
81
88
 
82
89
  def broadcast_render_to(*streamables, **rendering)
@@ -88,7 +95,12 @@ module Turbo::Streams::Broadcasts
88
95
  end
89
96
 
90
97
  def broadcast_stream_to(*streamables, content:)
91
- ActionCable.server.broadcast stream_name_from(streamables), content
98
+ streamables.flatten!
99
+ streamables.compact_blank!
100
+
101
+ if streamables.present?
102
+ ActionCable.server.broadcast stream_name_from(streamables), content
103
+ end
92
104
  end
93
105
 
94
106
  def refresh_debouncer_for(*streamables, request_id: nil) # :nodoc:
@@ -9,27 +9,27 @@
9
9
  # helper modules like <tt>Turbo::Streams::StreamName</tt>:
10
10
  #
11
11
  # class CustomChannel < ActionCable::Channel::Base
12
- # extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
13
- # include Turbo::Streams::StreamName::ClassMethods
12
+ # extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
13
+ # include Turbo::Streams::StreamName::ClassMethods
14
14
  #
15
- # def subscribed
16
- # if (stream_name = verified_stream_name_from_params).present? &&
15
+ # def subscribed
16
+ # if (stream_name = verified_stream_name_from_params).present? &&
17
17
  # subscription_allowed?
18
- # stream_from stream_name
19
- # else
20
- # reject
21
- # end
22
- # end
18
+ # stream_from stream_name
19
+ # else
20
+ # reject
21
+ # end
22
+ # end
23
23
  #
24
- # def subscription_allowed?
25
- # # ...
26
- # end
24
+ # def subscription_allowed?
25
+ # # ...
26
+ # end
27
27
  # end
28
28
  #
29
- # This channel can be connected to a web page using <tt>:channel</tt> option in
30
- # <tt>turbo_stream_from</tt> helper:
29
+ # This channel can be connected to a web page using <tt>:channel</tt> option in
30
+ # <tt>turbo_stream_from</tt> helper:
31
31
  #
32
- # <%= turbo_stream_from 'room', channel: CustomChannel %>
32
+ # <%= turbo_stream_from 'room', channel: CustomChannel %>
33
33
  #
34
34
  class Turbo::StreamsChannel < ActionCable::Channel::Base
35
35
  extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
@@ -24,7 +24,7 @@ module Turbo::Frames::FrameRequest
24
24
  layout -> { "turbo_rails/frame" if turbo_frame_request? }
25
25
  etag { :frame if turbo_frame_request? }
26
26
 
27
- helper_method :turbo_frame_request_id
27
+ helper_method :turbo_frame_request?, :turbo_frame_request_id
28
28
  end
29
29
 
30
30
  private
@@ -33,6 +33,6 @@ module Turbo::Frames::FrameRequest
33
33
  end
34
34
 
35
35
  def turbo_frame_request_id
36
- request&.headers["Turbo-Frame"]
36
+ request.headers["Turbo-Frame"]
37
37
  end
38
38
  end
@@ -51,7 +51,7 @@ module Turbo::DriveHelper
51
51
  # Configure how to handle page refreshes. A page refresh happens when
52
52
  # Turbo loads the current page again with a *replace* visit:
53
53
  #
54
- # === Parameters:
54
+ # ==== Parameters:
55
55
  #
56
56
  # * <tt>method</tt> - Method to update the +<body>+ of the page
57
57
  # during a page refresh. It can be one of:
@@ -64,7 +64,7 @@ module Turbo::DriveHelper
64
64
  # * +reset:+: Resets scroll to the top, left corner. This is the default.
65
65
  # * +preserve:+: Keeps the scroll.
66
66
  #
67
- # === Example Usage:
67
+ # ==== Example Usage:
68
68
  #
69
69
  # turbo_refreshes_with(method: :morph, scroll: :preserve)
70
70
  def turbo_refreshes_with(method: :replace, scroll: :reset)
@@ -44,8 +44,9 @@ module Turbo::Streams::ActionHelper
44
44
 
45
45
  private
46
46
  def convert_to_turbo_stream_dom_id(target, include_selector: false)
47
- if Array(target).any? { |value| value.respond_to?(:to_key) }
48
- "#{"#" if include_selector}#{ActionView::RecordIdentifier.dom_id(*target)}"
47
+ target_array = Array.wrap(target)
48
+ if target_array.any? { |value| value.respond_to?(:to_key) }
49
+ "#{"#" if include_selector}#{ActionView::RecordIdentifier.dom_id(*target_array)}"
49
50
  else
50
51
  target
51
52
  end
@@ -48,7 +48,13 @@ module Turbo::StreamsHelper
48
48
  # It is also possible to pass additional parameters to the channel by passing them through `data` attributes:
49
49
  #
50
50
  # <%= turbo_stream_from "room", channel: RoomChannel, data: {room_name: "room #1"} %>
51
+ #
52
+ # Raises an +ArgumentError+ if all streamables are blank
53
+ #
54
+ # <%= turbo_stream_from("") %> # => ArgumentError: streamables can't be blank
55
+ # <%= turbo_stream_from("", nil) %> # => ArgumentError: streamables can't be blank
51
56
  def turbo_stream_from(*streamables, **attributes)
57
+ raise ArgumentError, "streamables can't be blank" unless streamables.any?(&:present?)
52
58
  attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
53
59
  attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
54
60
 
@@ -1,4 +1,4 @@
1
- # Turbo streams can be broadcast directly from models that include this module (this is automatically done for Active Records).
1
+ # Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records).
2
2
  # This makes it convenient to execute both synchronous and asynchronous updates, and render directly from callbacks in models
3
3
  # or from controllers or jobs that act on those models. Here's an example:
4
4
  #
@@ -79,19 +79,19 @@
79
79
  #
80
80
  # == Page refreshes
81
81
  #
82
- # You can broadcast "page refresh" stream actions. This will make subscribed clients reload the
82
+ # You can broadcast "page refresh" stream actions. This will make subscribed clients reload the
83
83
  # page. For pages that configure morphing and scroll preservation, this will translate into smooth
84
84
  # updates when it only updates the content that changed.
85
-
85
+ #
86
86
  # This approach is an alternative to fine-grained stream actions targeting specific DOM elements. It
87
87
  # offers good fidelity with a much simpler programming model. As a tradeoff, the fidelity you can reach
88
88
  # is often not as high as with targeted stream actions since it renders the entire page again.
89
89
  #
90
- # The +broadcast_refreshes+ class method configures the model to broadcast a "page refresh" on creates,
90
+ # The +broadcasts_refreshes+ class method configures the model to broadcast a "page refresh" on creates,
91
91
  # updates, and destroys to a stream name derived at runtime by the <tt>stream</tt> symbol invocation. Examples
92
92
  #
93
93
  # class Board < ApplicationRecord
94
- # broadcast_refreshes
94
+ # broadcasts_refreshes
95
95
  # end
96
96
  #
97
97
  # In this example, when a board is created, updated, or destroyed, a Turbo Stream for a
@@ -104,16 +104,16 @@
104
104
  # belongs_to :board, touch: true # +Board+ will trigger a page refresh on column changes
105
105
  # end
106
106
  #
107
- # You can also specify the streamable declaratively by passing a symbol to the +broadcast_refreshes_to+ method:
107
+ # You can also specify the streamable declaratively by passing a symbol to the +broadcasts_refreshes_to+ method:
108
108
  #
109
109
  # class Column < ApplicationRecord
110
110
  # belongs_to :board
111
- # broadcast_refreshes_to :board
111
+ # broadcasts_refreshes_to :board
112
112
  # end
113
113
  #
114
- # For more granular control, you can also broadcast a "page refresh" to a stream name derived
114
+ # For more granular control, you can also broadcast a "page refresh" to a stream name derived
115
115
  # from the passed <tt>streamables</tt> by using the instance-level methods <tt>broadcast_refresh_to</tt> or
116
- # <tt>broadcast_refresh_later_to</tt>. These methods are particularly useful when you want to trigger
116
+ # <tt>broadcast_refresh_later_to</tt>. These methods are particularly useful when you want to trigger
117
117
  # a page refresh for more specific scenarios. Example:
118
118
  #
119
119
  # class Clearance < ApplicationRecord
@@ -128,11 +128,11 @@
128
128
  # end
129
129
  # end
130
130
  #
131
- # In this example, a "page refresh" is broadcast to the stream named "identity:<identity-id>:clearances"
131
+ # In this example, a "page refresh" is broadcast to the stream named "identity:<identity-id>:clearances"
132
132
  # after a new clearance is created. All clients subscribed to this stream will refresh the page to reflect
133
133
  # the changes.
134
134
  #
135
- # When broadcasting page refreshes, Turbo will automatically debounce multiple calls in a row to only broadcast the last one.
135
+ # When broadcasting page refreshes, Turbo will automatically debounce multiple calls in a row to only broadcast the last one.
136
136
  # This is meant for scenarios where you process records in mass. Because of the nature of such signals, it makes no sense to
137
137
  # broadcast them repeatedly and individually.
138
138
  # == Suppressing broadcasts
@@ -260,6 +260,10 @@ module Turbo::Broadcastable
260
260
  # # Sends <turbo-stream action="replace" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
261
261
  # # to the stream named "identity:2:clearances"
262
262
  # clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
263
+ #
264
+ # # Sends <turbo-stream action="replace" method="morph" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
265
+ # # to the stream named "identity:2:clearances"
266
+ # clearance.broadcast_replace_to examiner.identity, :clearance, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
263
267
  def broadcast_replace_to(*streamables, **rendering)
264
268
  Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
265
269
  end
@@ -279,6 +283,10 @@ module Turbo::Broadcastable
279
283
  # # Sends <turbo-stream action="update" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
280
284
  # # to the stream named "identity:2:clearances"
281
285
  # clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
286
+ #
287
+ # # sends <turbo-stream action="update" method="morph" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
288
+ # # to the stream named "identity:2:clearances"
289
+ # # clearance.broadcast_update_to examiner.identity, :clearances, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
282
290
  def broadcast_update_to(*streamables, **rendering)
283
291
  Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
284
292
  end
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  Rails.application.routes.draw do
2
- get "recede_historical_location" => "turbo/native/navigation#recede", as: :turbo_recede_historical_location
3
- get "resume_historical_location" => "turbo/native/navigation#resume", as: :turbo_resume_historical_location
4
- get "refresh_historical_location" => "turbo/native/navigation#refresh", as: :turbo_refresh_historical_location
2
+ get "recede_historical_location", to: "turbo/native/navigation#recede", as: :turbo_recede_historical_location
3
+ get "resume_historical_location", to: "turbo/native/navigation#resume", as: :turbo_resume_historical_location
4
+ get "refresh_historical_location", to: "turbo/native/navigation#refresh", as: :turbo_refresh_historical_location
5
5
  end if Turbo.draw_routes
@@ -5,20 +5,6 @@ module Turbo
5
5
  system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}"
6
6
  end
7
7
 
8
- def redis_installed?
9
- Gem.win_platform? ?
10
- system('where redis-server > NUL 2>&1') :
11
- system('which redis-server > /dev/null')
12
- end
13
-
14
- def switch_on_redis_if_available
15
- if redis_installed?
16
- Rake::Task["turbo:install:redis"].invoke
17
- else
18
- puts "Run turbo:install:redis to switch on Redis and use it in development for turbo streams"
19
- end
20
- end
21
-
22
8
  def using_bun?
23
9
  Rails.root.join("bun.config.js").exist?
24
10
  end
@@ -43,24 +29,16 @@ namespace :turbo do
43
29
  desc "Install Turbo into the app with asset pipeline"
44
30
  task :importmap do
45
31
  Turbo::Tasks.run_turbo_install_template "turbo_with_importmap"
46
- Turbo::Tasks.switch_on_redis_if_available
47
32
  end
48
33
 
49
34
  desc "Install Turbo into the app with webpacker"
50
35
  task :node do
51
36
  Turbo::Tasks.run_turbo_install_template "turbo_with_node"
52
- Turbo::Tasks.switch_on_redis_if_available
53
37
  end
54
38
 
55
39
  desc "Install Turbo into the app with bun"
56
40
  task :bun do
57
41
  Turbo::Tasks.run_turbo_install_template "turbo_with_bun"
58
- Turbo::Tasks.switch_on_redis_if_available
59
- end
60
-
61
- desc "Switch on Redis and use it in development"
62
- task :redis do
63
- Turbo::Tasks.run_turbo_install_template "turbo_needs_redis"
64
42
  end
65
43
  end
66
44
  end
data/lib/turbo/engine.rb CHANGED
@@ -15,8 +15,25 @@ module Turbo
15
15
  #{root}/app/jobs
16
16
  )
17
17
 
18
+ # If the parent application does not use Action Cable, app/channels cannot
19
+ # be eager loaded, because it references the ActionCable constant.
20
+ #
21
+ # When turbo-rails depends on Rails 7 or above, the entire block can be
22
+ # reduced to
23
+ #
24
+ # unless defined?(ActionCable)
25
+ # Rails.autoloaders.once.do_not_eager_load("#{root}/app/channels")
26
+ # end
27
+ #
18
28
  initializer "turbo.no_action_cable", before: :set_eager_load_paths do
19
- config.eager_load_paths.delete("#{root}/app/channels") unless defined?(ActionCable)
29
+ unless defined?(ActionCable)
30
+ if Rails.autoloaders.zeitwerk_enabled?
31
+ Rails.autoloaders.once.do_not_eager_load("#{root}/app/channels")
32
+ else
33
+ # This else branch only runs in Rails 6.x + classic mode.
34
+ config.eager_load_paths.delete("#{root}/app/channels")
35
+ end
36
+ end
20
37
  end
21
38
 
22
39
  # If you don't want to precompile Turbo's assets (eg. because you're using webpack),
@@ -63,11 +80,9 @@ module Turbo
63
80
  end
64
81
 
65
82
  initializer "turbo.renderer" do
66
- ActiveSupport.on_load(:action_controller) do
67
- ActionController::Renderers.add :turbo_stream do |turbo_streams_html, options|
68
- self.content_type = Mime[:turbo_stream] if media_type.nil?
69
- turbo_streams_html
70
- end
83
+ ActionController::Renderers.add :turbo_stream do |turbo_streams_html, options|
84
+ self.content_type = Mime[:turbo_stream] if media_type.nil?
85
+ turbo_streams_html
71
86
  end
72
87
  end
73
88
 
data/lib/turbo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Turbo
2
- VERSION = "2.0.5"
2
+ VERSION = "2.0.7"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Stephenson
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-03-08 00:00:00.000000000 Z
13
+ date: 2024-09-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activejob
@@ -93,7 +93,6 @@ files:
93
93
  - app/models/turbo/thread_debouncer.rb
94
94
  - app/views/layouts/turbo_rails/frame.html.erb
95
95
  - config/routes.rb
96
- - lib/install/turbo_needs_redis.rb
97
96
  - lib/install/turbo_with_bun.rb
98
97
  - lib/install/turbo_with_importmap.rb
99
98
  - lib/install/turbo_with_node.rb
@@ -107,7 +106,8 @@ files:
107
106
  homepage: https://github.com/hotwired/turbo-rails
108
107
  licenses:
109
108
  - MIT
110
- metadata: {}
109
+ metadata:
110
+ changelog_uri: https://github.com/hotwired/turbo-rails/releases
111
111
  post_install_message:
112
112
  rdoc_options: []
113
113
  require_paths:
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  requirements: []
126
- rubygems_version: 3.4.15
126
+ rubygems_version: 3.5.16
127
127
  signing_key:
128
128
  specification_version: 4
129
129
  summary: The speed of a single-page web application without having to write any JavaScript.
@@ -1,20 +0,0 @@
1
- if (cable_config_path = Rails.root.join("config/cable.yml")).exist?
2
- say "Enable redis in bundle"
3
-
4
- gemfile_content = File.read(Rails.root.join("Gemfile"))
5
- pattern = /gem ['"]redis['"]/
6
-
7
- if gemfile_content.match?(pattern)
8
- uncomment_lines "Gemfile", pattern
9
- else
10
- append_file "Gemfile", "\n# Use Redis for Action Cable"
11
- gem 'redis', '~> 4.0'
12
- end
13
-
14
- run_bundle
15
-
16
- say "Switch development cable to use redis"
17
- gsub_file cable_config_path.to_s, /development:\n\s+adapter: async/, "development:\n adapter: redis\n url: redis://localhost:6379/1"
18
- else
19
- say 'ActionCable config file (config/cable.yml) is missing. Uncomment "gem \'redis\'" in your Gemfile and create config/cable.yml to use the Turbo Streams broadcast feature.'
20
- end