turbo-rails 2.0.5 → 2.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +53 -6
- data/app/assets/javascripts/turbo.js +150 -121
- data/app/assets/javascripts/turbo.min.js +6 -6
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +15 -3
- data/app/channels/turbo/streams_channel.rb +15 -15
- data/app/controllers/turbo/frames/frame_request.rb +2 -2
- data/app/helpers/turbo/drive_helper.rb +2 -2
- data/app/helpers/turbo/streams/action_helper.rb +3 -2
- data/app/helpers/turbo/streams_helper.rb +6 -0
- data/app/models/concerns/turbo/broadcastable.rb +19 -11
- data/config/routes.rb +3 -3
- data/lib/tasks/turbo_tasks.rake +0 -22
- data/lib/turbo/engine.rb +21 -6
- data/lib/turbo/version.rb +1 -1
- metadata +5 -5
- data/lib/install/turbo_needs_redis.rb +0 -20
@@ -75,8 +75,15 @@ module Turbo::Streams::Broadcasts
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
|
78
|
-
|
79
|
-
|
78
|
+
streamables.flatten!
|
79
|
+
streamables.compact_blank!
|
80
|
+
|
81
|
+
if streamables.present?
|
82
|
+
target = convert_to_turbo_stream_dom_id(target)
|
83
|
+
targets = convert_to_turbo_stream_dom_id(targets, include_selector: true)
|
84
|
+
Turbo::Streams::ActionBroadcastJob.perform_later \
|
85
|
+
stream_name_from(streamables), action: action, target: target, targets: targets, attributes: attributes, **rendering
|
86
|
+
end
|
80
87
|
end
|
81
88
|
|
82
89
|
def broadcast_render_to(*streamables, **rendering)
|
@@ -88,7 +95,12 @@ module Turbo::Streams::Broadcasts
|
|
88
95
|
end
|
89
96
|
|
90
97
|
def broadcast_stream_to(*streamables, content:)
|
91
|
-
|
98
|
+
streamables.flatten!
|
99
|
+
streamables.compact_blank!
|
100
|
+
|
101
|
+
if streamables.present?
|
102
|
+
ActionCable.server.broadcast stream_name_from(streamables), content
|
103
|
+
end
|
92
104
|
end
|
93
105
|
|
94
106
|
def refresh_debouncer_for(*streamables, request_id: nil) # :nodoc:
|
@@ -9,27 +9,27 @@
|
|
9
9
|
# helper modules like <tt>Turbo::Streams::StreamName</tt>:
|
10
10
|
#
|
11
11
|
# class CustomChannel < ActionCable::Channel::Base
|
12
|
-
#
|
13
|
-
#
|
12
|
+
# extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
|
13
|
+
# include Turbo::Streams::StreamName::ClassMethods
|
14
14
|
#
|
15
|
-
#
|
16
|
-
#
|
15
|
+
# def subscribed
|
16
|
+
# if (stream_name = verified_stream_name_from_params).present? &&
|
17
17
|
# subscription_allowed?
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
18
|
+
# stream_from stream_name
|
19
|
+
# else
|
20
|
+
# reject
|
21
|
+
# end
|
22
|
+
# end
|
23
23
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
24
|
+
# def subscription_allowed?
|
25
|
+
# # ...
|
26
|
+
# end
|
27
27
|
# end
|
28
28
|
#
|
29
|
-
#
|
30
|
-
#
|
29
|
+
# This channel can be connected to a web page using <tt>:channel</tt> option in
|
30
|
+
# <tt>turbo_stream_from</tt> helper:
|
31
31
|
#
|
32
|
-
#
|
32
|
+
# <%= turbo_stream_from 'room', channel: CustomChannel %>
|
33
33
|
#
|
34
34
|
class Turbo::StreamsChannel < ActionCable::Channel::Base
|
35
35
|
extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
|
@@ -24,7 +24,7 @@ module Turbo::Frames::FrameRequest
|
|
24
24
|
layout -> { "turbo_rails/frame" if turbo_frame_request? }
|
25
25
|
etag { :frame if turbo_frame_request? }
|
26
26
|
|
27
|
-
helper_method :turbo_frame_request_id
|
27
|
+
helper_method :turbo_frame_request?, :turbo_frame_request_id
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -33,6 +33,6 @@ module Turbo::Frames::FrameRequest
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def turbo_frame_request_id
|
36
|
-
request
|
36
|
+
request.headers["Turbo-Frame"]
|
37
37
|
end
|
38
38
|
end
|
@@ -51,7 +51,7 @@ module Turbo::DriveHelper
|
|
51
51
|
# Configure how to handle page refreshes. A page refresh happens when
|
52
52
|
# Turbo loads the current page again with a *replace* visit:
|
53
53
|
#
|
54
|
-
#
|
54
|
+
# ==== Parameters:
|
55
55
|
#
|
56
56
|
# * <tt>method</tt> - Method to update the +<body>+ of the page
|
57
57
|
# during a page refresh. It can be one of:
|
@@ -64,7 +64,7 @@ module Turbo::DriveHelper
|
|
64
64
|
# * +reset:+: Resets scroll to the top, left corner. This is the default.
|
65
65
|
# * +preserve:+: Keeps the scroll.
|
66
66
|
#
|
67
|
-
#
|
67
|
+
# ==== Example Usage:
|
68
68
|
#
|
69
69
|
# turbo_refreshes_with(method: :morph, scroll: :preserve)
|
70
70
|
def turbo_refreshes_with(method: :replace, scroll: :reset)
|
@@ -44,8 +44,9 @@ module Turbo::Streams::ActionHelper
|
|
44
44
|
|
45
45
|
private
|
46
46
|
def convert_to_turbo_stream_dom_id(target, include_selector: false)
|
47
|
-
|
48
|
-
|
47
|
+
target_array = Array.wrap(target)
|
48
|
+
if target_array.any? { |value| value.respond_to?(:to_key) }
|
49
|
+
"#{"#" if include_selector}#{ActionView::RecordIdentifier.dom_id(*target_array)}"
|
49
50
|
else
|
50
51
|
target
|
51
52
|
end
|
@@ -48,7 +48,13 @@ module Turbo::StreamsHelper
|
|
48
48
|
# It is also possible to pass additional parameters to the channel by passing them through `data` attributes:
|
49
49
|
#
|
50
50
|
# <%= turbo_stream_from "room", channel: RoomChannel, data: {room_name: "room #1"} %>
|
51
|
+
#
|
52
|
+
# Raises an +ArgumentError+ if all streamables are blank
|
53
|
+
#
|
54
|
+
# <%= turbo_stream_from("") %> # => ArgumentError: streamables can't be blank
|
55
|
+
# <%= turbo_stream_from("", nil) %> # => ArgumentError: streamables can't be blank
|
51
56
|
def turbo_stream_from(*streamables, **attributes)
|
57
|
+
raise ArgumentError, "streamables can't be blank" unless streamables.any?(&:present?)
|
52
58
|
attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
|
53
59
|
attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
|
54
60
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Turbo streams can be
|
1
|
+
# Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records).
|
2
2
|
# This makes it convenient to execute both synchronous and asynchronous updates, and render directly from callbacks in models
|
3
3
|
# or from controllers or jobs that act on those models. Here's an example:
|
4
4
|
#
|
@@ -79,19 +79,19 @@
|
|
79
79
|
#
|
80
80
|
# == Page refreshes
|
81
81
|
#
|
82
|
-
# You can broadcast "page refresh" stream actions. This will make subscribed clients reload the
|
82
|
+
# You can broadcast "page refresh" stream actions. This will make subscribed clients reload the
|
83
83
|
# page. For pages that configure morphing and scroll preservation, this will translate into smooth
|
84
84
|
# updates when it only updates the content that changed.
|
85
|
-
|
85
|
+
#
|
86
86
|
# This approach is an alternative to fine-grained stream actions targeting specific DOM elements. It
|
87
87
|
# offers good fidelity with a much simpler programming model. As a tradeoff, the fidelity you can reach
|
88
88
|
# is often not as high as with targeted stream actions since it renders the entire page again.
|
89
89
|
#
|
90
|
-
# The +
|
90
|
+
# The +broadcasts_refreshes+ class method configures the model to broadcast a "page refresh" on creates,
|
91
91
|
# updates, and destroys to a stream name derived at runtime by the <tt>stream</tt> symbol invocation. Examples
|
92
92
|
#
|
93
93
|
# class Board < ApplicationRecord
|
94
|
-
#
|
94
|
+
# broadcasts_refreshes
|
95
95
|
# end
|
96
96
|
#
|
97
97
|
# In this example, when a board is created, updated, or destroyed, a Turbo Stream for a
|
@@ -104,16 +104,16 @@
|
|
104
104
|
# belongs_to :board, touch: true # +Board+ will trigger a page refresh on column changes
|
105
105
|
# end
|
106
106
|
#
|
107
|
-
# You can also specify the streamable declaratively by passing a symbol to the +
|
107
|
+
# You can also specify the streamable declaratively by passing a symbol to the +broadcasts_refreshes_to+ method:
|
108
108
|
#
|
109
109
|
# class Column < ApplicationRecord
|
110
110
|
# belongs_to :board
|
111
|
-
#
|
111
|
+
# broadcasts_refreshes_to :board
|
112
112
|
# end
|
113
113
|
#
|
114
|
-
# For more granular control, you can also broadcast a "page refresh" to a stream name derived
|
114
|
+
# For more granular control, you can also broadcast a "page refresh" to a stream name derived
|
115
115
|
# from the passed <tt>streamables</tt> by using the instance-level methods <tt>broadcast_refresh_to</tt> or
|
116
|
-
# <tt>broadcast_refresh_later_to</tt>. These methods are particularly useful when you want to trigger
|
116
|
+
# <tt>broadcast_refresh_later_to</tt>. These methods are particularly useful when you want to trigger
|
117
117
|
# a page refresh for more specific scenarios. Example:
|
118
118
|
#
|
119
119
|
# class Clearance < ApplicationRecord
|
@@ -128,11 +128,11 @@
|
|
128
128
|
# end
|
129
129
|
# end
|
130
130
|
#
|
131
|
-
# In this example, a "page refresh" is broadcast to the stream named "identity:<identity-id>:clearances"
|
131
|
+
# In this example, a "page refresh" is broadcast to the stream named "identity:<identity-id>:clearances"
|
132
132
|
# after a new clearance is created. All clients subscribed to this stream will refresh the page to reflect
|
133
133
|
# the changes.
|
134
134
|
#
|
135
|
-
# When broadcasting page refreshes, Turbo will automatically debounce multiple calls in a row to only broadcast the last one.
|
135
|
+
# When broadcasting page refreshes, Turbo will automatically debounce multiple calls in a row to only broadcast the last one.
|
136
136
|
# This is meant for scenarios where you process records in mass. Because of the nature of such signals, it makes no sense to
|
137
137
|
# broadcast them repeatedly and individually.
|
138
138
|
# == Suppressing broadcasts
|
@@ -260,6 +260,10 @@ module Turbo::Broadcastable
|
|
260
260
|
# # Sends <turbo-stream action="replace" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
261
261
|
# # to the stream named "identity:2:clearances"
|
262
262
|
# clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
|
263
|
+
#
|
264
|
+
# # Sends <turbo-stream action="replace" method="morph" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
265
|
+
# # to the stream named "identity:2:clearances"
|
266
|
+
# clearance.broadcast_replace_to examiner.identity, :clearance, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
263
267
|
def broadcast_replace_to(*streamables, **rendering)
|
264
268
|
Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
265
269
|
end
|
@@ -279,6 +283,10 @@ module Turbo::Broadcastable
|
|
279
283
|
# # Sends <turbo-stream action="update" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
280
284
|
# # to the stream named "identity:2:clearances"
|
281
285
|
# clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
|
286
|
+
#
|
287
|
+
# # sends <turbo-stream action="update" method="morph" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
288
|
+
# # to the stream named "identity:2:clearances"
|
289
|
+
# # clearance.broadcast_update_to examiner.identity, :clearances, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
282
290
|
def broadcast_update_to(*streamables, **rendering)
|
283
291
|
Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
284
292
|
end
|
data/config/routes.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
get "recede_historical_location"
|
3
|
-
get "resume_historical_location"
|
4
|
-
get "refresh_historical_location"
|
2
|
+
get "recede_historical_location", to: "turbo/native/navigation#recede", as: :turbo_recede_historical_location
|
3
|
+
get "resume_historical_location", to: "turbo/native/navigation#resume", as: :turbo_resume_historical_location
|
4
|
+
get "refresh_historical_location", to: "turbo/native/navigation#refresh", as: :turbo_refresh_historical_location
|
5
5
|
end if Turbo.draw_routes
|
data/lib/tasks/turbo_tasks.rake
CHANGED
@@ -5,20 +5,6 @@ module Turbo
|
|
5
5
|
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}"
|
6
6
|
end
|
7
7
|
|
8
|
-
def redis_installed?
|
9
|
-
Gem.win_platform? ?
|
10
|
-
system('where redis-server > NUL 2>&1') :
|
11
|
-
system('which redis-server > /dev/null')
|
12
|
-
end
|
13
|
-
|
14
|
-
def switch_on_redis_if_available
|
15
|
-
if redis_installed?
|
16
|
-
Rake::Task["turbo:install:redis"].invoke
|
17
|
-
else
|
18
|
-
puts "Run turbo:install:redis to switch on Redis and use it in development for turbo streams"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
8
|
def using_bun?
|
23
9
|
Rails.root.join("bun.config.js").exist?
|
24
10
|
end
|
@@ -43,24 +29,16 @@ namespace :turbo do
|
|
43
29
|
desc "Install Turbo into the app with asset pipeline"
|
44
30
|
task :importmap do
|
45
31
|
Turbo::Tasks.run_turbo_install_template "turbo_with_importmap"
|
46
|
-
Turbo::Tasks.switch_on_redis_if_available
|
47
32
|
end
|
48
33
|
|
49
34
|
desc "Install Turbo into the app with webpacker"
|
50
35
|
task :node do
|
51
36
|
Turbo::Tasks.run_turbo_install_template "turbo_with_node"
|
52
|
-
Turbo::Tasks.switch_on_redis_if_available
|
53
37
|
end
|
54
38
|
|
55
39
|
desc "Install Turbo into the app with bun"
|
56
40
|
task :bun do
|
57
41
|
Turbo::Tasks.run_turbo_install_template "turbo_with_bun"
|
58
|
-
Turbo::Tasks.switch_on_redis_if_available
|
59
|
-
end
|
60
|
-
|
61
|
-
desc "Switch on Redis and use it in development"
|
62
|
-
task :redis do
|
63
|
-
Turbo::Tasks.run_turbo_install_template "turbo_needs_redis"
|
64
42
|
end
|
65
43
|
end
|
66
44
|
end
|
data/lib/turbo/engine.rb
CHANGED
@@ -15,8 +15,25 @@ module Turbo
|
|
15
15
|
#{root}/app/jobs
|
16
16
|
)
|
17
17
|
|
18
|
+
# If the parent application does not use Action Cable, app/channels cannot
|
19
|
+
# be eager loaded, because it references the ActionCable constant.
|
20
|
+
#
|
21
|
+
# When turbo-rails depends on Rails 7 or above, the entire block can be
|
22
|
+
# reduced to
|
23
|
+
#
|
24
|
+
# unless defined?(ActionCable)
|
25
|
+
# Rails.autoloaders.once.do_not_eager_load("#{root}/app/channels")
|
26
|
+
# end
|
27
|
+
#
|
18
28
|
initializer "turbo.no_action_cable", before: :set_eager_load_paths do
|
19
|
-
|
29
|
+
unless defined?(ActionCable)
|
30
|
+
if Rails.autoloaders.zeitwerk_enabled?
|
31
|
+
Rails.autoloaders.once.do_not_eager_load("#{root}/app/channels")
|
32
|
+
else
|
33
|
+
# This else branch only runs in Rails 6.x + classic mode.
|
34
|
+
config.eager_load_paths.delete("#{root}/app/channels")
|
35
|
+
end
|
36
|
+
end
|
20
37
|
end
|
21
38
|
|
22
39
|
# If you don't want to precompile Turbo's assets (eg. because you're using webpack),
|
@@ -63,11 +80,9 @@ module Turbo
|
|
63
80
|
end
|
64
81
|
|
65
82
|
initializer "turbo.renderer" do
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
turbo_streams_html
|
70
|
-
end
|
83
|
+
ActionController::Renderers.add :turbo_stream do |turbo_streams_html, options|
|
84
|
+
self.content_type = Mime[:turbo_stream] if media_type.nil?
|
85
|
+
turbo_streams_html
|
71
86
|
end
|
72
87
|
end
|
73
88
|
|
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: 2.0.
|
4
|
+
version: 2.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Stephenson
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-09-12 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activejob
|
@@ -93,7 +93,6 @@ files:
|
|
93
93
|
- app/models/turbo/thread_debouncer.rb
|
94
94
|
- app/views/layouts/turbo_rails/frame.html.erb
|
95
95
|
- config/routes.rb
|
96
|
-
- lib/install/turbo_needs_redis.rb
|
97
96
|
- lib/install/turbo_with_bun.rb
|
98
97
|
- lib/install/turbo_with_importmap.rb
|
99
98
|
- lib/install/turbo_with_node.rb
|
@@ -107,7 +106,8 @@ files:
|
|
107
106
|
homepage: https://github.com/hotwired/turbo-rails
|
108
107
|
licenses:
|
109
108
|
- MIT
|
110
|
-
metadata:
|
109
|
+
metadata:
|
110
|
+
changelog_uri: https://github.com/hotwired/turbo-rails/releases
|
111
111
|
post_install_message:
|
112
112
|
rdoc_options: []
|
113
113
|
require_paths:
|
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
126
|
+
rubygems_version: 3.5.16
|
127
127
|
signing_key:
|
128
128
|
specification_version: 4
|
129
129
|
summary: The speed of a single-page web application without having to write any JavaScript.
|
@@ -1,20 +0,0 @@
|
|
1
|
-
if (cable_config_path = Rails.root.join("config/cable.yml")).exist?
|
2
|
-
say "Enable redis in bundle"
|
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
|
15
|
-
|
16
|
-
say "Switch development cable to use redis"
|
17
|
-
gsub_file cable_config_path.to_s, /development:\n\s+adapter: async/, "development:\n adapter: redis\n url: redis://localhost:6379/1"
|
18
|
-
else
|
19
|
-
say 'ActionCable config file (config/cable.yml) is missing. Uncomment "gem \'redis\'" in your Gemfile and create config/cable.yml to use the Turbo Streams broadcast feature.'
|
20
|
-
end
|