turbo-rails 0.5.9 → 0.7.14

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.
@@ -5,49 +5,71 @@
5
5
  module Turbo::Streams::Broadcasts
6
6
  include Turbo::Streams::ActionHelper
7
7
 
8
- def broadcast_remove_to(*streamables, target:)
9
- broadcast_action_to *streamables, action: :remove, target: target
8
+ def broadcast_remove_to(*streamables, **opts)
9
+ broadcast_action_to(*streamables, action: :remove, **opts)
10
10
  end
11
11
 
12
- def broadcast_replace_to(*streamables, target:, **rendering)
13
- broadcast_action_to *streamables, action: :replace, target: target, **rendering
12
+ def broadcast_replace_to(*streamables, **opts)
13
+ broadcast_action_to(*streamables, action: :replace, **opts)
14
14
  end
15
15
 
16
- def broadcast_append_to(*streamables, target:, **rendering)
17
- broadcast_action_to *streamables, action: :append, target: target, **rendering
16
+ def broadcast_update_to(*streamables, **opts)
17
+ broadcast_action_to(*streamables, action: :update, **opts)
18
18
  end
19
19
 
20
- def broadcast_prepend_to(*streamables, target:, **rendering)
21
- broadcast_action_to *streamables, action: :prepend, target: target, **rendering
20
+ def broadcast_before_to(*streamables, **opts)
21
+ broadcast_action_to(*streamables, action: :before, **opts)
22
22
  end
23
23
 
24
- def broadcast_action_to(*streamables, action:, target:, **rendering)
25
- broadcast_stream_to *streamables, content: turbo_stream_action_tag(action, target: target, template:
24
+ def broadcast_after_to(*streamables, **opts)
25
+ broadcast_action_to(*streamables, action: :after, **opts)
26
+ end
27
+
28
+ def broadcast_append_to(*streamables, **opts)
29
+ broadcast_action_to(*streamables, action: :append, **opts)
30
+ end
31
+
32
+ def broadcast_prepend_to(*streamables, **opts)
33
+ broadcast_action_to(*streamables, action: :prepend, **opts)
34
+ end
35
+
36
+ def broadcast_action_to(*streamables, action:, target: nil, targets: nil, **rendering)
37
+ broadcast_stream_to(*streamables, content: turbo_stream_action_tag(action, target: target, targets: targets, template:
26
38
  rendering.delete(:content) || (rendering.any? ? render_format(:html, **rendering) : nil)
27
- )
39
+ ))
28
40
  end
29
41
 
42
+ def broadcast_replace_later_to(*streamables, **opts)
43
+ broadcast_action_later_to(*streamables, action: :replace, **opts)
44
+ end
30
45
 
31
- def broadcast_replace_later_to(*streamables, target:, **rendering)
32
- broadcast_action_later_to *streamables, action: :replace, target: target, **rendering
46
+ def broadcast_update_later_to(*streamables, **opts)
47
+ broadcast_action_later_to(*streamables, action: :update, **opts)
33
48
  end
34
49
 
35
- def broadcast_append_later_to(*streamables, target:, **rendering)
36
- broadcast_action_later_to *streamables, action: :append, target: target, **rendering
50
+ def broadcast_before_later_to(*streamables, **opts)
51
+ broadcast_action_later_to(*streamables, action: :before, **opts)
37
52
  end
38
53
 
39
- def broadcast_prepend_later_to(*streamables, target:, **rendering)
40
- broadcast_action_later_to *streamables, action: :prepend, target: target, **rendering
54
+ def broadcast_after_later_to(*streamables, **opts)
55
+ broadcast_action_later_to(*streamables, action: :after, **opts)
41
56
  end
42
57
 
43
- def broadcast_action_later_to(*streamables, action:, target:, **rendering)
44
- Turbo::Streams::ActionBroadcastJob.perform_later \
45
- stream_name_from(streamables), action: action, target: target, **rendering
58
+ def broadcast_append_later_to(*streamables, **opts)
59
+ broadcast_action_later_to(*streamables, action: :append, **opts)
60
+ end
61
+
62
+ def broadcast_prepend_later_to(*streamables, **opts)
63
+ broadcast_action_later_to(*streamables, action: :prepend, **opts)
46
64
  end
47
65
 
