motion 0.1.2 → 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.
- checksums.yaml +4 -4
- data/lib/generators/motion/component_generator.rb +34 -0
- data/lib/generators/motion/install_generator.rb +10 -3
- data/lib/generators/motion/templates/motion.js +37 -0
- data/lib/generators/motion/templates/motion.rb +54 -15
- data/lib/motion.rb +6 -0
- data/lib/motion/action_cable_extentions.rb +6 -0
- data/lib/motion/action_cable_extentions/declarative_notifications.rb +101 -0
- data/lib/motion/action_cable_extentions/declarative_streams.rb +9 -43
- data/lib/motion/action_cable_extentions/synchronization.rb +34 -0
- data/lib/motion/callback.rb +35 -0
- data/lib/motion/channel.rb +13 -5
- data/lib/motion/component.rb +4 -0
- data/lib/motion/component/broadcasts.rb +40 -26
- data/lib/motion/component/callbacks.rb +19 -0
- data/lib/motion/component/lifecycle.rb +91 -9
- data/lib/motion/component/motions.rb +26 -16
- data/lib/motion/component/periodic_timers.rb +68 -0
- data/lib/motion/component/rendering.rb +25 -16
- data/lib/motion/component_connection.rb +18 -2
- data/lib/motion/configuration.rb +18 -11
- data/lib/motion/errors.rb +102 -71
- data/lib/motion/event.rb +9 -1
- data/lib/motion/log_helper.rb +2 -0
- data/lib/motion/markup_transformer.rb +6 -21
- data/lib/motion/railtie.rb +1 -0
- data/lib/motion/revision_calculator.rb +48 -0
- data/lib/motion/serializer.rb +4 -0
- data/lib/motion/version.rb +1 -1
- metadata +11 -4
- data/lib/generators/motion/templates/motion_controller.js +0 -28
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "motion"
|
4
|
+
|
5
|
+
module Motion
|
6
|
+
class Callback
|
7
|
+
attr_reader :broadcast
|
8
|
+
|
9
|
+
NAMESPACE = "motion:callback"
|
10
|
+
private_constant :NAMESPACE
|
11
|
+
|
12
|
+
def self.broadcast_for(component, method)
|
13
|
+
[
|
14
|
+
NAMESPACE,
|
15
|
+
component.stable_instance_identifier_for_callbacks,
|
16
|
+
method
|
17
|
+
].join(":")
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(component, method)
|
21
|
+
@broadcast = self.class.broadcast_for(component, method)
|
22
|
+
|
23
|
+
component.stream_from(broadcast, method)
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
other.is_a?(Callback) &&
|
28
|
+
other.broadcast == broadcast
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(message = nil)
|
32
|
+
ActionCable.server.broadcast(broadcast, message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/motion/channel.rb
CHANGED
@@ -6,6 +6,7 @@ require "motion"
|
|
6
6
|
|
7
7
|
module Motion
|
8
8
|
class Channel < ActionCable::Channel::Base
|
9
|
+
include ActionCableExtentions::DeclarativeNotifications
|
9
10
|
include ActionCableExtentions::DeclarativeStreams
|
10
11
|
include ActionCableExtentions::LogSuppression
|
11
12
|
|
@@ -24,9 +25,7 @@ module Motion
|
|
24
25
|
def subscribed
|
25
26
|
state, client_version = params.values_at("state", "version")
|
26
27
|
|
27
|
-
|
28
|
-
# older versions of the client that have a compatible protocol.
|
29
|
-
unless Motion::VERSION == client_version
|
28
|
+
if Gem::Version.new(Motion::VERSION) < Gem::Version.new(client_version)
|
30
29
|
raise IncompatibleClientError.new(Motion::VERSION, client_version)
|
31
30
|
end
|
32
31
|
|
@@ -58,14 +57,23 @@ module Motion
|
|
58
57
|
synchronize
|
59
58
|
end
|
60
59
|
|
60
|
+
def process_periodic_timer(timer)
|
61
|
+
component_connection.process_periodic_timer(timer)
|
62
|
+
synchronize
|
63
|
+
end
|
64
|
+
|
61
65
|
private
|
62
66
|
|
63
67
|
def synchronize
|
64
|
-
streaming_from(component_connection.broadcasts, to: :process_broadcast)
|
65
|
-
|
66
68
|
component_connection.if_render_required do |component|
|
67
69
|
transmit(renderer.render(component))
|
68
70
|
end
|
71
|
+
|
72
|
+
streaming_from component_connection.broadcasts,
|
73
|
+
to: :process_broadcast
|
74
|
+
|
75
|
+
periodically_notify component_connection.periodic_timers,
|
76
|
+
via: :process_periodic_timer
|
69
77
|
end
|
70
78
|
|
71
79
|
def handle_error(error, context)
|
data/lib/motion/component.rb
CHANGED
@@ -5,8 +5,10 @@ require "active_support/concern"
|
|
5
5
|
require "motion"
|
6
6
|
|
7
7
|
require "motion/component/broadcasts"
|
8
|
+
require "motion/component/callbacks"
|
8
9
|
require "motion/component/lifecycle"
|
9
10
|
require "motion/component/motions"
|
11
|
+
require "motion/component/periodic_timers"
|
10
12
|
require "motion/component/rendering"
|
11
13
|
|
12
14
|
module Motion
|
@@ -14,8 +16,10 @@ module Motion
|
|
14
16
|
extend ActiveSupport::Concern
|
15
17
|
|
16
18
|
include Broadcasts
|
19
|
+
include Callbacks
|
17
20
|
include Lifecycle
|
18
21
|
include Motions
|
22
|
+
include PeriodicTimers
|
19
23
|
include Rendering
|
20
24
|
end
|
21
25
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_support/concern"
|
4
4
|
require "active_support/core_ext/class/attribute"
|
5
5
|
require "active_support/core_ext/object/to_param"
|
6
|
+
require "active_support/core_ext/hash/except"
|
6
7
|
|
7
8
|
require "motion"
|
8
9
|
|
@@ -11,6 +12,31 @@ module Motion
|
|
11
12
|
module Broadcasts
|
12
13
|
extend ActiveSupport::Concern
|
13
14
|
|
15
|
+
# Analogous to `module_function` (available on both class and instance)
|
16
|
+
module ModuleFunctions
|
17
|
+
def stream_from(broadcast, handler)
|
18
|
+
self._broadcast_handlers =
|
19
|
+
_broadcast_handlers.merge(broadcast.to_s => handler.to_sym).freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop_streaming_from(broadcast)
|
23
|
+
self._broadcast_handlers =
|
24
|
+
_broadcast_handlers.except(broadcast.to_s).freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def stream_for(model, handler)
|
28
|
+
stream_from(broadcasting_for(model), handler)
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop_streaming_for(model)
|
32
|
+
stop_streaming_from(broadcasting_for(model))
|
33
|
+
end
|
34
|
+
|
35
|
+
def broadcasts
|
36
|
+
_broadcast_handlers.keys
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
14
40
|
included do
|
15
41
|
class_attribute :_broadcast_handlers,
|
16
42
|
instance_reader: false,
|
@@ -20,26 +46,19 @@ module Motion
|
|
20
46
|
end
|
21
47
|
|
22
48
|
class_methods do
|
49
|
+
include ModuleFunctions
|
50
|
+
|
23
51
|
def broadcast_to(model, message)
|
24
52
|
ActionCable.server.broadcast(broadcasting_for(model), message)
|
25
53
|
end
|
26
54
|
|
27
|
-
def stream_from(broadcast, handler)
|
28
|
-
self._broadcast_handlers =
|
29
|
-
_broadcast_handlers.merge(broadcast.to_s => handler.to_sym).freeze
|
30
|
-
end
|
31
|
-
|
32
|
-
def stream_for(model, handler)
|
33
|
-
stream_from(broadcasting_for(model), handler)
|
34
|
-
end
|
35
|
-
|
36
55
|
def broadcasting_for(model)
|
37
56
|
serialize_broadcasting([name, model])
|
38
57
|
end
|
39
58
|
|
40
59
|
private
|
41
60
|
|
42
|
-
#
|
61
|
+
# This definition is copied from ActionCable::Channel::Broadcasting
|
43
62
|
def serialize_broadcasting(object)
|
44
63
|
if object.is_a?(Array)
|
45
64
|
object.map { |m| serialize_broadcasting(m) }.join(":")
|
@@ -51,31 +70,26 @@ module Motion
|
|
51
70
|
end
|
52
71
|
end
|
53
72
|
|
54
|
-
|
55
|
-
_broadcast_handlers.keys
|
56
|
-
end
|
73
|
+
include ModuleFunctions
|
57
74
|
|
58
75
|
def process_broadcast(broadcast, message)
|
59
76
|
return unless (handler = _broadcast_handlers[broadcast])
|
60
77
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
78
|
+
_run_action_callbacks(context: handler) do
|
79
|
+
if method(handler).arity.zero?
|
80
|
+
send(handler)
|
81
|
+
else
|
82
|
+
send(handler, message)
|
83
|
+
end
|
84
|
+
end
|
66
85
|
end
|
67
86
|
|
68
|
-
|
69
|
-
self._broadcast_handlers =
|
70
|
-
_broadcast_handlers.merge(broadcast.to_s => handler.to_sym).freeze
|
71
|
-
end
|
87
|
+
private
|
72
88
|
|
73
|
-
def
|
74
|
-
|
89
|
+
def broadcasting_for(model)
|
90
|
+
self.class.broadcasting_for(model)
|
75
91
|
end
|
76
92
|
|
77
|
-
private
|
78
|
-
|
79
93
|
attr_writer :_broadcast_handlers
|
80
94
|
|
81
95
|
def _broadcast_handlers
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
require "motion"
|
6
|
+
|
7
|
+
module Motion
|
8
|
+
module Component
|
9
|
+
module Callbacks
|
10
|
+
def bind(method)
|
11
|
+
Callback.new(self, method)
|
12
|
+
end
|
13
|
+
|
14
|
+
def stable_instance_identifier_for_callbacks
|
15
|
+
@_stable_instance_identifier_for_callbacks ||= SecureRandom.uuid
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/callbacks"
|
3
4
|
require "active_support/concern"
|
5
|
+
require "active_support/deprecation"
|
4
6
|
|
5
7
|
require "motion"
|
6
8
|
|
@@ -9,22 +11,102 @@ module Motion
|
|
9
11
|
module Lifecycle
|
10
12
|
extend ActiveSupport::Concern
|
11
13
|
|
14
|
+
include ActiveSupport::Callbacks
|
15
|
+
|
16
|
+
included do
|
17
|
+
define_callbacks :action, :connect, :disconnect
|
18
|
+
|
19
|
+
# The built-in triggers defined on the target class will override ours.
|
20
|
+
remove_method(:_run_action_callbacks)
|
21
|
+
end
|
22
|
+
|
12
23
|
class_methods do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
Motion.config.revision,
|
19
|
-
previous_revision
|
24
|
+
def upgrade_from(previous_revision, instance)
|
25
|
+
raise UpgradeNotImplementedError.new(
|
26
|
+
instance,
|
27
|
+
previous_revision,
|
28
|
+
Motion.config.revision
|
20
29
|
)
|
21
30
|
end
|
31
|
+
|
32
|
+
def before_action(*methods, **options, &block)
|
33
|
+
set_action_callback(:before, *methods, **options, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def around_action(*methods, **options, &block)
|
37
|
+
set_action_callback(:around, *methods, **options, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_action(*methods, **options, &block)
|
41
|
+
set_action_callback(:after, *methods, **options, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def after_connect(*methods, **options, &block)
|
45
|
+
set_callback(:connect, :after, *methods, **options, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def after_disconnect(*methods, **options, &block)
|
49
|
+
set_callback(:disconnect, :after, *methods, **options, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def set_action_callback(kind, *methods, **options, &block)
|
55
|
+
filters = Array(options.delete(:if))
|
56
|
+
|
57
|
+
if (only = Array(options.delete(:only))).any?
|
58
|
+
filters << action_callback_context_filter(only)
|
59
|
+
end
|
60
|
+
|
61
|
+
if (except = Array(options.delete(:except))).any?
|
62
|
+
filters << action_callback_context_filter(except, invert: true)
|
63
|
+
end
|
64
|
+
|
65
|
+
set_callback(:action, kind, *methods, if: filters, **options, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def action_callback_context_filter(contexts, invert: false)
|
69
|
+
proc { contexts.include?(@_action_callback_context) ^ invert }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_connect
|
74
|
+
_run_connect_callbacks
|
75
|
+
|
76
|
+
# TODO: Remove at next minor release
|
77
|
+
if respond_to?(:connected)
|
78
|
+
ActiveSupport::Deprecation.warn(
|
79
|
+
"The `connected` lifecycle method is being replaced by the " \
|
80
|
+
"`after_connect` callback and will no longer be automatically " \
|
81
|
+
"invoked in the next **minor release** of Motion."
|
82
|
+
)
|
83
|
+
|
84
|
+
send(:connected)
|
85
|
+
end
|
22
86
|
end
|
23
87
|
|
24
|
-
def
|
88
|
+
def process_disconnect
|
89
|
+
_run_disconnect_callbacks
|
90
|
+
|
91
|
+
# TODO: Remove at next minor release
|
92
|
+
if respond_to?(:disconnected)
|
93
|
+
ActiveSupport::Deprecation.warn(
|
94
|
+
"The `disconnected` lifecycle method is being replaced by the " \
|
95
|
+
"`after_disconnect` callback and will no longer be automatically " \
|
96
|
+
"invoked in the next **minor release** of Motion."
|
97
|
+
)
|
98
|
+
|
99
|
+
send(:disconnected)
|
100
|
+
end
|
25
101
|
end
|
26
102
|
|
27
|
-
def
|
103
|
+
def _run_action_callbacks(context:, &block)
|
104
|
+
@_action_callback_context = context
|
105
|
+
|
106
|
+
run_callbacks(:action, &block)
|
107
|
+
ensure
|
108
|
+
# `@_action_callback_context = nil` would still appear in the state
|
109
|
+
remove_instance_variable(:@_action_callback_context)
|
28
110
|
end
|
29
111
|
end
|
30
112
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
4
|
require "active_support/core_ext/class/attribute"
|
5
|
+
require "active_support/core_ext/hash/except"
|
5
6
|
|
6
7
|
require "motion"
|
7
8
|
|
@@ -10,6 +11,23 @@ module Motion
|
|
10
11
|
module Motions
|
11
12
|
extend ActiveSupport::Concern
|
12
13
|
|
14
|
+
# Analogous to `module_function` (available on both class and instance)
|
15
|
+
module ModuleFunctions
|
16
|
+
def map_motion(motion, handler = motion)
|
17
|
+
self._motion_handlers =
|
18
|
+
_motion_handlers.merge(motion.to_s => handler.to_sym).freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def unmap_motion(motion)
|
22
|
+
self._motion_handlers =
|
23
|
+
_motion_handlers.except(motion.to_s).freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def motions
|
27
|
+
_motion_handlers.keys
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
13
31
|
included do
|
14
32
|
class_attribute :_motion_handlers,
|
15
33
|
instance_reader: false,
|
@@ -19,33 +37,25 @@ module Motion
|
|
19
37
|
end
|
20
38
|
|
21
39
|
class_methods do
|
22
|
-
|
23
|
-
self._motion_handlers =
|
24
|
-
_motion_handlers.merge(motion.to_s => handler.to_sym).freeze
|
25
|
-
end
|
40
|
+
include ModuleFunctions
|
26
41
|
end
|
27
42
|
|
28
|
-
|
29
|
-
_motion_handlers.keys
|
30
|
-
end
|
43
|
+
include ModuleFunctions
|
31
44
|
|
32
45
|
def process_motion(motion, event = nil)
|
33
46
|
unless (handler = _motion_handlers[motion])
|
34
47
|
raise MotionNotMapped.new(self, motion)
|
35
48
|
end
|
36
49
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
50
|
+
_run_action_callbacks(context: handler) do
|
51
|
+
if method(handler).arity.zero?
|
52
|
+
send(handler)
|
53
|
+
else
|
54
|
+
send(handler, event)
|
55
|
+
end
|
41
56
|
end
|
42
57
|
end
|
43
58
|
|
44
|
-
def map_motion(motion, handler = motion)
|
45
|
-
self._motion_handlers =
|
46
|
-
_motion_handlers.merge(motion.to_s => handler.to_sym).freeze
|
47
|
-
end
|
48
|
-
|
49
59
|
private
|
50
60
|
|
51
61
|
attr_writer :_motion_handlers
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
require "active_support/core_ext/hash/except"
|
6
|
+
|
7
|
+
require "motion"
|
8
|
+
|
9
|
+
module Motion
|
10
|
+
module Component
|
11
|
+
module PeriodicTimers
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
# Analogous to `module_function` (available on both class and instance)
|
15
|
+
module ModuleFunctions
|
16
|
+
def every(interval, handler, name: handler)
|
17
|
+
periodic_timer(name, handler, every: interval)
|
18
|
+
end
|
19
|
+
|
20
|
+
def periodic_timer(name, handler = name, every:)
|
21
|
+
self._periodic_timers =
|
22
|
+
_periodic_timers.merge(name.to_s => [handler.to_sym, every]).freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop_periodic_timer(name)
|
26
|
+
self._periodic_timers =
|
27
|
+
_periodic_timers.except(name.to_s).freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
def periodic_timers
|
31
|
+
_periodic_timers.transform_values { |_handler, interval| interval }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
included do
|
36
|
+
class_attribute :_periodic_timers,
|
37
|
+
instance_reader: false,
|
38
|
+
instance_writer: false,
|
39
|
+
instance_predicate: false,
|
40
|
+
default: {}.freeze
|
41
|
+
end
|
42
|
+
|
43
|
+
class_methods do
|
44
|
+
include ModuleFunctions
|
45
|
+
end
|
46
|
+
|
47
|
+
include ModuleFunctions
|
48
|
+
|
49
|
+
def process_periodic_timer(name)
|
50
|
+
return unless (handler, _interval = _periodic_timers[name])
|
51
|
+
|
52
|
+
_run_action_callbacks(context: handler) do
|
53
|
+
send(handler)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_writer :_periodic_timers
|
60
|
+
|
61
|
+
def _periodic_timers
|
62
|
+
return @_periodic_timers if defined?(@_periodic_timers)
|
63
|
+
|
64
|
+
self.class._periodic_timers
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|