turbo-rails 1.5.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/README.md +3 -6
- data/app/assets/javascripts/turbo.js +1894 -725
- data/app/assets/javascripts/turbo.min.js +9 -5
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +18 -4
- data/app/controllers/concerns/turbo/request_id_tracking.rb +12 -0
- data/app/helpers/turbo/drive_helper.rb +63 -4
- data/app/helpers/turbo/frames_helper.rb +4 -1
- data/app/helpers/turbo/streams/action_helper.rb +5 -1
- data/app/javascript/turbo/index.js +2 -0
- data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -2
- data/app/jobs/turbo/streams/broadcast_job.rb +1 -1
- data/app/jobs/turbo/streams/broadcast_stream_job.rb +7 -0
- data/app/models/concerns/turbo/broadcastable.rb +87 -20
- data/app/models/turbo/debouncer.rb +24 -0
- data/app/models/turbo/streams/tag_builder.rb +20 -0
- data/app/models/turbo/thread_debouncer.rb +28 -0
- data/config/routes.rb +0 -1
- data/lib/install/turbo_with_importmap.rb +1 -1
- data/lib/turbo/broadcastable/test_helper.rb +1 -1
- data/lib/turbo/engine.rb +6 -0
- data/lib/turbo/version.rb +1 -1
- data/lib/turbo-rails.rb +10 -0
- metadata +6 -2
@@ -33,9 +33,14 @@ module Turbo::Streams::Broadcasts
|
|
33
33
|
broadcast_action_to(*streamables, action: :prepend, **opts)
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def broadcast_refresh_to(*streamables, **opts)
|
37
|
+
broadcast_stream_to(*streamables, content: turbo_stream_refresh_tag)
|
38
|
+
end
|
39
|
+
|
40
|
+
def broadcast_action_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
|
37
41
|
broadcast_stream_to(*streamables, content: turbo_stream_action_tag(action, target: target, targets: targets, template:
|
38
|
-
rendering.delete(:content) || rendering.delete(:html) || (rendering.any? ? render_format(:html, **rendering) : nil)
|
42
|
+
rendering.delete(:content) || rendering.delete(:html) || (rendering[:render] != false && rendering.any? ? render_format(:html, **rendering) : nil),
|
43
|
+
**attributes
|
39
44
|
))
|
40
45
|
end
|
41
46
|
|
@@ -63,9 +68,15 @@ module Turbo::Streams::Broadcasts
|
|
63
68
|
broadcast_action_later_to(*streamables, action: :prepend, **opts)
|
64
69
|
end
|
65
70
|
|
66
|
-
def
|
71
|
+
def broadcast_refresh_later_to(*streamables, request_id: Turbo.current_request_id, **opts)
|
72
|
+
refresh_debouncer_for(*streamables, request_id: request_id).debounce do
|
73
|
+
Turbo::Streams::BroadcastStreamJob.perform_later stream_name_from(streamables), content: turbo_stream_refresh_tag(request_id: request_id, **opts)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
|
67
78
|
Turbo::Streams::ActionBroadcastJob.perform_later \
|
68
|
-
stream_name_from(streamables), action: action, target: target, targets: targets, **rendering
|
79
|
+
stream_name_from(streamables), action: action, target: target, targets: targets, attributes: attributes, **rendering
|
69
80
|
end
|
70
81
|
|
71
82
|
def broadcast_render_to(*streamables, **rendering)
|
@@ -80,6 +91,9 @@ module Turbo::Streams::Broadcasts
|
|
80
91
|
ActionCable.server.broadcast stream_name_from(streamables), content
|
81
92
|
end
|
82
93
|
|
94
|
+
def refresh_debouncer_for(*streamables, request_id: nil) # :nodoc:
|
95
|
+
Turbo::ThreadDebouncer.for("turbo-refresh-debouncer-#{stream_name_from(streamables.including(request_id))}")
|
96
|
+
end
|
83
97
|
|
84
98
|
private
|
85
99
|
def render_format(format, **rendering)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Turbo::RequestIdTracking
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
around_action :turbo_tracking_request_id
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def turbo_tracking_request_id(&block)
|
10
|
+
Turbo.with_request_id(request.headers["X-Turbo-Request-Id"], &block)
|
11
|
+
end
|
12
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module Turbo::DriveHelper
|
2
|
-
#
|
2
|
+
# Helpers to configure Turbo Drive via meta directives. They come in two
|
3
|
+
# variants:
|
4
|
+
#
|
5
|
+
# The recommended option is to include +yield :head+ in the +<head>+ section
|
6
|
+
# of the layout. Then you can use the helpers in any view.
|
3
7
|
#
|
4
8
|
# ==== Example
|
5
9
|
#
|
@@ -9,21 +13,76 @@ module Turbo::DriveHelper
|
|
9
13
|
# # app/views/trays/index.html.erb
|
10
14
|
# <% turbo_exempts_page_from_cache %>
|
11
15
|
# <p>Page that shouldn't be cached by Turbo</p>
|
16
|
+
#
|
17
|
+
# Alternatively, you can use the +_tag+ variant of the helpers to only get the
|
18
|
+
# HTML for the meta directive.
|
12
19
|
|
13
20
|
# Pages that are more likely than not to be a cache miss can skip turbo cache to avoid visual jitter.
|
14
21
|
# Cannot be used along with +turbo_exempts_page_from_preview+.
|
15
22
|
def turbo_exempts_page_from_cache
|
16
|
-
provide :head,
|
23
|
+
provide :head, turbo_exempts_page_from_cache_tag
|
24
|
+
end
|
25
|
+
|
26
|
+
# See +turbo_exempts_page_from_cache+.
|
27
|
+
def turbo_exempts_page_from_cache_tag
|
28
|
+
tag.meta(name: "turbo-cache-control", content: "no-cache")
|
17
29
|
end
|
18
30
|
|
19
31
|
# Specify that a cached version of the page should not be shown as a preview during an application visit.
|
20
32
|
# Cannot be used along with +turbo_exempts_page_from_cache+.
|
21
33
|
def turbo_exempts_page_from_preview
|
22
|
-
provide :head,
|
34
|
+
provide :head, turbo_exempts_page_from_preview_tag
|
35
|
+
end
|
36
|
+
|
37
|
+
# See +turbo_exempts_page_from_preview+.
|
38
|
+
def turbo_exempts_page_from_preview_tag
|
39
|
+
tag.meta(name: "turbo-cache-control", content: "no-preview")
|
23
40
|
end
|
24
41
|
|
25
42
|
# Force the page, when loaded by Turbo, to be cause a full page reload.
|
26
43
|
def turbo_page_requires_reload
|
27
|
-
provide :head,
|
44
|
+
provide :head, turbo_page_requires_reload_tag
|
45
|
+
end
|
46
|
+
|
47
|
+
# See +turbo_page_requires_reload+.
|
48
|
+
def turbo_page_requires_reload_tag
|
49
|
+
tag.meta(name: "turbo-visit-control", content: "reload")
|
50
|
+
end
|
51
|
+
|
52
|
+
# Configure how to handle page refreshes. A page refresh happens when
|
53
|
+
# Turbo loads the current page again with a *replace* visit:
|
54
|
+
#
|
55
|
+
# === Parameters:
|
56
|
+
#
|
57
|
+
# * <tt>method</tt> - Method to update the +<body>+ of the page
|
58
|
+
# during a page refresh. It can be one of:
|
59
|
+
# * +replace:+: Replaces the existing +<body>+ with the new one. This is the
|
60
|
+
# default behavior.
|
61
|
+
# * +morph:+: Morphs the existing +<body>+ into the new one.
|
62
|
+
#
|
63
|
+
# * <tt>scroll</tt> - Controls the scroll behavior when a page refresh happens. It
|
64
|
+
# can be one of:
|
65
|
+
# * +reset:+: Resets scroll to the top, left corner. This is the default.
|
66
|
+
# * +preserve:+: Keeps the scroll.
|
67
|
+
#
|
68
|
+
# === Example Usage:
|
69
|
+
#
|
70
|
+
# turbo_refreshes_with(method: :morph, scroll: :preserve)
|
71
|
+
def turbo_refreshes_with(method: :replace, scroll: :reset)
|
72
|
+
provide :head, turbo_refresh_method_tag(method)
|
73
|
+
provide :head, turbo_refresh_scroll_tag(scroll)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Configure method to perform page refreshes. See +turbo_refreshes_with+.
|
77
|
+
def turbo_refresh_method_tag(method = :replace)
|
78
|
+
raise ArgumentError, "Invalid refresh option '#{method}'" unless method.in?(%i[ replace morph ])
|
79
|
+
tag.meta(name: "turbo-refresh-method", content: method)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Configure scroll strategy for page refreshes. See +turbo_refreshes_with+.
|
83
|
+
def turbo_refresh_scroll_tag(scroll = :reset)
|
84
|
+
raise ArgumentError, "Invalid scroll option '#{scroll}'" unless scroll.in?(%i[ reset preserve ])
|
85
|
+
tag.meta(name: "turbo-refresh-scroll", content: scroll)
|
28
86
|
end
|
29
87
|
end
|
88
|
+
|
@@ -24,6 +24,9 @@ module Turbo::FramesHelper
|
|
24
24
|
# <% end %>
|
25
25
|
# # => <turbo-frame id="tray"><div>My tray frame!</div></turbo-frame>
|
26
26
|
#
|
27
|
+
# <%= turbo_frame_tag [user_id, "tray"], src: tray_path(tray) %>
|
28
|
+
# # => <turbo-frame id="1_tray" src="http://example.com/trays/1"></turbo-frame>
|
29
|
+
#
|
27
30
|
# The `turbo_frame_tag` helper will convert the arguments it receives to their
|
28
31
|
# `dom_id` if applicable to easily generate unique ids for Turbo Frames:
|
29
32
|
#
|
@@ -36,7 +39,7 @@ module Turbo::FramesHelper
|
|
36
39
|
# <%= turbo_frame_tag(Article.find(1), Comment.new) %>
|
37
40
|
# # => <turbo-frame id="article_1_new_comment"></turbo-frame>
|
38
41
|
def turbo_frame_tag(*ids, src: nil, target: nil, **attributes, &block)
|
39
|
-
id = ids.first.respond_to?(:to_key) ? ActionView::RecordIdentifier.dom_id(*ids) : ids.
|
42
|
+
id = ids.first.respond_to?(:to_key) ? ActionView::RecordIdentifier.dom_id(*ids) : ids.join('_')
|
40
43
|
src = url_for(src) if src.present?
|
41
44
|
|
42
45
|
tag.turbo_frame(**attributes.merge(id: id, src: src, target: target).compact, &block)
|
@@ -24,7 +24,7 @@ module Turbo::Streams::ActionHelper
|
|
24
24
|
# # => <turbo-stream action="remove" target="special_message_1"></turbo-stream>
|
25
25
|
#
|
26
26
|
def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **attributes)
|
27
|
-
template = action.to_sym
|
27
|
+
template = action.to_sym.in?(%i[ remove refresh ]) ? "" : tag.template(template.to_s.html_safe)
|
28
28
|
|
29
29
|
if target = convert_to_turbo_stream_dom_id(target)
|
30
30
|
tag.turbo_stream(template, **attributes, action: action, target: target)
|
@@ -35,6 +35,10 @@ module Turbo::Streams::ActionHelper
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
def turbo_stream_refresh_tag(request_id: Turbo.current_request_id, **attributes)
|
39
|
+
turbo_stream_action_tag(:refresh, **{ "request-id": request_id }.compact, **attributes)
|
40
|
+
end
|
41
|
+
|
38
42
|
private
|
39
43
|
def convert_to_turbo_stream_dom_id(target, include_selector: false)
|
40
44
|
if Array(target).any? { |value| value.respond_to?(:to_key) }
|
@@ -2,7 +2,7 @@
|
|
2
2
|
class Turbo::Streams::ActionBroadcastJob < ActiveJob::Base
|
3
3
|
discard_on ActiveJob::DeserializationError
|
4
4
|
|
5
|
-
def perform(stream, action:, target:, **rendering)
|
6
|
-
Turbo::StreamsChannel.broadcast_action_to stream, action: action, target: target, **rendering
|
5
|
+
def perform(stream, action:, target:, attributes: {}, **rendering)
|
6
|
+
Turbo::StreamsChannel.broadcast_action_to stream, action: action, target: target, attributes: attributes, **rendering
|
7
7
|
end
|
8
8
|
end
|
@@ -75,9 +75,32 @@
|
|
75
75
|
# In addition to the four basic actions, you can also use <tt>broadcast_render</tt>,
|
76
76
|
# <tt>broadcast_render_to</tt> <tt>broadcast_render_later</tt>, and <tt>broadcast_render_later_to</tt>
|
77
77
|
# to render a turbo stream template with multiple actions.
|
78
|
+
#
|
79
|
+
# == Suppressing broadcasts
|
80
|
+
#
|
81
|
+
# Sometimes, you need to disable broadcasts in certain scenarios. You can use <tt>.suppressing_turbo_broadcasts</tt> to create
|
82
|
+
# execution contexts where broadcasts are disabled:
|
83
|
+
#
|
84
|
+
# class Message < ApplicationRecord
|
85
|
+
# after_create_commit :update_message
|
86
|
+
#
|
87
|
+
# private
|
88
|
+
# def update_message
|
89
|
+
# broadcast_replace_to(user, :message, target: "message", renderable: MessageComponent.new)
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Message.suppressing_turbo_broadcasts do
|
94
|
+
# Message.create!(board: board) # This won't broadcast the replace action
|
95
|
+
# end
|
78
96
|
module Turbo::Broadcastable
|
79
97
|
extend ActiveSupport::Concern
|
80
98
|
|
99
|
+
included do
|
100
|
+
thread_mattr_accessor :suppressed_turbo_broadcasts, instance_accessor: false
|
101
|
+
delegate :suppressed_turbo_broadcasts?, to: "self.class"
|
102
|
+
end
|
103
|
+
|
81
104
|
module ClassMethods
|
82
105
|
# Configures the model to broadcast creates, updates, and destroys to a stream name derived at runtime by the
|
83
106
|
# <tt>stream</tt> symbol invocation. By default, the creates are appended to a dom id target name derived from
|
@@ -112,10 +135,36 @@ module Turbo::Broadcastable
|
|
112
135
|
after_destroy_commit -> { broadcast_remove }
|
113
136
|
end
|
114
137
|
|
138
|
+
# Configures the model to broadcast a "page refresh" on creates, updates, and destroys to a stream
|
139
|
+
# name derived at runtime by the <tt>stream</tt> symbol invocation.
|
140
|
+
def broadcasts_refreshes_to(stream)
|
141
|
+
after_commit -> { broadcast_refresh_later_to(stream.try(:call, self) || send(stream)) }
|
142
|
+
end
|
143
|
+
|
144
|
+
# Same as <tt>#broadcasts_refreshes_to</tt>, but the designated stream for page refreshes is automatically set to
|
145
|
+
# the current model, for creates - to the model plural name, which can be overriden by passing <tt>stream</tt>.
|
146
|
+
def broadcasts_refreshes(stream = model_name.plural)
|
147
|
+
after_create_commit -> { broadcast_refresh_later_to(stream) }
|
148
|
+
after_update_commit -> { broadcast_refresh_later }
|
149
|
+
after_destroy_commit -> { broadcast_refresh }
|
150
|
+
end
|
151
|
+
|
115
152
|
# All default targets will use the return of this method. Overwrite if you want something else than <tt>model_name.plural</tt>.
|
116
153
|
def broadcast_target_default
|
117
154
|
model_name.plural
|
118
155
|
end
|
156
|
+
|
157
|
+
# Executes +block+ preventing both synchronous and asynchronous broadcasts from this model.
|
158
|
+
def suppressing_turbo_broadcasts(&block)
|
159
|
+
original, self.suppressed_turbo_broadcasts = self.suppressed_turbo_broadcasts, true
|
160
|
+
yield
|
161
|
+
ensure
|
162
|
+
self.suppressed_turbo_broadcasts = original
|
163
|
+
end
|
164
|
+
|
165
|
+
def suppressed_turbo_broadcasts?
|
166
|
+
suppressed_turbo_broadcasts
|
167
|
+
end
|
119
168
|
end
|
120
169
|
|
121
170
|
# Remove this broadcastable model from the dom for subscribers of the stream name identified by the passed streamables.
|
@@ -124,7 +173,7 @@ module Turbo::Broadcastable
|
|
124
173
|
# # Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
|
125
174
|
# clearance.broadcast_remove_to examiner.identity, :clearances
|
126
175
|
def broadcast_remove_to(*streamables, target: self)
|
127
|
-
Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target)
|
176
|
+
Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target) unless suppressed_turbo_broadcasts?
|
128
177
|
end
|
129
178
|
|
130
179
|
# Same as <tt>#broadcast_remove_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -143,7 +192,7 @@ module Turbo::Broadcastable
|
|
143
192
|
# # to the stream named "identity:2:clearances"
|
144
193
|
# clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
|
145
194
|
def broadcast_replace_to(*streamables, **rendering)
|
146
|
-
Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
|
195
|
+
Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
147
196
|
end
|
148
197
|
|
149
198
|
# Same as <tt>#broadcast_replace_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -162,7 +211,7 @@ module Turbo::Broadcastable
|
|
162
211
|
# # to the stream named "identity:2:clearances"
|
163
212
|
# clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
|
164
213
|
def broadcast_update_to(*streamables, **rendering)
|
165
|
-
Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
|
214
|
+
Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
166
215
|
end
|
167
216
|
|
168
217
|
# Same as <tt>#broadcast_update_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -215,7 +264,7 @@ module Turbo::Broadcastable
|
|
215
264
|
# clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
|
216
265
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
217
266
|
def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
|
218
|
-
Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
|
267
|
+
Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
219
268
|
end
|
220
269
|
|
221
270
|
# Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -236,7 +285,7 @@ module Turbo::Broadcastable
|
|
236
285
|
# clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
|
237
286
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
238
287
|
def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
|
239
|
-
Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
|
288
|
+
Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
240
289
|
end
|
241
290
|
|
242
291
|
# Same as <tt>#broadcast_prepend_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -244,24 +293,32 @@ module Turbo::Broadcastable
|
|
244
293
|
broadcast_prepend_to self, target: target, **rendering
|
245
294
|
end
|
246
295
|
|
296
|
+
def broadcast_refresh_to(*streamables)
|
297
|
+
Turbo::StreamsChannel.broadcast_refresh_to(*streamables) unless suppressed_turbo_broadcasts?
|
298
|
+
end
|
299
|
+
|
300
|
+
def broadcast_refresh
|
301
|
+
broadcast_refresh_to self
|
302
|
+
end
|
303
|
+
|
247
304
|
# Broadcast a named <tt>action</tt>, allowing for dynamic dispatch, instead of using the concrete action methods. Examples:
|
248
305
|
#
|
249
306
|
# # Sends <turbo-stream action="prepend" target="clearances"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
|
250
307
|
# # to the stream named "identity:2:clearances"
|
251
308
|
# clearance.broadcast_action_to examiner.identity, :clearances, action: :prepend, target: "clearances"
|
252
|
-
def broadcast_action_to(*streamables, action:, target: broadcast_target_default, **rendering)
|
253
|
-
Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, target: target, **broadcast_rendering_with_defaults(rendering))
|
309
|
+
def broadcast_action_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
|
310
|
+
Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
254
311
|
end
|
255
312
|
|
256
313
|
# Same as <tt>#broadcast_action_to</tt>, but the designated stream is automatically set to the current model.
|
257
|
-
def broadcast_action(action, target: broadcast_target_default, **rendering)
|
258
|
-
broadcast_action_to self, action: action, target: target, **rendering
|
314
|
+
def broadcast_action(action, target: broadcast_target_default, attributes: {}, **rendering)
|
315
|
+
broadcast_action_to self, action: action, target: target, attributes: attributes, **rendering
|
259
316
|
end
|
260
317
|
|
261
318
|
|
262
319
|
# Same as <tt>broadcast_replace_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
263
320
|
def broadcast_replace_later_to(*streamables, **rendering)
|
264
|
-
Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
|
321
|
+
Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
265
322
|
end
|
266
323
|
|
267
324
|
# Same as <tt>#broadcast_replace_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -271,7 +328,7 @@ module Turbo::Broadcastable
|
|
271
328
|
|
272
329
|
# Same as <tt>broadcast_update_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
273
330
|
def broadcast_update_later_to(*streamables, **rendering)
|
274
|
-
Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
|
331
|
+
Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
275
332
|
end
|
276
333
|
|
277
334
|
# Same as <tt>#broadcast_update_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -281,7 +338,7 @@ module Turbo::Broadcastable
|
|
281
338
|
|
282
339
|
# Same as <tt>broadcast_append_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
283
340
|
def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
|
284
|
-
Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
|
341
|
+
Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
285
342
|
end
|
286
343
|
|
287
344
|
# Same as <tt>#broadcast_append_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -291,7 +348,7 @@ module Turbo::Broadcastable
|
|
291
348
|
|
292
349
|
# Same as <tt>broadcast_prepend_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
293
350
|
def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
|
294
|
-
Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
|
351
|
+
Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
295
352
|
end
|
296
353
|
|
297
354
|
# Same as <tt>#broadcast_prepend_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -299,14 +356,22 @@ module Turbo::Broadcastable
|
|
299
356
|
broadcast_prepend_later_to self, target: target, **rendering
|
300
357
|
end
|
301
358
|
|
359
|
+
def broadcast_refresh_later_to(*streamables)
|
360
|
+
Turbo::StreamsChannel.broadcast_refresh_later_to(*streamables, request_id: Turbo.current_request_id) unless suppressed_turbo_broadcasts?
|
361
|
+
end
|
362
|
+
|
363
|
+
def broadcast_refresh_later
|
364
|
+
broadcast_refresh_later_to self
|
365
|
+
end
|
366
|
+
|
302
367
|
# Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
303
|
-
def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, **rendering)
|
304
|
-
Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, **broadcast_rendering_with_defaults(rendering))
|
368
|
+
def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
|
369
|
+
Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
305
370
|
end
|
306
371
|
|
307
372
|
# Same as <tt>#broadcast_action_later_to</tt>, but the designated stream is automatically set to the current model.
|
308
|
-
def broadcast_action_later(action:, target: broadcast_target_default, **rendering)
|
309
|
-
broadcast_action_later_to self, action: action, target: target, **rendering
|
373
|
+
def broadcast_action_later(action:, target: broadcast_target_default, attributes: {}, **rendering)
|
374
|
+
broadcast_action_later_to self, action: action, target: target, attributes: attributes, **rendering
|
310
375
|
end
|
311
376
|
|
312
377
|
# Render a turbo stream template with this broadcastable model passed as the local variable. Example:
|
@@ -337,7 +402,7 @@ module Turbo::Broadcastable
|
|
337
402
|
# desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
|
338
403
|
# be using `broadcast_render_later_to`, unless you specifically know why synchronous rendering is needed.
|
339
404
|
def broadcast_render_to(*streamables, **rendering)
|
340
|
-
Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering))
|
405
|
+
Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
341
406
|
end
|
342
407
|
|
343
408
|
# Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
@@ -348,7 +413,7 @@ module Turbo::Broadcastable
|
|
348
413
|
# Same as <tt>broadcast_render_later</tt> but run with the added option of naming the stream using the passed
|
349
414
|
# <tt>streamables</tt>.
|
350
415
|
def broadcast_render_later_to(*streamables, **rendering)
|
351
|
-
Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering))
|
416
|
+
Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
352
417
|
end
|
353
418
|
|
354
419
|
|
@@ -361,12 +426,14 @@ module Turbo::Broadcastable
|
|
361
426
|
options.tap do |o|
|
362
427
|
# Add the current instance into the locals with the element name (which is the un-namespaced name)
|
363
428
|
# as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
|
364
|
-
o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self)
|
429
|
+
o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self, request_id: Turbo.current_request_id).compact
|
365
430
|
|
366
431
|
if o[:html] || o[:partial]
|
367
432
|
return o
|
368
433
|
elsif o[:template] || o[:renderable]
|
369
434
|
o[:layout] = false
|
435
|
+
elsif o[:render] == false
|
436
|
+
return o
|
370
437
|
else
|
371
438
|
# if none of these options are passed in, it will set a partial from #to_partial_path
|
372
439
|
o[:partial] ||= to_partial_path
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Turbo::Debouncer
|
2
|
+
attr_reader :delay, :scheduled_task
|
3
|
+
|
4
|
+
DEFAULT_DELAY = 0.5
|
5
|
+
|
6
|
+
def initialize(delay: DEFAULT_DELAY)
|
7
|
+
@delay = delay
|
8
|
+
@scheduled_task = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def debounce(&block)
|
12
|
+
scheduled_task&.cancel unless scheduled_task&.complete?
|
13
|
+
@scheduled_task = Concurrent::ScheduledTask.execute(delay, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def wait
|
17
|
+
scheduled_task&.wait(wait_timeout)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def wait_timeout
|
22
|
+
delay + 1
|
23
|
+
end
|
24
|
+
end
|
@@ -22,6 +22,24 @@
|
|
22
22
|
# <%= turbo_stream.append dom_id(topic_merge) do %>
|
23
23
|
# <%= link_to topic_merge.topic.name, topic_path(topic_merge.topic) %>
|
24
24
|
# <% end %>
|
25
|
+
#
|
26
|
+
# To integrate with custom actions, extend this class in response to the :turbo_streams_tag_builder load hook:
|
27
|
+
#
|
28
|
+
# ActiveSupport.on_load :turbo_streams_tag_builder do
|
29
|
+
# def highlight(target)
|
30
|
+
# action :highlight, target
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def highlight_all(targets)
|
34
|
+
# action_all :highlight, targets
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# turbo_stream.highlight "my-element"
|
39
|
+
# # => <turbo-stream action="highlight" target="my-element"><template></template></turbo-stream>
|
40
|
+
#
|
41
|
+
# turbo_stream.highlight_all ".my-selector"
|
42
|
+
# # => <turbo-stream action="highlight" targets=".my-selector"><template></template></turbo-stream>
|
25
43
|
class Turbo::Streams::TagBuilder
|
26
44
|
include Turbo::Streams::ActionHelper
|
27
45
|
|
@@ -246,4 +264,6 @@ class Turbo::Streams::TagBuilder
|
|
246
264
|
@view_context.render(partial: record, formats: :html)
|
247
265
|
end
|
248
266
|
end
|
267
|
+
|
268
|
+
ActiveSupport.run_load_hooks :turbo_streams_tag_builder, self
|
249
269
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# A decorated debouncer that will store instances in the current thread clearing them
|
2
|
+
# after the debounced logic triggers.
|
3
|
+
class Turbo::ThreadDebouncer
|
4
|
+
delegate :wait, to: :debouncer
|
5
|
+
|
6
|
+
def self.for(key, delay: Turbo::Debouncer::DEFAULT_DELAY)
|
7
|
+
Thread.current[key] ||= new(key, Thread.current, delay: delay)
|
8
|
+
end
|
9
|
+
|
10
|
+
private_class_method :new
|
11
|
+
|
12
|
+
def initialize(key, thread, delay: )
|
13
|
+
@key = key
|
14
|
+
@debouncer = Turbo::Debouncer.new(delay: delay)
|
15
|
+
@thread = thread
|
16
|
+
end
|
17
|
+
|
18
|
+
def debounce
|
19
|
+
debouncer.debounce do
|
20
|
+
yield.tap do
|
21
|
+
thread[key] = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
attr_reader :key, :debouncer, :thread
|
28
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# FIXME: Offer flag to opt out of these native routes
|
2
1
|
Rails.application.routes.draw do
|
3
2
|
get "recede_historical_location" => "turbo/native/navigation#recede", as: :turbo_recede_historical_location
|
4
3
|
get "resume_historical_location" => "turbo/native/navigation#resume", as: :turbo_resume_historical_location
|
@@ -2,4 +2,4 @@ say "Import Turbo"
|
|
2
2
|
append_to_file "app/javascript/application.js", %(import "@hotwired/turbo-rails"\n)
|
3
3
|
|
4
4
|
say "Pin Turbo"
|
5
|
-
append_to_file "config/importmap.rb", %(pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
5
|
+
append_to_file "config/importmap.rb", %(pin "@hotwired/turbo-rails", to: "turbo.min.js"\n)
|
@@ -64,7 +64,7 @@ module Turbo
|
|
64
64
|
else
|
65
65
|
broadcasts = "Turbo Stream broadcast".pluralize(count)
|
66
66
|
|
67
|
-
assert count == payloads.count, "Expected #{count} #{broadcasts} on #{stream_name.inspect}, but there were
|
67
|
+
assert count == payloads.count, "Expected #{count} #{broadcasts} on #{stream_name.inspect}, but there were #{payloads.count}"
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
data/lib/turbo/engine.rb
CHANGED
@@ -46,6 +46,12 @@ module Turbo
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
initializer "turbo.request_id_tracking" do
|
50
|
+
ActiveSupport.on_load(:action_controller) do
|
51
|
+
include Turbo::RequestIdTracking
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
49
55
|
initializer "turbo.broadcastable" do
|
50
56
|
ActiveSupport.on_load(:active_record) do
|
51
57
|
include Turbo::Broadcastable
|
data/lib/turbo/version.rb
CHANGED
data/lib/turbo-rails.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require "turbo/engine"
|
2
|
+
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
2
3
|
|
3
4
|
module Turbo
|
4
5
|
extend ActiveSupport::Autoload
|
5
6
|
|
6
7
|
mattr_accessor :draw_routes, default: true
|
7
8
|
|
9
|
+
thread_mattr_accessor :current_request_id
|
10
|
+
|
8
11
|
class << self
|
9
12
|
attr_writer :signed_stream_verifier_key
|
10
13
|
|
@@ -15,5 +18,12 @@ module Turbo
|
|
15
18
|
def signed_stream_verifier_key
|
16
19
|
@signed_stream_verifier_key or raise ArgumentError, "Turbo requires a signed_stream_verifier_key"
|
17
20
|
end
|
21
|
+
|
22
|
+
def with_request_id(request_id)
|
23
|
+
old_request_id, self.current_request_id = self.current_request_id, request_id
|
24
|
+
yield
|
25
|
+
ensure
|
26
|
+
self.current_request_id = old_request_id
|
27
|
+
end
|
18
28
|
end
|
19
29
|
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:
|
4
|
+
version: 2.0.0
|
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:
|
13
|
+
date: 2024-02-07 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activejob
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- app/channels/turbo/streams/broadcasts.rb
|
70
70
|
- app/channels/turbo/streams/stream_name.rb
|
71
71
|
- app/channels/turbo/streams_channel.rb
|
72
|
+
- app/controllers/concerns/turbo/request_id_tracking.rb
|
72
73
|
- app/controllers/turbo/frames/frame_request.rb
|
73
74
|
- app/controllers/turbo/native/navigation.rb
|
74
75
|
- app/controllers/turbo/native/navigation_controller.rb
|
@@ -85,8 +86,11 @@ files:
|
|
85
86
|
- app/javascript/turbo/snakeize.js
|
86
87
|
- app/jobs/turbo/streams/action_broadcast_job.rb
|
87
88
|
- app/jobs/turbo/streams/broadcast_job.rb
|
89
|
+
- app/jobs/turbo/streams/broadcast_stream_job.rb
|
88
90
|
- app/models/concerns/turbo/broadcastable.rb
|
91
|
+
- app/models/turbo/debouncer.rb
|
89
92
|
- app/models/turbo/streams/tag_builder.rb
|
93
|
+
- app/models/turbo/thread_debouncer.rb
|
90
94
|
- app/views/layouts/turbo_rails/frame.html.erb
|
91
95
|
- config/routes.rb
|
92
96
|
- lib/install/turbo_needs_redis.rb
|