live_cable 0.3.0 → 0.4.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -0
  3. data/README.md +14 -2
  4. data/app/assets/javascript/controllers/live_controller.js +51 -7
  5. data/app/assets/javascript/live_cable.js +2 -0
  6. data/app/assets/javascript/loading.js +58 -6
  7. data/app/assets/javascript/subscriptions.js +292 -22
  8. data/app/channels/live_channel.rb +51 -29
  9. data/app/helpers/live_cable_helper.rb +31 -2
  10. data/lib/live_cable/action_cable_connection.rb +4 -1
  11. data/lib/live_cable/component/broadcasting.rb +37 -6
  12. data/lib/live_cable/component/dispatching.rb +36 -0
  13. data/lib/live_cable/component/lifecycle.rb +1 -1
  14. data/lib/live_cable/component/reactive_variables.rb +33 -5
  15. data/lib/live_cable/component/rendering.rb +30 -14
  16. data/lib/live_cable/component/streaming.rb +35 -4
  17. data/lib/live_cable/component.rb +4 -3
  18. data/lib/live_cable/connection/broadcasting.rb +15 -3
  19. data/lib/live_cable/connection/component_management.rb +12 -8
  20. data/lib/live_cable/connection/error_handling.rb +28 -7
  21. data/lib/live_cable/connection/messaging.rb +40 -52
  22. data/lib/live_cable/connection/state_management.rb +13 -16
  23. data/lib/live_cable/connection.rb +33 -0
  24. data/lib/live_cable/container.rb +17 -11
  25. data/lib/live_cable/defaults_signer.rb +95 -0
  26. data/lib/live_cable/delegation/array.rb +27 -1
  27. data/lib/live_cable/delegation/hash.rb +55 -1
  28. data/lib/live_cable/delegation/methods.rb +25 -1
  29. data/lib/live_cable/delegation/model.rb +12 -0
  30. data/lib/live_cable/delegator.rb +54 -6
  31. data/lib/live_cable/dispatch.rb +54 -0
  32. data/lib/live_cable/engine.rb +3 -1
  33. data/lib/live_cable/forbidden.rb +7 -0
  34. data/lib/live_cable/identity_digest.rb +27 -0
  35. data/lib/live_cable/model_observer.rb +37 -0
  36. data/lib/live_cable/observer_tracking.rb +10 -1
  37. data/lib/live_cable/predicate_builder.rb +13 -0
  38. data/lib/live_cable/render_context.rb +82 -8
  39. data/lib/live_cable/rendering/controller_renderer.rb +26 -0
  40. data/lib/live_cable/rendering/dependency_visitor.rb +68 -24
  41. data/lib/live_cable/rendering/handler.rb +1 -1
  42. data/lib/live_cable/rendering/method_analyzer.rb +18 -4
  43. data/lib/live_cable/rendering/method_collector.rb +13 -5
  44. data/lib/live_cable/rendering/partial.rb +13 -1
  45. data/lib/live_cable/rendering/partial_renderer.rb +59 -7
  46. data/lib/live_cable/rendering/render_result.rb +5 -0
  47. data/lib/live_cable/rendering/renderer.rb +18 -14
  48. data/lib/live_cable/rendering/view_buffer.rb +21 -0
  49. data/lib/live_cable/testing/render_state.rb +5 -3
  50. data/lib/live_cable/testing/test_channel.rb +6 -0
  51. data/lib/live_cable/testing/test_component.rb +3 -3
  52. data/lib/live_cable/testing.rb +9 -5
  53. data/lib/live_cable/version.rb +1 -1
  54. data/lib/live_cable.rb +32 -3
  55. metadata +9 -1
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiveCable
4
+ module Rendering
5
+ # Appends to whichever buffer the view is writing to when the append happens,
6
+ # so a template block a view helper yields lands where plain ERB would put it.
7
+ class ViewBuffer
8
+ delegate :safe_append=, :append=, :safe_expr_append=, to: :output_buffer
9
+
10
+ def initialize(view_context)
11
+ @view_context = view_context
12
+ end
13
+
14
+ private
15
+
16
+ def output_buffer
17
+ @view_context.output_buffer
18
+ end
19
+ end
20
+ end
21
+ end
@@ -6,14 +6,16 @@ module LiveCable
6
6
  # mirroring what the JavaScript client does: the first refresh carries
