turbo-rails 0.7.11 → 1.3.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.
@@ -6,61 +6,61 @@ module Turbo::Streams::Broadcasts
6
6
  include Turbo::Streams::ActionHelper
7
7
 
8
8
  def broadcast_remove_to(*streamables, **opts)
9
- broadcast_action_to *streamables, action: :remove, **opts
9
+ broadcast_action_to(*streamables, action: :remove, **opts)
10
10
  end
11
11
 
12
12
  def broadcast_replace_to(*streamables, **opts)
13
- broadcast_action_to *streamables, action: :replace, **opts
13
+ broadcast_action_to(*streamables, action: :replace, **opts)
14
14
  end
15
15
 
16
16
  def broadcast_update_to(*streamables, **opts)
17
- broadcast_action_to *streamables, action: :update, **opts
17
+ broadcast_action_to(*streamables, action: :update, **opts)
18
18
  end
19
19
 
20
20
  def broadcast_before_to(*streamables, **opts)
21
- broadcast_action_to *streamables, action: :before, **opts
21
+ broadcast_action_to(*streamables, action: :before, **opts)
22
22
  end
23
23
 
24
24
  def broadcast_after_to(*streamables, **opts)
25
- broadcast_action_to *streamables, action: :after, **opts
25
+ broadcast_action_to(*streamables, action: :after, **opts)
26
26
  end
27
27
 
28
28
  def broadcast_append_to(*streamables, **opts)
29
- broadcast_action_to *streamables, action: :append, **opts
29
+ broadcast_action_to(*streamables, action: :append, **opts)
30
30
  end
31
31
 
32
32
  def broadcast_prepend_to(*streamables, **opts)
33
- broadcast_action_to *streamables, action: :prepend, **opts
33
+ broadcast_action_to(*streamables, action: :prepend, **opts)
34
34
  end
35
35
 
36
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:
38
- rendering.delete(:content) || (rendering.any? ? render_format(:html, **rendering) : nil)
39
- )
37
+ 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)
39
+ ))
40
40
  end
41
41
 
42
42
  def broadcast_replace_later_to(*streamables, **opts)
43
- broadcast_action_later_to *streamables, action: :replace, **opts
43
+ broadcast_action_later_to(*streamables, action: :replace, **opts)
44
44
  end
45
45
 
46
46
  def broadcast_update_later_to(*streamables, **opts)
47
- broadcast_action_later_to *streamables, action: :update, **opts
47
+ broadcast_action_later_to(*streamables, action: :update, **opts)
48
48
  end
49
49
 
50
50
  def broadcast_before_later_to(*streamables, **opts)
51
- broadcast_action_later_to *streamables, action: :before, **opts
51
+ broadcast_action_later_to(*streamables, action: :before, **opts)
52
52
  end
53
53
 
54
54
  def broadcast_after_later_to(*streamables, **opts)
55
- broadcast_action_later_to *streamables, action: :after, **opts
55
+ broadcast_action_later_to(*streamables, action: :after, **opts)
56
56
  end
57
57
 
58
58
  def broadcast_append_later_to(*streamables, **opts)
59
- broadcast_action_later_to *streamables, action: :append, **opts
59
+ broadcast_action_later_to(*streamables, action: :append, **opts)
60
60
  end
61
61
 
62
62
  def broadcast_prepend_later_to(*streamables, **opts)
63
- broadcast_action_later_to *streamables, action: :prepend, **opts
63
+ broadcast_action_later_to(*streamables, action: :prepend, **opts)
64
64
  end
65
65
 
66
66
  def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, **rendering)
@@ -69,7 +69,7 @@ module Turbo::Streams::Broadcasts
69
69
  end
70
70
 
71
71
  def broadcast_render_to(*streamables, **rendering)
72
- broadcast_stream_to *streamables, content: render_format(:turbo_stream, **rendering)
72
+ broadcast_stream_to(*streamables, content: render_format(:turbo_stream, **rendering))
73
73
  end
74
74
 
75
75
  def broadcast_render_later_to(*streamables, **rendering)
@@ -13,6 +13,13 @@ module Turbo::Streams::StreamName
13
13
  Turbo.signed_stream_verifier.generate stream_name_from(streamables)
14
14
  end
15
15
 
16
+ module ClassMethods
17
+ # Can be used by custom turbo stream channels to obtain signed stream name from <tt>params</tt>
18
+ def verified_stream_name_from_params
19
+ self.class.verified_stream_name(params[:signed_stream_name])
20
+ end
21
+ end
22
+
16
23
  private
