turbo-rails 1.5.0 → 2.0.0.pre.beta.1

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.
@@ -33,9 +33,14 @@ module Turbo::Streams::Broadcasts
33
33
  broadcast_action_to(*streamables, action: :prepend, **opts)
34
34
  end
35
35
 
36
- def broadcast_action_to(*streamables, action:, target: nil, targets: nil, **rendering)
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 broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, **rendering)
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
@@ -26,4 +26,12 @@ module Turbo::DriveHelper
26
26
  def turbo_page_requires_reload
27
27
  provide :head, tag.meta(name: "turbo-visit-control", content: "reload")
28
28
  end
29
+
30
+ def turbo_refreshes_with(method: :replace, scroll: :reset)
31
+ raise ArgumentError, "Invalid refresh option '#{method}'" unless method.in?(%i[ replace morph ])
32
+ raise ArgumentError, "Invalid scroll option '#{scroll}'" unless scroll.in?(%i[ reset preserve ])
33
+
34
+ provide :head, tag.meta(name: "turbo-refresh-method", content: method)
35
+ provide :head, tag.meta(name: "turbo-refresh-scroll", content: scroll)
36
+ end
29
37
  end
@@ -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.first
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 == :remove ? "" : tag.template(template.to_s.html_safe)
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
@@ -0,0 +1,7 @@
1
+ class Turbo::Streams::BroadcastStreamJob < ActiveJob::Base
2
+ discard_on ActiveJob::DeserializationError
3
+
4
+ def perform(stream, content:)
5
+ Turbo::StreamsChannel.broadcast_stream_to(stream, content: content)
6
+ end
7
+ 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,34 @@ 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.
146
+ def broadcasts_refreshes
147
+ after_commit -> { broadcast_refresh_later }
148
+ end
149
+
115
150
  # All default targets will use the return of this method. Overwrite if you want something else than <tt>model_name.plural</tt>.
116
151
  def broadcast_target_default
117
152
  model_name.plural
118
153
  end
154
+
155
+ # Executes +block+ preventing both synchronous and asynchronous broadcasts from this model.
156
+ def suppressing_turbo_broadcasts(&block)
157
+ original, self.suppressed_turbo_broadcasts = self.suppressed_turbo_broadcasts, true
158
+ yield
159
+ ensure
160
+ self.suppressed_turbo_broadcasts = original
161
+ end
162
+
163
+ def suppressed_turbo_broadcasts?
164
+ suppressed_turbo_broadcasts
165
+ end
119
166
  end
120
167
 
121
168
  # Remove this broadcastable model from the dom for subscribers of the stream name identified by the passed streamables.
@@ -124,7 +171,7 @@ module Turbo::Broadcastable
124
171
  # # Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
125
172
  # clearance.broadcast_remove_to examiner.identity, :clearances
126
173
  def broadcast_remove_to(*streamables, target: self)
127
- Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target)
174
+ Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target) unless suppressed_turbo_broadcasts?
128
175
  end
129
176
 
130
177
  # Same as <tt>#broadcast_remove_to</tt>, but the designated stream is automatically set to the current model.
@@ -143,7 +190,7 @@ module Turbo::Broadcastable
143
190
  # # to the stream named "identity:2:clearances"
144
191
  # clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
145
192
  def broadcast_replace_to(*streamables, **rendering)
146
- Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
193
+ Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
147
194
  end
148
195
 
149
196
  # Same as <tt>#broadcast_replace_to</tt>, but the designated stream is automatically set to the current model.
@@ -162,7 +209,7 @@ module Turbo::Broadcastable
162
209
  # # to the stream named "identity:2:clearances"
163
210
  # clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
164
211
  def broadcast_update_to(*streamables, **rendering)
165
- Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
212
+ Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
166
213
  end
167
214
 
168
215
  # Same as <tt>#broadcast_update_to</tt>, but the designated stream is automatically set to the current model.
@@ -215,7 +262,7 @@ module Turbo::Broadcastable
215
262
  # clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
216
263
  # partial: "clearances/other_partial", locals: { a: 1 }
217
264
  def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
218
- Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
265
+ Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
219
266
  end
220
267
 
221
268
  # Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
@@ -236,7 +283,7 @@ module Turbo::Broadcastable
236
283
  # clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
237
284
  # partial: "clearances/other_partial", locals: { a: 1 }
238
285
  def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
239
- Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
286
+ Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
240
287
  end
241
288
 
242
289
  # Same as <tt>#broadcast_prepend_to</tt>, but the designated stream is automatically set to the current model.
@@ -244,24 +291,32 @@ module Turbo::Broadcastable
244
291
  broadcast_prepend_to self, target: target, **rendering
245
292
  end
246
293
 
294
+ def broadcast_refresh_to(*streamables)
295
+ Turbo::StreamsChannel.broadcast_refresh_to *streamables unless suppressed_turbo_broadcasts?
296
+ end
297
+
298
+ def broadcast_refresh
299
+ broadcast_refresh_to self
300
+ end
301
+
247
302
  # Broadcast a named <tt>action</tt>, allowing for dynamic dispatch, instead of using the concrete action methods. Examples:
248
303
  #
249
304
  # # Sends <turbo-stream action="prepend" target="clearances"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
250
305
  # # to the stream named "identity:2:clearances"
251
306
  # 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))
307
+ def broadcast_action_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
308
+ Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
254
309
  end