66
+ def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, **rendering)
67
+ Turbo::Streams::ActionBroadcastJob.perform_later \
68
+ stream_name_from(streamables), action: action, target: target, targets: targets, **rendering
69
+ end
48
70
 
49
71
  def broadcast_render_to(*streamables, **rendering)
50
- broadcast_stream_to *streamables, content: render_format(:turbo_stream, **rendering)
72
+ broadcast_stream_to(*streamables, content: render_format(:turbo_stream, **rendering))
51
73
  end
52
74
 
53
75
  def broadcast_render_later_to(*streamables, **rendering)
@@ -16,12 +16,16 @@ module Turbo::FramesHelper
16
16
  # <%= turbo_frame_tag "tray", target: "other_tray" %>
17
17
  # # => <turbo-frame id="tray" target="other_tray"></turbo-frame>
18
18
  #
19
+ # <%= turbo_frame_tag "tray", src: tray_path(tray), loading: "lazy" %>
20
+ # # => <turbo-frame id="tray" src="http://example.com/trays/1" loading="lazy"></turbo-frame>
21
+ #
19
22
  # <%= turbo_frame_tag "tray" do %>
20
23
  # <div>My tray frame!</div>
21
24
  # <% end %>
22
25
  # # => <turbo-frame id="tray"><div>My tray frame!</div></turbo-frame>
23
26
  def turbo_frame_tag(id, src: nil, target: nil, **attributes, &block)
24
27
  id = id.respond_to?(:to_key) ? dom_id(id) : id
28
+ src = url_for(src) if src.present?
25
29
 
26
30
  tag.turbo_frame(**attributes.merge(id: id, src: src, target: target).compact, &block)
27
31
  end
@@ -6,15 +6,27 @@ module Turbo::Streams::ActionHelper
6
6
  #
7
7
  # turbo_stream_action_tag "replace", target: "message_1", template: %(<div id="message_1">Hello!</div>)
8
8
  # # => <turbo-stream action="replace" target="message_1"><template><div id="message_1">Hello!</div></template></turbo-stream>
9
- def turbo_stream_action_tag(action, target:, template: nil)
10
- target = convert_to_turbo_stream_dom_id(target)
9
+ #
10
+ # turbo_stream_action_tag "replace", targets: "message_1", template: %(<div id="message_1">Hello!</div>)
11
+ # # => <turbo-stream action="replace" targets="message_1"><template><div id="message_1">Hello!</div></template></turbo-stream>
12
+ def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil)
11
13
  template = action.to_sym == :remove ? "" : "<template>#{template}</template>"
12
14
 
