turbo-rails 0.9.1 → 1.1.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.
- checksums.yaml +4 -4
- data/README.md +44 -11
- data/app/assets/javascripts/turbo.js +92 -18
- data/app/assets/javascripts/turbo.min.js +3 -3
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams_channel.rb +1 -1
- data/app/helpers/turbo/frames_helper.rb +14 -2
- data/app/helpers/turbo/streams_helper.rb +5 -0
- data/app/javascript/turbo/cable_stream_source_element.js +2 -1
- data/app/javascript/turbo/form_submissions.js +7 -0
- data/app/javascript/turbo/index.js +3 -0
- data/app/javascript/turbo/snakeize.js +31 -0
- data/app/models/concerns/turbo/broadcastable.rb +37 -13
- data/app/models/turbo/streams/tag_builder.rb +4 -4
- data/lib/tasks/turbo_tasks.rake +3 -1
- data/lib/turbo/engine.rb +2 -2
- data/lib/turbo/test_assertions.rb +10 -4
- data/lib/turbo/version.rb +1 -1
- metadata +19 -3
@@ -9,7 +9,7 @@
|
|
9
9
|
# helper modules like <tt>Turbo::Streams::StreamName</tt>:
|
10
10
|
#
|
11
11
|
# class CustomChannel < ActionCable::Channel::Base
|
12
|
-
# extend Turbo::
|
12
|
+
# extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
|
13
13
|
# include Turbo::Streams::StreamName::ClassMethods
|
14
14
|
#
|
15
15
|
# def subscribed
|
@@ -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
|
-
|
27
|
-
|
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)
|
@@ -44,6 +44,11 @@ module Turbo::StreamsHelper
|
|
44
44
|
# or a class name):
|
45
45
|
#
|
46
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
|
+
#
|
47
52
|
def turbo_stream_from(*streamables, **attributes)
|
48
53
|
attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
|
49
54
|
attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
|
@@ -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
|
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import "./cable_stream_source_element"
|
2
|
+
import { overrideMethodWithFormmethod } from "./form_submissions"
|
2
3
|
|
3
4
|
import * as Turbo from "@hotwired/turbo"
|
4
5
|
export { Turbo }
|
5
6
|
|
6
7
|
import * as cable from "./cable"
|
7
8
|
export { cable }
|
9
|
+
|
10
|
+
addEventListener("turbo:submit-start", overrideMethodWithFormmethod)
|
@@ -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
|
+
};
|
@@ -45,8 +45,9 @@
|
|
45
45
|
# within a real-time path, like a controller or model, since all those updates require a rendering step, which can slow down
|
46
46
|
# execution. You don't need to do this for remove, since only the dom id for the model is used.
|
47
47
|
#
|
48
|
-
# In addition to the four basic actions, you can also use <tt>
|
49
|
-
# <tt>
|
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.
|
50
51
|
module Turbo::Broadcastable
|
51
52
|
extend ActiveSupport::Concern
|
52
53
|
|
@@ -65,16 +66,22 @@ module Turbo::Broadcastable
|
|
65
66
|
# belongs_to :board
|
66
67
|
# broadcasts_to ->(message) { [ message.board, :messages ] }, inserts_by: :prepend, target: "board_messages"
|
67
68
|
# end
|
68
|
-
|
69
|
-
|
70
|
-
|
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 }
|
71
77
|
after_destroy_commit -> { broadcast_remove_to stream.try(:call, self) || send(stream) }
|
72
78
|
end
|
73
79
|
|
74
|
-
# Same as <tt>#broadcasts_to</tt>, but the designated stream is automatically set to
|
75
|
-
|
76
|
-
|
77
|
-
|
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 }
|
78
85
|
after_destroy_commit -> { broadcast_remove }
|
79
86
|
end
|
80
87
|
|
@@ -275,9 +282,7 @@ module Turbo::Broadcastable
|
|
275
282
|
broadcast_action_later_to self, action: action, target: target, **rendering
|
276
283
|
end
|
277
284
|
|
278
|
-
|
279
|
-
# Render a turbo stream template asynchronously with this broadcastable model passed as the local variable using a
|
280
|
-
# <tt>Turbo::Streams::BroadcastJob</tt>. Example:
|
285
|
+
# Render a turbo stream template with this broadcastable model passed as the local variable. Example:
|
281
286
|
#
|
282
287
|
# # Template: entries/_entry.turbo_stream.erb
|
283
288
|
# <%= turbo_stream.remove entry %>
|
@@ -289,7 +294,26 @@ module Turbo::Broadcastable
|
|
289
294
|
# <turbo-stream action="remove" target="entry_5"></turbo-stream>
|
290
295
|
# <turbo-stream action="append" target="entries"><template><div id="entry_5">My Entry</div></template></turbo-stream>
|
291
296
|
#
|
292
|
-
# ...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>.
|
293
317
|
def broadcast_render_later(**rendering)
|
294
318
|
broadcast_render_later_to self, **rendering
|
295
319
|
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
|
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
|
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
|
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
|
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" %>
|
data/lib/tasks/turbo_tasks.rake
CHANGED
@@ -3,7 +3,9 @@ def run_turbo_install_template(path)
|
|
3
3
|
end
|
4
4
|
|
5
5
|
def redis_installed?
|
6
|
-
|
6
|
+
Gem.win_platform? ?
|
7
|
+
system('where redis-server > NUL 2>&1') :
|
8
|
+
system('which redis-server > /dev/null')
|
7
9
|
end
|
8
10
|
|
9
11
|
def switch_on_redis_if_available
|
data/lib/turbo/engine.rb
CHANGED
@@ -16,8 +16,8 @@ module Turbo
|
|
16
16
|
#{root}/app/jobs
|
17
17
|
)
|
18
18
|
|
19
|
-
initializer "turbo.no_action_cable" do
|
20
|
-
|
19
|
+
initializer "turbo.no_action_cable", before: :set_eager_load_paths do
|
20
|
+
config.eager_load_paths.delete("#{root}/app/channels") unless defined?(ActionCable)
|
21
21
|
end
|
22
22
|
|
23
23
|
# If you don't want to precompile Turbo's assets (eg. because you're using webpack),
|
@@ -7,15 +7,21 @@ module Turbo
|
|
7
7
|
delegate :dom_id, :dom_class, to: ActionView::RecordIdentifier
|
8
8
|
end
|
9
9
|
|
10
|
-
def assert_turbo_stream(action:, target: nil, status: :ok, &block)
|
10
|
+
def assert_turbo_stream(action:, target: nil, targets: nil, status: :ok, &block)
|
11
11
|
assert_response status
|
12
12
|
assert_equal Mime[:turbo_stream], response.media_type
|
13
|
-
|
13
|
+
selector = %(turbo-stream[action="#{action}"])
|
14
|
+
selector << %([target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]) if target
|
15
|
+
selector << %([targets="#{targets}"]) if targets
|
16
|
+
assert_select selector, count: 1, &block
|
14
17
|
end
|
15
18
|
|
16
|
-
def assert_no_turbo_stream(action:, target: nil)
|
19
|
+
def assert_no_turbo_stream(action:, target: nil, targets: nil)
|
17
20
|
assert_equal Mime[:turbo_stream], response.media_type
|
18
|
-
|
21
|
+
selector = %(turbo-stream[action="#{action}"])
|
22
|
+
selector << %([target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]) if target
|
23
|
+
selector << %([targets="#{targets}"]) if targets
|
24
|
+
assert_select selector, count: 0
|
19
25
|
end
|
20
26
|
end
|
21
27
|
end
|
data/lib/turbo/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbo-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Stephenson
|
@@ -10,8 +10,22 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2022-05-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activejob
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 6.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 6.0.0
|
15
29
|
- !ruby/object:Gem::Dependency
|
16
30
|
name: actionpack
|
17
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,7 +80,9 @@ files:
|
|
66
80
|
- app/helpers/turbo/streams_helper.rb
|
67
81
|
- app/javascript/turbo/cable.js
|
68
82
|
- app/javascript/turbo/cable_stream_source_element.js
|
83
|
+
- app/javascript/turbo/form_submissions.js
|
69
84
|
- app/javascript/turbo/index.js
|
85
|
+
- app/javascript/turbo/snakeize.js
|
70
86
|
- app/jobs/turbo/streams/action_broadcast_job.rb
|
71
87
|
- app/jobs/turbo/streams/broadcast_job.rb
|
72
88
|
- app/models/concerns/turbo/broadcastable.rb
|
@@ -99,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
115
|
- !ruby/object:Gem::Version
|
100
116
|
version: '0'
|
101
117
|
requirements: []
|
102
|
-
rubygems_version: 3.2.
|
118
|
+
rubygems_version: 3.2.33
|
103
119
|
signing_key:
|
104
120
|
specification_version: 4
|
105
121
|
summary: The speed of a single-page web application without having to write any JavaScript.
|