17
24
  def stream_name_from(streamables)
18
25
  if streamables.is_a?(Array)
@@ -4,12 +4,40 @@
4
4
  # into signed stream name using <tt>Turbo::Streams::StreamName#signed_stream_name</tt>. This is automatically done
5
5
  # using the view helper <tt>Turbo::StreamsHelper#turbo_stream_from(*streamables)</tt>.
6
6
  # If the signed stream name cannot be verified, the subscription is rejected.
7
+ #
8
+ # In case if custom behavior is desired, one can create their own channel and re-use some of the primitives from
9
+ # helper modules like <tt>Turbo::Streams::StreamName</tt>:
10
+ #
11
+ # class CustomChannel < ActionCable::Channel::Base
12
+ # extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
13
+ # include Turbo::Streams::StreamName::ClassMethods
14
+ #
15
+ # def subscribed
16
+ # if (stream_name = verified_stream_name_from_params).present? &&
17
+ # subscription_allowed?
18
+ # stream_from stream_name
19
+ # else
20
+ # reject
21
+ # end
22
+ # end
23
+ #
24
+ # def subscription_allowed?
25
+ # # ...
26
+ # end
27
+ # end
28
+ #
29
+ # This channel can be connected to a web page using <tt>:channel</tt> option in
30
+ # <tt>turbo_stream_from</tt> helper:
31
+ #
32
+ # <%= turbo_stream_from 'room', channel: CustomChannel %>
33
+ #
7
34
  class Turbo::StreamsChannel < ActionCable::Channel::Base
8
35
  extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
36
+ include Turbo::Streams::StreamName::ClassMethods
9
37
 
10
38
  def subscribed
11
- if verified_stream_name = self.class.verified_stream_name(params[:signed_stream_name])
12
- stream_from verified_stream_name
39
+ if stream_name = verified_stream_name_from_params
40
+ stream_from stream_name
13
41
  else
14
42
  reject
15
43
  end
@@ -19,6 +19,10 @@ module Turbo::Frames::FrameRequest
19
19
 
20
20
  private
21
21
  def turbo_frame_request?
22
- request.headers["Turbo-Frame"].present?
22
+ turbo_frame_request_id.present?
23
+ end
24
+
25
+ def turbo_frame_request_id
26
+ request.headers["Turbo-Frame"]
23
27
  end
24
28
  end
@@ -1,6 +1,5 @@
1
1
  module Turbo::DriveHelper
2
- # Pages that are more likely than not to be a cache miss can skip turbo cache to avoid visual jitter.
3
- # Note: This requires a +yield :head+ provision in the application layout.
2
+ # Note: These helpers require a +yield :head+ provision in the layout.
4
3
  #
5
4
  # ==== Example
6
5
  #
@@ -10,7 +9,21 @@ module Turbo::DriveHelper
10
9
  # # app/views/trays/index.html.erb
11
10
  # <% turbo_exempts_page_from_cache %>
12
11
  # <p>Page that shouldn't be cached by Turbo</p>
12
+
13
+ # Pages that are more likely than not to be a cache miss can skip turbo cache to avoid visual jitter.
14
+ # Cannot be used along with +turbo_exempts_page_from_preview+.
13
15
  def turbo_exempts_page_from_cache
14
- provide :head, %(<meta name="turbo-cache-control" content="no-cache">).html_safe
16
+ provide :head, tag.meta(name: "turbo-cache-control", content: "no-cache")
17
+ end
18
+
19
+ # Specify that a cached version of the page should not be shown as a preview during an application visit.
20
+ # Cannot be used along with +turbo_exempts_page_from_cache+.
21
+ def turbo_exempts_page_from_preview
22
+ provide :head, tag.meta(name: "turbo-cache-control", content: "no-preview")
23
+ end
24
+
25
+ # Force the page, when loaded by Turbo, to be cause a full page reload.
26
+ def turbo_page_requires_reload
27
+ provide :head, tag.meta(name: "turbo-visit-control", content: "reload")
15
28
  end
16
29
  end
@@ -23,8 +23,20 @@ module Turbo::FramesHelper
23
23
  # <div>My tray frame!</div>
24
24
  # <% end %>
25
25
  # # => <turbo-frame id="tray"><div>My tray frame!</div></turbo-frame>