13
- %(<turbo-stream action="#{action}" target="#{target}">#{template}</turbo-stream>).html_safe
15
+ if target = convert_to_turbo_stream_dom_id(target)
16
+ %(<turbo-stream action="#{action}" target="#{target}">#{template}</turbo-stream>).html_safe
17
+ elsif targets = convert_to_turbo_stream_dom_id(targets, include_selector: true)
18
+ %(<turbo-stream action="#{action}" targets="#{targets}">#{template}</turbo-stream>).html_safe
19
+ else
20
+ raise ArgumentError, "target or targets must be supplied"
21
+ end
14
22
  end
15
23
 
16
24
  private
17
- def convert_to_turbo_stream_dom_id(target)
18
- target.respond_to?(:to_key) ? ActionView::RecordIdentifier.dom_id(target) : target
25
+ def convert_to_turbo_stream_dom_id(target, include_selector: false)
26
+ if target.respond_to?(:to_key)
27
+ [ ("#" if include_selector), ActionView::RecordIdentifier.dom_id(target) ].compact.join
28
+ else
29
+ target
30
+ end
19
31
  end
20
32
  end
@@ -1,5 +1,5 @@
1
1
  module Turbo::StreamsHelper
2
- # Returns a new <tt>Turbo::Streams::TagBuilder</tt> object that accepts stream actions and renders them them as
2
+ # Returns a new <tt>Turbo::Streams::TagBuilder</tt> object that accepts stream actions and renders them as
3
3
  # the template tags needed to send across the wire. This object is automatically yielded to turbo_stream.erb templates.
4
4
  #
5
5
  # When responding to HTTP requests, controllers can declare `turbo_stream` format response templates in that same
@@ -39,7 +39,9 @@ module Turbo::StreamsHelper
39
39
  # The example above will process all turbo streams sent to a stream name like <tt>account:5:entries</tt>
40
40
  # (when Current.account.id = 5). Updates to this stream can be sent like
41
41
  # <tt>entry.broadcast_append_to entry.account, :entries, target: "entries"</tt>.
42
- def turbo_stream_from(*streamables)
43
- tag.turbo_cable_stream_source channel: "Turbo::StreamsChannel", "signed-stream-name": Turbo::StreamsChannel.signed_stream_name(streamables)
42
+ def turbo_stream_from(*streamables, **attributes)
43
+ attributes[:channel] = "Turbo::StreamsChannel"
44
+ attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
45
+ tag.turbo_cable_stream_source(**attributes)
44
46
  end
45
47
  end
@@ -1,15 +1,18 @@
1
1
  let consumer
2
2
 
3
3
  export async function getConsumer() {
4
- if (consumer) return consumer
5
- const { createConsumer } = await import("@rails/actioncable/src")
6
- return setConsumer(createConsumer())
4
+ return consumer || setConsumer(createConsumer().then(setConsumer))
7
5
  }
8
6
 
9
7
  export function setConsumer(newConsumer) {
10
8
  return consumer = newConsumer
11
9
  }
12
10
 
11
+ export async function createConsumer() {
12
+ const { createConsumer } = await import(/* webpackChunkName: "actioncable" */ "@rails/actioncable/src")
13
+ return createConsumer()
14
+ }
15
+
13
16
  export async function subscribeTo(channel, mixin) {
14
17
  const { subscriptions } = await getConsumer()
15
18
  return subscriptions.create(channel, mixin)
@@ -1,5 +1,7 @@
1
1
  # The job that powers all the <tt>broadcast_$action_later</tt> broadcasts available in <tt>Turbo::Streams::Broadcasts</tt>.
2
2
  class Turbo::Streams::ActionBroadcastJob < ActiveJob::Base
3
+ discard_on ActiveJob::DeserializationError
4
+
3
5
  def perform(stream, action:, target:, **rendering)
4
6
  Turbo::StreamsChannel.broadcast_action_to stream, action: action, target: target, **rendering
5
7
  end
@@ -1,6 +1,8 @@
1
1
  # The job that powers the <tt>broadcast_render_later_to</tt> available in <tt>Turbo::Streams::Broadcasts</tt> for rendering
2
2
  # turbo stream templates.
3
3
  class Turbo::Streams::BroadcastJob < ActiveJob::Base
4
+ discard_on ActiveJob::DeserializationError
5
+
4
6
  def perform(stream, **rendering)
5
7
  Turbo::StreamsChannel.broadcast_render_to stream, **rendering
6
8
  end
@@ -39,7 +39,7 @@ module Turbo::Broadcastable
39
39
  module ClassMethods
40
40
  # Configures the model to broadcast creates, updates, and destroys to a stream name derived at runtime by the
41
41
  # <tt>stream</tt> symbol invocation. By default, the creates are appended to a dom id target name derived from
42
- # the model's plural name. The insertion can also be made to be a prepend by overwriting <tt>insertion</tt> and
42
+ # the model's plural name. The insertion can also be made to be a prepend by overwriting <tt>inserts_by</tt> and
43
43
  # the target dom id overwritten by passing <tt>target</tt>. Examples:
44
44
  #
45
45
  # class Message < ApplicationRecord
@@ -52,14 +52,14 @@ module Turbo::Broadcastable
52
52
  # broadcasts_to ->(message) { [ message.board, :messages ] }, inserts_by: :prepend, target: "board_messages"
53
53
  # end
54
54
  def broadcasts_to(stream, inserts_by: :append, target: broadcast_target_default)
55
- after_create_commit -> { broadcast_action_later_to stream.try(:call, self) || send(stream), action: inserts_by, target: target }
55
+ after_create_commit -> { broadcast_action_later_to stream.try(:call, self) || send(stream), action: inserts_by, target: target.try(:call, self) || target }
56
56
  after_update_commit -> { broadcast_replace_later_to stream.try(:call, self) || send(stream) }
57
57
  after_destroy_commit -> { broadcast_remove_to stream.try(:call, self) || send(stream) }
58
58
  end
59
59
 
60
60
  # Same as <tt>#broadcasts_to</tt>, but the designated stream is automatically set to the current model.
61
61
  def broadcasts(inserts_by: :append, target: broadcast_target_default)
62
- after_create_commit -> { broadcast_action_later action: inserts_by, target: target }
62
+ after_create_commit -> { broadcast_action_later action: inserts_by, target: target.try(:call, self) || target }
63
63
  after_update_commit -> { broadcast_replace_later }
64
64
  after_destroy_commit -> { broadcast_remove }
65
65
  end
@@ -75,8 +75,8 @@ module Turbo::Broadcastable
75
75
  #
76
76
  # # Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
77
77
  # clearance.broadcast_remove_to examiner.identity, :clearances
78
- def broadcast_remove_to(*streamables)
79
- Turbo::StreamsChannel.broadcast_remove_to *streamables, target: self
78
+ def broadcast_remove_to(*streamables, target: self)
79
+ Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target)
80
80
  end
81
81
 
82
82
  # Same as <tt>#broadcast_remove_to</tt>, but the designated stream is automatically set to the current model.
@@ -95,7 +95,7 @@ module Turbo::Broadcastable
95
95
  # # to the stream named "identity:2:clearances"
96
96
  # clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
97
97
  def broadcast_replace_to(*streamables, **rendering)
98
- Turbo::StreamsChannel.broadcast_replace_to *streamables, target: self, **broadcast_rendering_with_defaults(rendering)
98
+ Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
99
99
  end
100
100
 
101
101
  # Same as <tt>#broadcast_replace_to</tt>, but the designated stream is automatically set to the current model.
@@ -103,6 +103,57 @@ module Turbo::Broadcastable
103
103
  broadcast_replace_to self, **rendering
104
104
  end
105
105
 
106
+ # Update this broadcastable model in the dom for subscribers of the stream name identified by the passed
107
+ # <tt>streamables</tt>. The rendering parameters can be set by appending named arguments to the call. Examples:
108
+ #
109
+ # # Sends <turbo-stream action="update" target="clearance_5"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
110
+ # # to the stream named "identity:2:clearances"
111
+ # clearance.broadcast_update_to examiner.identity, :clearances
112
+ #
113
+ # # Sends <turbo-stream action="update" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
114
+ # # to the stream named "identity:2:clearances"
115
+ # clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
116
+ def broadcast_update_to(*streamables, **rendering)
117
+ Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
118
+ end
119
+
120
+ # Same as <tt>#broadcast_update_to</tt>, but the designated stream is automatically set to the current model.
121
+ def broadcast_update(**rendering)
122
+ broadcast_update_to self, **rendering
123
+ end
124
+
125
+ # Insert a rendering of this broadcastable model before the target identified by it's dom id passed as <tt>target</tt>
126
+ # for subscribers of the stream name identified by the passed <tt>streamables</tt>. The rendering parameters can be set by
127
+ # appending named arguments to the call. Examples:
128
+ #
129
+ # # Sends <turbo-stream action="before" target="clearance_5"><template><div id="clearance_4">My Clearance</div></template></turbo-stream>
130
+ # # to the stream named "identity:2:clearances"
131
+ # clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5"
132
+ #
133
+ # # Sends <turbo-stream action="before" target="clearance_5"><template><div id="clearance_4">Other partial</div></template></turbo-stream>
134
+ # # to the stream named "identity:2:clearances"
135
+ # clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5",
136
+ # partial: "clearances/other_partial", locals: { a: 1 }
137
+ def broadcast_before_to(*streamables, target:, **rendering)
138
+ Turbo::StreamsChannel.broadcast_before_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
139
+ end
140
+
141
+ # Insert a rendering of this broadcastable model after the target identified by it's dom id passed as <tt>target</tt>
142
+ # for subscribers of the stream name identified by the passed <tt>streamables</tt>. The rendering parameters can be set by
143
+ # appending named arguments to the call. Examples:
144
+ #
145
+ # # Sends <turbo-stream action="after" target="clearance_5"><template><div id="clearance_6">My Clearance</div></template></turbo-stream>
146
+ # # to the stream named "identity:2:clearances"
147
+ # clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5"
148
+ #
149
+ # # Sends <turbo-stream action="after" target="clearance_5"><template><div id="clearance_6">Other partial</div></template></turbo-stream>
150
+ # # to the stream named "identity:2:clearances"
151
+ # clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5",
152
+ # partial: "clearances/other_partial", locals: { a: 1 }
153
+ def broadcast_after_to(*streamables, target:, **rendering)
154
+ Turbo::StreamsChannel.broadcast_after_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
155
+ end
156
+
106
157
  # Append a rendering of this broadcastable model to the target identified by it's dom id passed as <tt>target</tt>
107
158
  # for subscribers of the stream name identified by the passed <tt>streamables</tt>. The rendering parameters can be set by
108
159
  # appending named arguments to the call. Examples:
@@ -116,7 +167,7 @@ module Turbo::Broadcastable
116
167
  # clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
117
168
  # partial: "clearances/other_partial", locals: { a: 1 }
118
169
  def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
119
- Turbo::StreamsChannel.broadcast_append_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
170
+ Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
120
171
  end
121
172
 
122
173
  # Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
@@ -137,10 +188,10 @@ module Turbo::Broadcastable
137
188
  # clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
138
189
  # partial: "clearances/other_partial", locals: { a: 1 }
139
190
  def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
140
- Turbo::StreamsChannel.broadcast_prepend_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
191
+ Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
141
192
  end
142
193
 
143
- # Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
194
+ # Same as <tt>#broadcast_prepend_to</tt>, but the designated stream is automatically set to the current model.
144
195
  def broadcast_prepend(target: broadcast_target_default, **rendering)
145
196
  broadcast_prepend_to self, target: target, **rendering
146
197
  end
@@ -162,7 +213,7 @@ module Turbo::Broadcastable
162
213
 
163
214
  # Same as <tt>broadcast_replace_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
164
215
  def broadcast_replace_later_to(*streamables, **rendering)
165
- Turbo::StreamsChannel.broadcast_replace_later_to *streamables, target: self, **broadcast_rendering_with_defaults(rendering)
216
+ Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
166
217
  end
167
218
 
168
219
  # Same as <tt>#broadcast_replace_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -170,9 +221,19 @@ module Turbo::Broadcastable
170
221
  broadcast_replace_later_to self, **rendering
171
222
  end
172
223
 
224
+ # Same as <tt>broadcast_update_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
225
+ def broadcast_update_later_to(*streamables, **rendering)
226
+ Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
227
+ end
228
+
229
+ # Same as <tt>#broadcast_update_later_to</tt>, but the designated stream is automatically set to the current model.
230
+ def broadcast_update_later(**rendering)
231
+ broadcast_update_later_to self, **rendering
232
+ end
233
+
173
234
  # Same as <tt>broadcast_append_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
174
235
  def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
175
- Turbo::StreamsChannel.broadcast_append_later_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
236
+ Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
176
237
  end
177
238
 
178
239
  # Same as <tt>#broadcast_append_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -182,7 +243,7 @@ module Turbo::Broadcastable
182
243
 
183
244
  # Same as <tt>broadcast_prepend_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
184
245
  def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
185
- Turbo::StreamsChannel.broadcast_prepend_later_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
246
+ Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
186
247
  end
187
248
 
188
249
  # Same as <tt>#broadcast_prepend_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -190,7 +251,7 @@ module Turbo::Broadcastable
190
251
  broadcast_prepend_later_to self, target: target, **rendering
191
252
  end
192
253
 
193
- # Same as <tt>broadcast_action_later_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
254
+ # Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
194
255
  def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, **rendering)
195
256
  Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, **broadcast_rendering_with_defaults(rendering))
196
257
  end
@@ -219,10 +280,10 @@ module Turbo::Broadcastable
219
280
  broadcast_render_later_to self, **rendering
220
281
  end
221
282
 
222
- # Same as <tt>broadcast_prepend_to</tt> but run with the added option of naming the stream using the passed
283
+ # Same as <tt>broadcast_render_later</tt> but run with the added option of naming the stream using the passed
223
284
  # <tt>streamables</tt>.
224
285
  def broadcast_render_later_to(*streamables, **rendering)
225
- Turbo::StreamsChannel.broadcast_render_later_to *streamables, **broadcast_rendering_with_defaults(rendering)
286
+ Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering))
226
287
  end