7
7
  # all parts, subsequent refreshes carry only the changed parts (nil
8
8
  # means unchanged), and child components arrive as separate results
9
- # referenced by <LiveCable child-live-id="..."> placeholders.
9
+ # referenced by <LiveCable child-live-id="..."> placeholders. Results for
10
+ # children at every depth arrive in the root's c, so the whole tree
11
+ # shares one set of child states.
10
12
  class RenderState
11
13
  CHILD_PLACEHOLDER = %r{<LiveCable child-live-id="(?<live_id>[^"]+)"></LiveCable>}
12
14
 
13
- def initialize
15
+ def initialize(children = Hash.new { |hash, key| hash[key] = RenderState.new(hash) })
14
16
  @parts_by_template = {}
15
17
  @last_template = nil
16
- @children = Hash.new { |hash, key| hash[key] = RenderState.new }
18
+ @children = children
17
19
  end
18
20
 
19
21
  # @param refresh [Hash] A _refresh payload ({ h:, p:, c: })
@@ -12,17 +12,23 @@ module LiveCable
12
12
  # @return [Array<Hash>] Payloads sent by components through this channel
13
13
  attr_reader :transmissions
14
14
 
15
+ # @return [Array<Hash>] Every _refresh payload sent, including any
16
+ # since cleared from transmissions
17
+ attr_reader :refreshes
18
+
15
19
  # @return [LiveCable::Testing::TestCableConnection]
16
20
  attr_reader :connection
17
21
 
18
22
  def initialize(identifiers = {})
19
23
  @streams = {}
20
24
  @transmissions = []
25
+ @refreshes = []
21
26
  @connection = TestCableConnection.new(identifiers)
22
27
  end
23
28
 
24
29
  def broadcast(data)
25
30
  @transmissions << data
31
+ @refreshes << data[:_refresh] if data.key?(:_refresh)
26
32
  end
27
33
 
28
34
  def stream_from(name, coder: nil, &block)
@@ -77,7 +77,7 @@ module LiveCable
77
77
  end
78
78
 
79
79
  # Forget previously captured broadcasts. Useful after mounting, to
80
- # assert on the effects of a single action.
80
+ # assert on the effects of a single action. Doesn't affect rendered.
81
81
  def clear_broadcasts
82
82
  channel.transmissions.clear
83
83
  end
@@ -98,8 +98,8 @@ module LiveCable
98
98
  def rendered_html
99
99
  state = RenderState.new
100
100
 
101
- broadcasts(:_refresh).each do |broadcast|
102
- state.apply(broadcast[:_refresh])
101
+ channel.refreshes.each do |refresh|
102
+ state.apply(refresh)
103
103
  end
104
104
 
105
105
  state.html
@@ -54,11 +54,15 @@ module LiveCable
54
54
 
55
55
  test_component = TestComponent.new(instance, connection, TestChannel.new(identifiers))
56
56
 
57
- connection.add_component(instance)
58
- instance.defaults = defaults
59
- instance.apply_defaults
60
- instance.connect(test_component.channel)
61
- instance.broadcast_render
57
+ begin
58
+ connection.add_component(instance)
59
+ instance.defaults = defaults
60
+ instance.apply_defaults
61
+ instance.connect(test_component.channel)
62
+ instance.broadcast_render(subscribed: true)
63
+ rescue StandardError => error
64
+ connection.handle_error(instance, error, channel: test_component.channel)
65
+ end
62
66
 
63
67
  test_component
64
68
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LiveCable
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/live_cable.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
3
4
  require 'zeitwerk'
4
5
  require 'herb'
5
6
 
@@ -19,18 +20,46 @@ require_relative '../app/helpers/live_cable_helper'
19
20
  require_relative 'live'
20
21
 
21
22
  module LiveCable
23
+ @warn_mutex = Mutex.new
24
+ @warned_messages = Set.new
25
+
26
+ # Log a warning through the Rails logger at most once per process for a
27
+ # given message. Used for performance and configuration hints (e.g. a
28
+ # component rendered without a .live.erb template) that would otherwise
29
+ # repeat on every render and flood the log.
30
+ def self.warn_once(message)
31
+ @warn_mutex.synchronize do
32
+ return unless @warned_messages.add?(message)
33
+ end
34
+
35
+ logger.warn(message)
36
+ end
37
+
38
+ # @return [Logger]
39
+ def self.logger
40
+ if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
41
+ Rails.logger
42
+ else
43
+ @fallback_logger ||= Logger.new($stderr)
44
+ end
45
+ end
46
+
22
47
  def self.instance_from_string(string, id)
23
48
  klass = Live
24
49
  klass_string = string.camelize
25
50
 
26
51
  begin
27
52
  klass_string.split('::').each do |part|
28
- unless klass.const_defined?(part)
53
+ # inherit: false so component names that collide with a top-level
54
+ # constant (e.g. "string" -> ::String) don't resolve through Object's
55
+ # ancestry and slip past this guard, only to blow up later with a
56
+ # confusing NameError instead of this friendly message.
57
+ unless klass.const_defined?(part, false)
29
58
  raise LiveCable::Error,
30
59
  "Component Live::#{klass_string} not found, make sure it is located in the Live:: module"
31
60
  end
32
61
 
33
- klass = klass.const_get(part)
62
+ klass = klass.const_get(part, false)
34
63
  end
35
64
  rescue NameError
36
65
  raise LiveCable::Error, "Invalid component name \"#{string}\" - Live::#{klass_string} not found"
@@ -38,7 +67,7 @@ module LiveCable
38
67
 
39
68
  klass = "Live::#{klass_string}".safe_constantize
40
69
 
41
- unless klass < LiveCable::Component
70
+ unless klass && klass < LiveCable::Component
42
71
  raise LiveCable::Error, 'Components must extend LiveCable::Component'
43
72
  end
44
73
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: live_cable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig Blanchette
@@ -136,6 +136,7 @@ files:
136
136
  - lib/live_cable/action_cable_connection.rb
137
137
  - lib/live_cable/component.rb
138
138
  - lib/live_cable/component/broadcasting.rb
139
+ - lib/live_cable/component/dispatching.rb
139
140
  - lib/live_cable/component/events.rb
140
141
  - lib/live_cable/component/identification.rb
141
142
  - lib/live_cable/component/lifecycle.rb
@@ -151,18 +152,24 @@ files:
151
152
  - lib/live_cable/connection/messaging.rb
152
153
  - lib/live_cable/connection/state_management.rb
153
154
  - lib/live_cable/container.rb
155
+ - lib/live_cable/defaults_signer.rb
154
156
  - lib/live_cable/delegation/array.rb
155
157
  - lib/live_cable/delegation/hash.rb
156
158
  - lib/live_cable/delegation/methods.rb
157
159
  - lib/live_cable/delegation/model.rb
158
160
  - lib/live_cable/delegator.rb
161
+ - lib/live_cable/dispatch.rb
159
162
  - lib/live_cable/engine.rb
160
163
  - lib/live_cable/error.rb
164
+ - lib/live_cable/forbidden.rb
165
+ - lib/live_cable/identity_digest.rb
161
166
  - lib/live_cable/model_observer.rb
162
167
  - lib/live_cable/observer.rb
163
168
  - lib/live_cable/observer_tracking.rb
169
+ - lib/live_cable/predicate_builder.rb
164
170
  - lib/live_cable/render_context.rb
165
171
  - lib/live_cable/rendering/compiler.rb
172
+ - lib/live_cable/rendering/controller_renderer.rb
166
173
  - lib/live_cable/rendering/dependency_visitor.rb
167
174
  - lib/live_cable/rendering/handler.rb
168
175
  - lib/live_cable/rendering/method_analyzer.rb
@@ -172,6 +179,7 @@ files:
172
179
  - lib/live_cable/rendering/partial_renderer.rb
173
180
  - lib/live_cable/rendering/render_result.rb
174
181
  - lib/live_cable/rendering/renderer.rb
182
+ - lib/live_cable/rendering/view_buffer.rb
175
183
  - lib/live_cable/testing.rb
176
184
  - lib/live_cable/testing/render_state.rb
177
185
  - lib/live_cable/testing/test_cable_connection.rb