26
- def turbo_frame_tag(id, src: nil, target: nil, **attributes, &block)
27
- id = id.respond_to?(:to_key) ? dom_id(id) : id
26
+ #
27
+ # The `turbo_frame_tag` helper will convert the arguments it receives to their
28
+ # `dom_id` if applicable to easily generate unique ids for Turbo Frames:
29
+ #
30
+ # <%= turbo_frame_tag(Article.find(1)) %>
31
+ # # => <turbo-frame id="article_1"></turbo-frame>
32
+ #
33
+ # <%= turbo_frame_tag(Article.find(1), "comments") %>
34
+ # # => <turbo-frame id="article_1_comments"></turbo-frame>
35
+ #
36
+ # <%= turbo_frame_tag(Article.find(1), Comment.new) %>
37
+ # # => <turbo-frame id="article_1_new_comment"></turbo-frame>
38
+ def turbo_frame_tag(*ids, src: nil, target: nil, **attributes, &block)
39
+ id = ids.map { |id| id.respond_to?(:to_key) ? ActionView::RecordIdentifier.dom_id(id) : id }.join("_")
28
40
  src = url_for(src) if src.present?
29
41
 
30
42
  tag.turbo_frame(**attributes.merge(id: id, src: src, target: target).compact, &block)
@@ -1,4 +1,6 @@
1
1
  module Turbo::Streams::ActionHelper
2
+ include ActionView::Helpers::TagHelper
3
+
2
4
  # Creates a `turbo-stream` tag according to the passed parameters. Examples:
3
5
  #
4
6
  # turbo_stream_action_tag "remove", target: "message_1"
@@ -9,20 +11,24 @@ module Turbo::Streams::ActionHelper
9
11
  #
10
12
  # turbo_stream_action_tag "replace", targets: "message_1", template: %(<div id="message_1">Hello!</div>)
11
13
  # # => <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)
13
- template = action.to_sym == :remove ? "" : "<template>#{template}</template>"
14
+ def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **attributes)
15
+ template = action.to_sym == :remove ? "" : tag.template(template.to_s.html_safe)
14
16
 
15
17
  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)