227
288
 
228
289
 
@@ -233,7 +294,9 @@ module Turbo::Broadcastable
233
294
 
234
295
  def broadcast_rendering_with_defaults(options)
235
296
  options.tap do |o|
236
- o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.singular.to_sym => self)
297
+ # Add the current instance into the locals with the element name (which is the un-namespaced name)
298
+ # as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
299
+ o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self)
237
300
  o[:partial] ||= to_partial_path
238
301
  end
239
302
  end
@@ -40,6 +40,16 @@ class Turbo::Streams::TagBuilder
40
40
  action :remove, target, allow_inferred_rendering: false
41
41
  end
42
42
 
43
+ # Removes the <tt>targets</tt> from the dom. The targets can either be a CSS selector string or an object that responds to
44
+ # <tt>to_key</tt>, which is then called and passed through <tt>ActionView::RecordIdentifier.dom_id</tt> (all Active Records
45
+ # do). Examples:
46
+ #
47
+ # <%= turbo_stream.remove_all ".clearance_item" %>
48
+ # <%= turbo_stream.remove_all clearance %>
49
+ def remove_all(targets)
50
+ action_all :remove, targets, allow_inferred_rendering: false
51
+ end
52
+
43
53
  # Replace the <tt>target</tt> in the dom with the either the <tt>content</tt> passed in, a rendering result determined
