turbo-rails 2.0.4 → 2.0.6

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
@@ -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.4"
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.4
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-02-21 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.