18
- %(<turbo-stream action="#{action}" targets="#{targets}">#{template}</turbo-stream>).html_safe
18
+ tag.turbo_stream(template, **attributes.merge(action: action, target: target))
19
+ elsif targets = convert_to_turbo_stream_dom_id(targets, include_selector: true)
20
+ tag.turbo_stream(template, **attributes.merge(action: action, targets: targets))
19
21
  else
20
- raise ArgumentError, "target or targets must be supplied"
22
+ tag.turbo_stream(template, **attributes.merge(action: action))
21
23
  end
22
24
  end
23
25
 
24
26
  private
25
- def convert_to_turbo_stream_dom_id(target)
26
- target.respond_to?(:to_key) ? ActionView::RecordIdentifier.dom_id(target) : target
27
+ def convert_to_turbo_stream_dom_id(target, include_selector: false)
28
+ if target.respond_to?(:to_key)
29
+ [ ("#" if include_selector), ActionView::RecordIdentifier.dom_id(target) ].compact.join
30
+ else
31
+ target
32
+ end
27
33
  end
28
34
  end
@@ -39,9 +39,20 @@ 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
+ #
43
+ # Custom channel class name can be passed using <tt>:channel</tt> option (either as a String
44
+ # or a class name):
45
+ #
46
+ # <%= turbo_stream_from "room", channel: RoomChannel %>
47
+ #
48
+ # It is also possible to pass additional parameters to the channel by passing them through `data` attributes:
49
+ #
50
+ # <%= turbo_stream_from "room", channel: RoomChannel, data: {room_name: "room #1"} %>
51
+ #
42
52
  def turbo_stream_from(*streamables, **attributes)
43
- attributes[:channel] = "Turbo::StreamsChannel"
53
+ attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
44
54
  attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
55
+
45
56
  tag.turbo_cable_stream_source(**attributes)
46
57
  end
47
58
  end
@@ -1,5 +1,6 @@
1
1
  import { connectStreamSource, disconnectStreamSource } from "@hotwired/turbo"
2
2
  import { subscribeTo } from "./cable"
3
+ import snakeize from "./snakeize"
3
4
 
4
5
  class TurboCableStreamSourceElement extends HTMLElement {
5
6
  async connectedCallback() {
@@ -20,7 +21,7 @@ class TurboCableStreamSourceElement extends HTMLElement {
20
21
  get channel() {
21
22
  const channel = this.getAttribute("channel")
22
23
  const signed_stream_name = this.getAttribute("signed-stream-name")
23
- return { channel, signed_stream_name }
24
+ return { channel, signed_stream_name, ...snakeize({ ...this.dataset }) }
24
25
  }
25
26
  }
26
27
 
@@ -0,0 +1,19 @@
1
+ export function encodeMethodIntoRequestBody(event) {
2
+ if (event.target instanceof HTMLFormElement) {
3
+ const { target: form, detail: { fetchOptions } } = event
4
+
5
+ form.addEventListener("turbo:submit-start", ({ detail: { formSubmission: { submitter } } }) => {
6
+ const method = (submitter && submitter.formMethod) || (fetchOptions.body && fetchOptions.body.get("_method")) || form.getAttribute("method")
7
+
8
+ if (!/get/i.test(method)) {
9
+ if (/post/i.test(method)) {
10
+ fetchOptions.body.delete("_method")
11
+ } else {
12
+ fetchOptions.body.set("_method", method)
13
+ }
14
+
15
+ fetchOptions.method = "post"
16
+ }
17
+ }, { once: true })
18
+ }
19
+ }
@@ -5,3 +5,7 @@ export { Turbo }
5
5
 
6
6
  import * as cable from "./cable"
7
7
  export { cable }
8
+
9
+ import { encodeMethodIntoRequestBody } from "./fetch_requests"
10
+
11
+ addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody)
@@ -0,0 +1,31 @@
1
+ // Based on https://github.com/nathan7/snakeize
2
+ //
3
+ // This software is released under the MIT license:
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ // this software and associated documentation files (the "Software"), to deal in
6
+ // the Software without restriction, including without limitation the rights to
7
+ // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ // the Software, and to permit persons to whom the Software is furnished to do so,
9
+ // subject to the following conditions:
10
+
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ export default function walk (obj) {
21
+ if (!obj || typeof obj !== 'object') return obj;
22
+ if (obj instanceof Date || obj instanceof RegExp) return obj;
23
+ if (Array.isArray(obj)) return obj.map(walk);
24
+ return Object.keys(obj).reduce(function (acc, key) {
25
+ var camel = key[0].toLowerCase() + key.slice(1).replace(/([A-Z]+)/g, function (m, x) {
26
+ return '_' + x.toLowerCase();
27
+ });
28
+ acc[camel] = walk(obj[key]);
29
+ return acc;
30
+ }, {});
31
+ };
@@ -26,13 +26,28 @@
26
26
  # and finally prepend the result of that partial rendering to the target identified with the dom id "clearances"
27
27
  # (which is derived by default from the plural model name of the model, but can be overwritten).
28
28
  #
29
+ # You can also choose to render html instead of a partial inside of a broadcast
30
+ # you do this by passing the html: option to any broadcast method that accepts the **rendering argument
31
+ #
32
+ # class Message < ApplicationRecord
33
+ # belongs_to :user
34
+ #
35
+ # after_create_commit :update_message_count
36
+ #
37
+ # private
38
+ # def update_message_count
39
+ # broadcast_update_to(user, :messages, target: "message-count", html: "<p> #{user.messages.count} </p>")
40
+ # end
41
+ # end
42
+ #
29
43
  # There are four basic actions you can broadcast: <tt>remove</tt>, <tt>replace</tt>, <tt>append</tt>, and
30
44
  # <tt>prepend</tt>. As a rule, you should use the <tt>_later</tt> versions of everything except for remove when broadcasting
31
45
  # within a real-time path, like a controller or model, since all those updates require a rendering step, which can slow down
32
46
  # execution. You don't need to do this for remove, since only the dom id for the model is used.
33
47
  #
34
- # In addition to the four basic actions, you can also use <tt>broadcast_render_later</tt> or
35
- # <tt>broadcast_render_later_to</tt> to render a turbo stream template with multiple actions.
48
+ # In addition to the four basic actions, you can also use <tt>broadcast_render</tt>,
49
+ # <tt>broadcast_render_to</tt> <tt>broadcast_render_later</tt>, and <tt>broadcast_render_later_to</tt>
50
+ # to render a turbo stream template with multiple actions.
36
51
  module Turbo::Broadcastable
37
52
  extend ActiveSupport::Concern
38
53
 
@@ -51,16 +66,22 @@ module Turbo::Broadcastable
51
66
  # belongs_to :board
52
67
  # broadcasts_to ->(message) { [ message.board, :messages ] }, inserts_by: :prepend, target: "board_messages"
53
68
  # end
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.try(:call, self) || target }
56
- after_update_commit -> { broadcast_replace_later_to stream.try(:call, self) || send(stream) }
69
+ #
70
+ # class Message < ApplicationRecord
71
+ # belongs_to :board
72
+ # broadcasts_to ->(message) { [ message.board, :messages ] }, partial: "messages/custom_message"
73
+ # end
74
+ def broadcasts_to(stream, inserts_by: :append, target: broadcast_target_default, **rendering)
75
+ after_create_commit -> { broadcast_action_later_to stream.try(:call, self) || send(stream), action: inserts_by, target: target.try(:call, self) || target, **rendering }
76
+ after_update_commit -> { broadcast_replace_later_to stream.try(:call, self) || send(stream), **rendering }
57
77
  after_destroy_commit -> { broadcast_remove_to stream.try(:call, self) || send(stream) }
58
78
  end
59
79
 
60
- # Same as <tt>#broadcasts_to</tt>, but the designated stream is automatically set to the current model.
61
- def broadcasts(inserts_by: :append, target: broadcast_target_default)
62
- after_create_commit -> { broadcast_action_later action: inserts_by, target: target.try(:call, self) || target }
63
- after_update_commit -> { broadcast_replace_later }
80
+ # Same as <tt>#broadcasts_to</tt>, but the designated stream for updates and destroys is automatically set to
81
+ # the current model, for creates - to the model plural name, which can be overriden by passing <tt>stream</tt>.
82
+ def broadcasts(stream = model_name.plural, inserts_by: :append, target: broadcast_target_default, **rendering)
83
+ after_create_commit -> { broadcast_action_later_to stream, action: inserts_by, target: target.try(:call, self) || target, **rendering }
84
+ after_update_commit -> { broadcast_replace_later **rendering }
64
85
  after_destroy_commit -> { broadcast_remove }
65
86
  end
66
87
 
@@ -76,7 +97,7 @@ module Turbo::Broadcastable
76
97
  # # Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
77
98
  # clearance.broadcast_remove_to examiner.identity, :clearances
78
99
  def broadcast_remove_to(*streamables, target: self)
79
- Turbo::StreamsChannel.broadcast_remove_to *streamables, target: target
100
+ Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target)
80
101
  end