255
310
 
256
311
  # 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
312
+ def broadcast_action(action, target: broadcast_target_default, attributes: {}, **rendering)
313
+ broadcast_action_to self, action: action, target: target, attributes: attributes, **rendering
259
314
  end
260
315
 
261
316
 
262
317
  # Same as <tt>broadcast_replace_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
263
318
  def broadcast_replace_later_to(*streamables, **rendering)
264
- Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
319
+ Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
265
320
  end
266
321
 
267
322
  # Same as <tt>#broadcast_replace_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -271,7 +326,7 @@ module Turbo::Broadcastable
271
326
 
272
327
  # Same as <tt>broadcast_update_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
273
328
  def broadcast_update_later_to(*streamables, **rendering)
274
- Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
329
+ Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
275
330
  end
276
331
 
277
332
  # Same as <tt>#broadcast_update_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -281,7 +336,7 @@ module Turbo::Broadcastable
281
336
 
282
337
  # Same as <tt>broadcast_append_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
283
338
  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))
339
+ Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
285
340
  end
286
341
 
287
342
  # Same as <tt>#broadcast_append_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -291,7 +346,7 @@ module Turbo::Broadcastable
291
346
 
292
347
  # Same as <tt>broadcast_prepend_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
293
348
  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))
349
+ Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
295
350
  end
296
351
 
297
352
  # Same as <tt>#broadcast_prepend_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -299,14 +354,22 @@ module Turbo::Broadcastable
299
354
  broadcast_prepend_later_to self, target: target, **rendering
300
355
  end
301
356
 
357
+ def broadcast_refresh_later_to(*streamables)
358
+ Turbo::StreamsChannel.broadcast_refresh_later_to(*streamables, request_id: Turbo.current_request_id) unless suppressed_turbo_broadcasts?
359
+ end
360
+
361
+ def broadcast_refresh_later
362
+ broadcast_refresh_later_to self
363
+ end
364
+
302
365
  # 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))
366
+ def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
367
+ Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
305
368
  end
306
369
 
307
370
  # 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
371
+ def broadcast_action_later(action:, target: broadcast_target_default, attributes: {}, **rendering)
372
+ broadcast_action_later_to self, action: action, target: target, attributes: attributes, **rendering
310
373
  end
311
374
 
312
375
  # Render a turbo stream template with this broadcastable model passed as the local variable. Example:
@@ -337,7 +400,7 @@ module Turbo::Broadcastable
337
400
  # desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
338
401
  # be using `broadcast_render_later_to`, unless you specifically know why synchronous rendering is needed.
339
402
  def broadcast_render_to(*streamables, **rendering)
340
- Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering))
403
+ Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
341
404
  end
342
405
 
343
406
  # Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
@@ -348,7 +411,7 @@ module Turbo::Broadcastable
348
411
  # Same as <tt>broadcast_render_later</tt> but run with the added option of naming the stream using the passed
349
412
  # <tt>streamables</tt>.
350
413
  def broadcast_render_later_to(*streamables, **rendering)
351
- Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering))
414
+ Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
352
415
  end
353
416
 
354
417
 
@@ -361,12 +424,14 @@ module Turbo::Broadcastable
361
424
  options.tap do |o|
362
425
  # Add the current instance into the locals with the element name (which is the un-namespaced name)
363
426
  # 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)
427
+ o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self, request_id: Turbo.current_request_id).compact
365
428
 
366
429
  if o[:html] || o[:partial]
367
430
  return o
368
431
  elsif o[:template] || o[:renderable]
369
432
  o[:layout] = false
433
+ elsif o[:render] == false
434
+ return o
370
435
  else
371
436
  # if none of these options are passed in, it will set a partial from #to_partial_path
372
437
  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
@@ -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
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
@@ -1,3 +1,3 @@
1
1
  module Turbo
2
- VERSION = "1.5.0"
2
+ VERSION = "2.0.0-beta.1"
3
3
  end
data/lib/turbo-rails.rb CHANGED
@@ -5,6 +5,8 @@ module Turbo
5
5
 
6
6
  mattr_accessor :draw_routes, default: true
7
7
 
8
+ thread_mattr_accessor :current_request_id
9
+
8
10
  class << self
9
11
  attr_writer :signed_stream_verifier_key
10
12
 
@@ -15,5 +17,12 @@ module Turbo
15
17
  def signed_stream_verifier_key
16
18
  @signed_stream_verifier_key or raise ArgumentError, "Turbo requires a signed_stream_verifier_key"
17
19
  end
20
+
21
+ def with_request_id(request_id)
22
+ old_request_id, self.current_request_id = self.current_request_id, request_id
23
+ yield
24
+ ensure
25
+ self.current_request_id = old_request_id
26
+ end
18
27
  end
19
28
  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: 1.5.0
4
+ version: 2.0.0.pre.beta.1
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: 2023-10-11 00:00:00.000000000 Z
13
+ date: 2023-11-23 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
@@ -115,9 +119,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
119
  version: 2.6.0
116
120
  required_rubygems_version: !ruby/object:Gem::Requirement
117
121
  requirements:
118
- - - ">="
122
+ - - ">"
119
123
  - !ruby/object:Gem::Version
120
- version: '0'
124
+ version: 1.3.1
121
125
  requirements: []
122
126
  rubygems_version: 3.4.15
123
127
  signing_key: