turbo-rails 1.0.1 → 1.1.0

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