81
102
 
82
103
  # Same as <tt>#broadcast_remove_to</tt>, but the designated stream is automatically set to the current model.
@@ -95,7 +116,7 @@ module Turbo::Broadcastable
95
116
  # # to the stream named "identity:2:clearances"
96
117
  # clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
97
118
  def broadcast_replace_to(*streamables, **rendering)
98
- Turbo::StreamsChannel.broadcast_replace_to *streamables, target: self, **broadcast_rendering_with_defaults(rendering)
119
+ Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
99
120
  end
100
121
 
101
122
  # Same as <tt>#broadcast_replace_to</tt>, but the designated stream is automatically set to the current model.
@@ -114,7 +135,7 @@ module Turbo::Broadcastable
114
135
  # # to the stream named "identity:2:clearances"
115
136
  # clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
116
137
  def broadcast_update_to(*streamables, **rendering)
117
- Turbo::StreamsChannel.broadcast_update_to *streamables, target: self, **broadcast_rendering_with_defaults(rendering)
138
+ Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
118
139
  end
119
140
 
120
141
  # Same as <tt>#broadcast_update_to</tt>, but the designated stream is automatically set to the current model.
@@ -135,7 +156,7 @@ module Turbo::Broadcastable
135
156
  # clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5",
136
157
  # partial: "clearances/other_partial", locals: { a: 1 }
137
158
  def broadcast_before_to(*streamables, target:, **rendering)
138
- Turbo::StreamsChannel.broadcast_before_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
159
+ Turbo::StreamsChannel.broadcast_before_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
139
160
  end
140
161
 
141
162
  # Insert a rendering of this broadcastable model after the target identified by it's dom id passed as <tt>target</tt>
@@ -151,7 +172,7 @@ module Turbo::Broadcastable
151
172
  # clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5",
152
173
  # partial: "clearances/other_partial", locals: { a: 1 }
153
174
  def broadcast_after_to(*streamables, target:, **rendering)
154
- Turbo::StreamsChannel.broadcast_after_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
175
+ Turbo::StreamsChannel.broadcast_after_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
155
176
  end
156
177
 
157
178
  # Append a rendering of this broadcastable model to the target identified by it's dom id passed as <tt>target</tt>
@@ -167,7 +188,7 @@ module Turbo::Broadcastable
167
188
  # clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
168
189
  # partial: "clearances/other_partial", locals: { a: 1 }
169
190
  def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
170
- Turbo::StreamsChannel.broadcast_append_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
191
+ Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
171
192
  end
172
193
 
173
194
  # Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
@@ -188,7 +209,7 @@ module Turbo::Broadcastable
188
209
  # clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
189
210
  # partial: "clearances/other_partial", locals: { a: 1 }
190
211
  def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
191
- Turbo::StreamsChannel.broadcast_prepend_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
212
+ Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
192
213
  end
193
214
 
194
215
  # Same as <tt>#broadcast_prepend_to</tt>, but the designated stream is automatically set to the current model.
@@ -213,7 +234,7 @@ module Turbo::Broadcastable
213
234
 
214
235
  # Same as <tt>broadcast_replace_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
215
236
  def broadcast_replace_later_to(*streamables, **rendering)
216
- Turbo::StreamsChannel.broadcast_replace_later_to *streamables, target: self, **broadcast_rendering_with_defaults(rendering)
237
+ Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
217
238
  end
218
239
 
219
240
  # Same as <tt>#broadcast_replace_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -223,7 +244,7 @@ module Turbo::Broadcastable
223
244
 
224
245
  # Same as <tt>broadcast_update_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
225
246
  def broadcast_update_later_to(*streamables, **rendering)
226
- Turbo::StreamsChannel.broadcast_update_later_to *streamables, target: self, **broadcast_rendering_with_defaults(rendering)
247
+ Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
227
248
  end
228
249
 
229
250
  # Same as <tt>#broadcast_update_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -233,7 +254,7 @@ module Turbo::Broadcastable
233
254
 
234
255
  # Same as <tt>broadcast_append_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
235
256
  def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
236
- Turbo::StreamsChannel.broadcast_append_later_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
257
+ Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
237
258
  end
238
259
 
239
260
  # Same as <tt>#broadcast_append_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -243,7 +264,7 @@ module Turbo::Broadcastable
243
264
 
244
265
  # Same as <tt>broadcast_prepend_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
245
266
  def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
246
- Turbo::StreamsChannel.broadcast_prepend_later_to *streamables, target: target, **broadcast_rendering_with_defaults(rendering)
267
+ Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
247
268
  end
248
269
 
249
270
  # Same as <tt>#broadcast_prepend_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -261,9 +282,7 @@ module Turbo::Broadcastable
261
282
  broadcast_action_later_to self, action: action, target: target, **rendering
262
283
  end
263
284
 
264
-
265
- # Render a turbo stream template asynchronously with this broadcastable model passed as the local variable using a
266
- # <tt>Turbo::Streams::BroadcastJob</tt>. Example:
285
+ # Render a turbo stream template with this broadcastable model passed as the local variable. Example:
267
286
  #
268
287
  # # Template: entries/_entry.turbo_stream.erb
269
288
  # <%= turbo_stream.remove entry %>
@@ -275,7 +294,26 @@ module Turbo::Broadcastable
275
294
  # <turbo-stream action="remove" target="entry_5"></turbo-stream>
276
295
  # <turbo-stream action="append" target="entries"><template><div id="entry_5">My Entry</div></template></turbo-stream>
277
296
  #
278
- # ...to the stream named "entry:5"
297
+ # ...to the stream named "entry:5".
298
+ #
299
+ # Note that rendering inline via this method will cause template rendering to happen synchronously. That is usually not
300
+ # desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
301
+ # be using `broadcast_render_later`, unless you specifically know why synchronous rendering is needed.
302
+ def broadcast_render(**rendering)
303
+ broadcast_render_to self, **rendering
304
+ end
305
+
306
+ # Same as <tt>broadcast_render</tt> but run with the added option of naming the stream using the passed
307
+ # <tt>streamables</tt>.
308
+ #
309
+ # Note that rendering inline via this method will cause template rendering to happen synchronously. That is usually not
310
+ # desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
311
+ # be using `broadcast_render_later_to`, unless you specifically know why synchronous rendering is needed.
312
+ def broadcast_render_to(*streamables, **rendering)
313
+ Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering))
314
+ end
315
+
316
+ # Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
279
317
  def broadcast_render_later(**rendering)