44
54
  # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
45
55
  #
@@ -53,6 +63,71 @@ class Turbo::Streams::TagBuilder
53
63
  action :replace, target, content, **rendering, &block
54
64
  end
55
65
 
66
+ # Replace the <tt>targets</tt> in the dom with the either the <tt>content</tt> passed in, a rendering result determined
67
+ # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
68
+ #
69
+ # <%= turbo_stream.replace_all ".clearance_item", "<div class='clearance_item'>Replace the dom target identified by the class clearance_item</div>" %>
70
+ # <%= turbo_stream.replace_all clearance %>
71
+ # <%= turbo_stream.replace_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
72
+ # <%= turbo_stream.replace_all ".clearance_item" do %>
73
+ # <div class='.clearance_item'>Replace the dom target identified by the class clearance_item</div>
74
+ # <% end %>
75
+ def replace_all(targets, content = nil, **rendering, &block)
76
+ action_all :replace, targets, content, **rendering, &block
77
+ end
78
+
79
+ # Insert the <tt>content</tt> passed in, a rendering result determined by the <tt>rendering</tt> keyword arguments,
80
+ # the content in the block, or the rendering of the target as a record before the <tt>target</tt> in the dom. Examples:
81
+ #
82
+ # <%= turbo_stream.before "clearance_5", "<div id='clearance_4'>Insert before the dom target identified by clearance_5</div>" %>
83
+ # <%= turbo_stream.before clearance %>
84
+ # <%= turbo_stream.before clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
85
+ # <%= turbo_stream.before "clearance_5" do %>
86
+ # <div id='clearance_4'>Insert before the dom target identified by clearance_5</div>
87
+ # <% end %>
88
+ def before(target, content = nil, **rendering, &block)
89
+ action :before, target, content, **rendering, &block
90
+ end
91
+
92
+ # Insert the <tt>content</tt> passed in, a rendering result determined by the <tt>rendering</tt> keyword arguments,
93
+ # the content in the block, or the rendering of the target as a record before the <tt>targets</tt> in the dom. Examples:
94
+ #
95
+ # <%= turbo_stream.before_all ".clearance_item", "<div class='clearance_item'>Insert before the dom target identified by the class clearance_item</div>" %>
96
+ # <%= turbo_stream.before_all clearance %>
97
+ # <%= turbo_stream.before_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
98
+ # <%= turbo_stream.before_all ".clearance_item" do %>
99
+ # <div class='clearance_item'>Insert before the dom target identified by clearance_item</div>
100
+ # <% end %>
101
+ def before_all(targets, content = nil, **rendering, &block)
102
+ action_all :before, targets, content, **rendering, &block
103
+ end
104
+
105
+ # Insert the <tt>content</tt> passed in, a rendering result determined by the <tt>rendering</tt> keyword arguments,
106
+ # the content in the block, or the rendering of the target as a record after the <tt>target</tt> in the dom. Examples:
107
+ #
108
+ # <%= turbo_stream.after "clearance_5", "<div id='clearance_6'>Insert after the dom target identified by clearance_5</div>" %>
109
+ # <%= turbo_stream.after clearance %>
110
+ # <%= turbo_stream.after clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
111
+ # <%= turbo_stream.after "clearance_5" do %>
112
+ # <div id='clearance_6'>Insert after the dom target identified by clearance_5</div>
113
+ # <% end %>
114
+ def after(target, content = nil, **rendering, &block)
115
+ action :after, target, content, **rendering, &block
116
+ end
117
+
118
+ # Insert the <tt>content</tt> passed in, a rendering result determined by the <tt>rendering</tt> keyword arguments,
119
+ # the content in the block, or the rendering of the target as a record after the <tt>targets</tt> in the dom. Examples:
120
+ #
121
+ # <%= turbo_stream.after_all ".clearance_item", "<div class='clearance_item'>Insert after the dom target identified by the class clearance_item</div>" %>
122
+ # <%= turbo_stream.after_all clearance %>
123
+ # <%= turbo_stream.after_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
124
+ # <%= turbo_stream.after_all "clearance_item" do %>
125
+ # <div class='clearance_item'>Insert after the dom target identified by the class clearance_item</div>
126
+ # <% end %>
127
+ def after_all(targets, content = nil, **rendering, &block)
128
+ action_all :after, targets, content, **rendering, &block
129
+ end
130
+
56
131
  # Update the <tt>target</tt> in the dom with the either the <tt>content</tt> passed in or a rendering result determined
