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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: