turbo-rails 0.7.11 → 1.3.0

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