turbo-rails 1.0.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.
@@ -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)
@@ -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
 
@@ -0,0 +1,7 @@
1
+ export function overrideMethodWithFormmethod({ detail: { formSubmission: { fetchRequest, submitter } } }) {
2
+ const formMethod = submitter?.formMethod
3
+
4
+ if (formMethod && fetchRequest.body.has("_method")) {
5
+ fetchRequest.body.set("_method", formMethod)
6
+ }
7
+ }
@@ -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
+ };
@@ -66,16 +66,22 @@ module Turbo::Broadcastable
66
66
  # belongs_to :board
67
67
  # broadcasts_to ->(message) { [ message.board, :messages ] }, inserts_by: :prepend, target: "board_messages"
68
68
  # end
69
- def broadcasts_to(stream, inserts_by: :append, target: broadcast_target_default)
70
- after_create_commit -> { broadcast_action_later_to stream.try(:call, self) || send(stream), action: inserts_by, target: target.try(:call, self) || target }
71
- 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 }
72
77
  after_destroy_commit -> { broadcast_remove_to stream.try(:call, self) || send(stream) }
73
78
  end
74
79
 
75
- # Same as <tt>#broadcasts_to</tt>, but the designated stream is automatically set to the current model.
76
- def broadcasts(inserts_by: :append, target: broadcast_target_default)
77
- after_create_commit -> { broadcast_action_later action: inserts_by, target: target.try(:call, self) || target }
78
- 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 }
79
85
  after_destroy_commit -> { broadcast_remove }
80
86
  end
81
87
 
@@ -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" %>
@@ -3,7 +3,9 @@ def run_turbo_install_template(path)
3
3
  end
4
4
 
5
5
  def redis_installed?
6
- system('which redis-server > /dev/null')
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
- Rails.autoloaders.once.do_not_eager_load(Dir["#{root}/app/channels/turbo/*_channel.rb"]) unless defined?(ActionCable)
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
- assert_select %(turbo-stream[action="#{action}"][target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]), count: 1, &block
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
- assert_select %(turbo-stream[action="#{action}"][target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]), count: 0
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
@@ -1,3 +1,3 @@
1
1
  module Turbo
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
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: 1.0.1
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: 2022-01-16 00:00:00.000000000 Z
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.32
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.