57
132
  # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
58
133
  #
@@ -66,6 +141,19 @@ class Turbo::Streams::TagBuilder
66
141
  action :update, target, content, **rendering, &block
67
142
  end
68
143
 
144
+ # Update the <tt>targets</tt> in the dom with the either the <tt>content</tt> passed in or a rendering result determined
145
+ # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the targets as a record. Examples:
146
+ #
147
+ # <%= turbo_stream.update_all "clearance_item", "Update the content of the dom target identified by the class clearance_item" %>
148
+ # <%= turbo_stream.update_all clearance %>
149
+ # <%= turbo_stream.update_all clearance, partial: "clearances/new_clearance", locals: { title: "Hello" } %>
150
+ # <%= turbo_stream.update_all "clearance_item" do %>
151
+ # Update the content of the dom target identified by the class clearance_item
152
+ # <% end %>
153
+ def update_all(targets, content = nil, **rendering, &block)
154
+ action_all :update, targets, content, **rendering, &block
155
+ end
156
+
69
157
  # Append to the target in the dom identified with <tt>target</tt> either the <tt>content</tt> passed in or a
70
158
  # rendering result determined by the <tt>rendering</tt> keyword arguments, the content in the block,
71
159
  # or the rendering of the content as a record. Examples:
@@ -80,6 +168,20 @@ class Turbo::Streams::TagBuilder
80
168
  action :append, target, content, **rendering, &block
