turbo-rails 0.9.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|