280
318
  broadcast_render_later_to self, **rendering
281
319
  end
@@ -283,7 +321,7 @@ module Turbo::Broadcastable
283
321
  # Same as <tt>broadcast_render_later</tt> but run with the added option of naming the stream using the passed
284
322
  # <tt>streamables</tt>.
285
323
  def broadcast_render_later_to(*streamables, **rendering)
286
- Turbo::StreamsChannel.broadcast_render_later_to *streamables, **broadcast_rendering_with_defaults(rendering)
324
+ Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering))
287
325
  end
288
326
 
289
327
 
@@ -297,7 +335,10 @@ module Turbo::Broadcastable
297
335
  # Add the current instance into the locals with the element name (which is the un-namespaced name)
298
336
  # as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
299
337
  o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self)
300
- o[:partial] ||= to_partial_path
338
+ # if the html option is passed in it will skip setting a partial from #to_partial_path
339
+ unless o.include?(:html)
340
+ o[:partial] ||= to_partial_path
341
+ end
301
342
  end
302
343
  end
303
344
  end
@@ -50,7 +50,7 @@ class Turbo::Streams::TagBuilder
50
50
  action_all :remove, targets, allow_inferred_rendering: false
51
51
  end
52
52
 
53
- # Replace the <tt>target</tt> in the dom with the either the <tt>content</tt> passed in, a rendering result determined
53
+ # Replace the <tt>target</tt> in the dom with either the <tt>content</tt> passed in, a rendering result determined
54
54
  # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
55
55
  #
56
56
  # <%= turbo_stream.replace "clearance_5", "<div id='clearance_5'>Replace the dom target identified by clearance_5</div>" %>
@@ -63,7 +63,7 @@ class Turbo::Streams::TagBuilder
63
63
  action :replace, target, content, **rendering, &block
64
64
  end
65
65
 
66
- # Replace the <tt>targets</tt> in the dom with the either the <tt>content</tt> passed in, a rendering result determined
66
+ # Replace the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in, a rendering result determined
67
67
  # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
68
68
  #
69
69
  # <%= turbo_stream.replace_all ".clearance_item", "<div class='clearance_item'>Replace the dom target identified by the class clearance_item</div>" %>
@@ -128,7 +128,7 @@ class Turbo::Streams::TagBuilder
128
128
  action_all :after, targets, content, **rendering, &block
129
129
  end
130
130
 
131
- # Update the <tt>target</tt> in the dom with the either the <tt>content</tt> passed in or a rendering result determined
131
+ # Update the <tt>target</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
132
132
  # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
133
133
  #
134
134
  # <%= turbo_stream.update "clearance_5", "Update the content of the dom target identified by clearance_5" %>
@@ -141,7 +141,7 @@ class Turbo::Streams::TagBuilder
141
141
  action :update, target, content, **rendering, &block
142
142
  end
143
143
 
144
- # Update the <tt>targets</tt> in the dom with the either the <tt>content</tt> passed in or a rendering result determined
144
+ # Update the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
145
145
  # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the targets as a record. Examples:
146
146
  #
147
147
  # <%= turbo_stream.update_all "clearance_item", "Update the content of the dom target identified by the class clearance_item" %>
@@ -1,6 +1,17 @@
1
1
  if (cable_config_path = Rails.root.join("config/cable.yml")).exist?
2
2
  say "Enable redis in bundle"
3
- uncomment_lines "Gemfile", %(gem 'redis')
3
+
4
+ gemfile_content = File.read(Rails.root.join("Gemfile"))
5
+ pattern = /gem ['"]redis['"]/
6
+
7
+ if gemfile_content.match?(pattern)
8
+ uncomment_lines "Gemfile", pattern
9
+ else
10
+ append_file "Gemfile", "\n# Use Redis for Action Cable"
11
+ gem 'redis', '~> 4.0'
12
+ end
13
+
14
+ run_bundle
4
15
 
5
16
  say "Switch development cable to use redis"
6
17
  gsub_file cable_config_path.to_s, /development:\n\s+adapter: async/, "development:\n adapter: redis\n url: redis://localhost:6379/1"
@@ -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.js"\n)
5
+ append_to_file "config/importmap.rb", %(pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true\n)