turbo-rails 2.0.5 → 2.0.6

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.
@@ -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
@@ -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
@@ -228,6 +228,32 @@ class Turbo::Streams::TagBuilder
228
228
  action_all :prepend, targets, content, **rendering, &block
229
229
  end
230
230
 
231
+ # Morph the <tt>target</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
232
+ # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
233
+ #
234
+ # <%= turbo_stream.morph "clearance_5", "<div id='clearance_5'>Morph the dom target identified by clearance_5</div>" %>
235
+ # <%= turbo_stream.morph clearance %>
236
+ # <%= turbo_stream.morph clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
237
+ # <%= turbo_stream.morph "clearance_5" do %>
238
+ # <div id='clearance_5'>Morph the dom target identified by clearance_5</div>
239
+ # <% end %>
240
+ def morph(target, content = nil, **rendering, &block)
241
+ action :morph, target, content, **rendering, &block
242
+ end
243
+
244
+ # Morph the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
245
+ # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the targets as a record. Examples:
246
+ #
247
+ # <%= turbo_stream.morph_all ".clearance_item", "<div class='clearance_item'>Morph the dom target identified by the class clearance_item</div>" %>
248
+ # <%= turbo_stream.morph_all clearance %>
249
+ # <%= turbo_stream.morph_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
250
+ # <%= turbo_stream.morph_all ".clearance_item" do %>
251
+ # <div class='clearance_item'>Morph the dom target identified by the class clearance_item</div>
252
+ # <% end %>
253
+ def morph_all(targets, content = nil, **rendering, &block)
254
+ action_all :morph, targets, content, **rendering, &block
255
+ end
256
+
231
257
  # Send an action of the type <tt>name</tt> to <tt>target</tt>. Options described in the concrete methods.
232
258
  def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block)
233
259
  template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
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.6"
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.6
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-07-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activejob
@@ -107,7 +107,8 @@ files:
107
107
  homepage: https://github.com/hotwired/turbo-rails
108
108
  licenses:
109
109
  - MIT
110
- metadata: {}
110
+ metadata:
111
+ changelog_uri: https://github.com/hotwired/turbo-rails/releases
111
112
  post_install_message:
112
113
  rdoc_options: []
113
114
  require_paths:
@@ -123,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
124
  - !ruby/object:Gem::Version
124
125
  version: '0'
125
126
  requirements: []
126
- rubygems_version: 3.4.15
127
+ rubygems_version: 3.5.11
127
128
  signing_key:
128
129
  specification_version: 4
129
130
  summary: The speed of a single-page web application without having to write any JavaScript.