81
169
  end
82
170
 
171
+ # Append to the targets in the dom identified with <tt>targets</tt> either the <tt>content</tt> passed in or a
172
+ # rendering result determined by the <tt>rendering</tt> keyword arguments, the content in the block,
173
+ # or the rendering of the content as a record. Examples:
174
+ #
175
+ # <%= turbo_stream.append_all ".clearances", "<div class='clearance_item'>Append this to .clearance_group</div>" %>
176
+ # <%= turbo_stream.append_all ".clearances", clearance %>
177
+ # <%= turbo_stream.append_all ".clearances", partial: "clearances/new_clearance", locals: { clearance: clearance } %>
178
+ # <%= turbo_stream.append_all ".clearances" do %>
179
+ # <div id='clearance_item'>Append this to .clearances</div>
180
+ # <% end %>
181
+ def append_all(targets, content = nil, **rendering, &block)
182
+ action_all :append, targets, content, **rendering, &block
183
+ end
184
+
83
185
  # Prepend to the target in the dom identified with <tt>target</tt> either the <tt>content</tt> passed in or a
84
186
  # rendering result determined by the <tt>rendering</tt> keyword arguments or the content in the block,
85
187
  # or the rendering of the content as a record. Examples:
@@ -94,28 +196,45 @@ class Turbo::Streams::TagBuilder
94
196
  action :prepend, target, content, **rendering, &block
95
197
  end
96
198
 
97
- # Send an action of the type <tt>name</tt>. Options described in the concrete methods.
199
+ # Prepend to the targets in the dom identified with <tt>targets</tt> either the <tt>content</tt> passed in or a
200
+ # rendering result determined by the <tt>rendering</tt> keyword arguments or the content in the block,
201
+ # or the rendering of the content as a record. Examples:
202
+ #
203
+ # <%= turbo_stream.prepend_all ".clearances", "<div class='clearance_item'>Prepend this to .clearances</div>" %>
204
+ # <%= turbo_stream.prepend_all ".clearances", clearance %>
205
+ # <%= turbo_stream.prepend_all ".clearances", partial: "clearances/new_clearance", locals: { clearance: clearance } %>
206
+ # <%= turbo_stream.prepend_all ".clearances" do %>
207
+ # <div class='clearance_item'>Prepend this to .clearances</div>
208
+ # <% end %>
209
+ def prepend_all(targets, content = nil, **rendering, &block)
210
+ action_all :prepend, targets, content, **rendering, &block
211
+ end
212
+
213
+ # Send an action of the type <tt>name</tt> to <tt>target</tt>. Options described in the concrete methods.
98
214
  def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block)
99
- target_name = extract_target_name_from(target)
100
-
101
- case
102
- when content
103
- turbo_stream_action_tag name, target: target_name, template: (render_record(content) if allow_inferred_rendering) || content
104
- when block_given?
105
- turbo_stream_action_tag name, target: target_name, template: @view_context.capture(&block)
106
- when rendering.any?
107
- turbo_stream_action_tag name, target: target_name, template: @view_context.render(formats: [ :html ], **rendering)
108
- else
109
- turbo_stream_action_tag name, target: target_name, template: (render_record(target) if allow_inferred_rendering)
110
- end
215
+ template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
216
+
217
+ turbo_stream_action_tag name, target: target, template: template
218
+ end
219
+
220
+ # Send an action of the type <tt>name</tt> to <tt>targets</tt>. Options described in the concrete methods.
221
+ def action_all(name, targets, content = nil, allow_inferred_rendering: true, **rendering, &block)
222
+ template = render_template(targets, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
223
+
224
+ turbo_stream_action_tag name, targets: targets, template: template
111
225
  end
112
226
 
113
227
  private
114
- def extract_target_name_from(target)
115
- if target.respond_to?(:to_key)
116
- ActionView::RecordIdentifier.dom_id(target)
228
+ def render_template(target, content = nil, allow_inferred_rendering: true, **rendering, &block)
229
+ case
230
+ when content
231
+ allow_inferred_rendering ? (render_record(content) || content) : content
232
+ when block_given?
233
+ @view_context.capture(&block)
234
+ when rendering.any?
235
+ @view_context.render(formats: [ :html ], **rendering)
117
236
  else
118
- target
237
+ render_record(target) if allow_inferred_rendering
119
238
  end
120
239
  end
121
240
 
@@ -0,0 +1,9 @@
1
+ if (cable_config_path = Rails.root.join("config/cable.yml")).exist?
2
+ say "Enable redis in bundle"
3
+ uncomment_lines "Gemfile", /gem ['"]redis['"]/
4
+
5
+ say "Switch development cable to use redis"
6
+ gsub_file cable_config_path.to_s, /development:\n\s+adapter: async/, "development:\n adapter: redis\n url: redis://localhost:6379/1"
7
+ else
8
+ say 'ActionCable config file (config/cable.yml) is missing. Uncomment "gem \'redis\'" in your Gemfile and create config/cable.yml to use the Turbo Streams broadcast feature.'
9
+ end
@@ -0,0 +1,5 @@
1
+ say "Import Turbo"
2
+ append_to_file "app/javascript/application.js", %(import "@hotwired/turbo-rails"\n)
3
+
4
+ say "Pin Turbo"
5
+ append_to_file "config/importmap.rb", %(pin "@hotwired/turbo-rails", to: "turbo.js"\n)
@@ -0,0 +1,9 @@
1
+ if (js_entrypoint_path = Rails.root.join("app/javascript/application.js")).exist?
2
+ say "Import Turbo"
3
+ append_to_file "app/javascript/application.js", %(import "@hotwired/turbo-rails"\n)
4
+ else
5
+ say "You must import @hotwired/turbo-rails in your JavaScript entrypoint file", :red
6
+ end
7
+
8
+ say "Install Turbo"
9
+ run "yarn add @hotwired/turbo-rails"