stimulus_reflex 3.4.0.pre1 → 3.4.0.pre6
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.
Potentially problematic release.
This version of stimulus_reflex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +95 -2
- data/Gemfile.lock +15 -15
- data/README.md +6 -10
- data/Rakefile +5 -5
- data/app/channels/stimulus_reflex/channel.rb +20 -4
- data/bin/console +1 -0
- data/bin/standardize +1 -1
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/%file_name%_controller.js.tt +14 -2
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application_controller.js.tt +10 -2
- data/lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt +18 -9
- data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +2 -2
- data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +13 -2
- data/lib/stimulus_reflex.rb +3 -0
- data/lib/stimulus_reflex/broadcasters/broadcaster.rb +11 -6
- data/lib/stimulus_reflex/broadcasters/nothing_broadcaster.rb +4 -0
- data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +7 -2
- data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +12 -2
- data/lib/stimulus_reflex/cable_ready_channels.rb +18 -0
- data/lib/stimulus_reflex/configuration.rb +5 -2
- data/lib/stimulus_reflex/logger.rb +106 -0
- data/lib/stimulus_reflex/reflex.rb +9 -5
- data/lib/stimulus_reflex/sanity_checker.rb +60 -11
- data/lib/stimulus_reflex/utils/colorize.rb +23 -0
- data/lib/stimulus_reflex/version.rb +1 -1
- data/lib/tasks/stimulus_reflex/install.rake +3 -2
- data/package.json +11 -17
- data/stimulus_reflex.gemspec +1 -1
- data/tags +82 -41
- data/test/broadcasters/broadcaster_test.rb +10 -0
- data/test/broadcasters/broadcaster_test_case.rb +13 -0
- data/test/broadcasters/nothing_broadcaster_test.rb +33 -0
- data/test/broadcasters/page_broadcaster_test.rb +73 -0
- data/test/broadcasters/selector_broadcaster_test.rb +55 -0
- data/test/test_helper.rb +37 -0
- data/test/tmp/app/reflexes/application_reflex.rb +2 -2
- data/test/tmp/app/reflexes/demo_reflex.rb +34 -0
- data/yarn.lock +248 -1798
- metadata +23 -10
- data/test/tmp/app/reflexes/user_reflex.rb +0 -33
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ApplicationReflex < StimulusReflex::Reflex
|
4
|
-
# Put application
|
4
|
+
# Put application-wide Reflex behavior and callbacks in this file.
|
5
5
|
#
|
6
6
|
# Example:
|
7
7
|
#
|
8
8
|
# # If your ActionCable connection is: `identified_by :current_user`
|
9
9
|
# delegate :current_user, to: :connection
|
10
10
|
#
|
11
|
-
# Learn more at: https://docs.stimulusreflex.com
|
11
|
+
# Learn more at: https://docs.stimulusreflex.com/reflexes#reflex-classes
|
12
12
|
end
|
@@ -1,9 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
StimulusReflex.configure do |config|
|
4
|
-
# Enable/disable
|
5
|
-
#
|
4
|
+
# Enable/disable exiting / warning when the sanity checks fail options:
|
5
|
+
# `:exit` or `:warn` or `:ignore`
|
6
|
+
|
7
|
+
# config.on_failed_sanity_checks = :exit
|
6
8
|
|
7
9
|
# Override the parent class that the StimulusReflex ActionCable channel inherits from
|
10
|
+
|
8
11
|
# config.parent_channel = "ApplicationCable::Channel"
|
12
|
+
|
13
|
+
# Customize server-side Reflex logging format, with optional colorization:
|
14
|
+
# Available tokens: session_id, session_id_full, reflex_info, operation, reflex_id, reflex_id_full, mode, selector, operation_counter, connection_id, connection_id_full, timestamp
|
15
|
+
# Available colors: green, yellow, blue, magenta, cyan, white
|
16
|
+
# You can also use attributes from your ActionCable Connection's identifiers that resolve to valid ActiveRecord models
|
17
|
+
# eg. if your connection is `identified_by :current_user` and your User model has an email attribute, you can access r.email (it will display `-` if the user isn't logged in)
|
18
|
+
|
19
|
+
# config.logging = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
|
9
20
|
end
|
data/lib/stimulus_reflex.rb
CHANGED
@@ -9,6 +9,7 @@ require "action_cable"
|
|
9
9
|
require "nokogiri"
|
10
10
|
require "cable_ready"
|
11
11
|
require "stimulus_reflex/version"
|
12
|
+
require "stimulus_reflex/cable_ready_channels"
|
12
13
|
require "stimulus_reflex/configuration"
|
13
14
|
require "stimulus_reflex/reflex"
|
14
15
|
require "stimulus_reflex/element"
|
@@ -17,6 +18,8 @@ require "stimulus_reflex/broadcasters/broadcaster"
|
|
17
18
|
require "stimulus_reflex/broadcasters/nothing_broadcaster"
|
18
19
|
require "stimulus_reflex/broadcasters/page_broadcaster"
|
19
20
|
require "stimulus_reflex/broadcasters/selector_broadcaster"
|
21
|
+
require "stimulus_reflex/utils/colorize"
|
22
|
+
require "stimulus_reflex/logger"
|
20
23
|
|
21
24
|
module StimulusReflex
|
22
25
|
class Engine < Rails::Engine
|
@@ -2,14 +2,13 @@
|
|
2
2
|
|
3
3
|
module StimulusReflex
|
4
4
|
class Broadcaster
|
5
|
-
|
6
|
-
|
7
|
-
attr_reader :reflex, :logger
|
8
|
-
delegate :permanent_attribute_name, :stream_name, to: :reflex
|
5
|
+
attr_reader :reflex, :logger, :operations
|
6
|
+
delegate :cable_ready, :permanent_attribute_name, to: :reflex
|
9
7
|
|
10
8
|
def initialize(reflex)
|
11
9
|
@reflex = reflex
|
12
|
-
@logger = Rails.logger
|
10
|
+
@logger = Rails.logger if defined?(Rails.logger)
|
11
|
+
@operations = []
|
13
12
|
end
|
14
13
|
|
15
14
|
def nothing?
|
@@ -26,7 +25,8 @@ module StimulusReflex
|
|
26
25
|
|
27
26
|
def broadcast_message(subject:, body: nil, data: {}, error: nil)
|
28
27
|
logger.error "\e[31m#{body}\e[0m" if subject == "error"
|
29
|
-
|
28
|
+
operations << ["document", :dispatch_event]
|
29
|
+
cable_ready.dispatch_event(
|
30
30
|
name: "stimulus-reflex:server-message",
|
31
31
|
detail: {
|
32
32
|
reflexId: data["reflexId"],
|
@@ -48,5 +48,10 @@ module StimulusReflex
|
|
48
48
|
def to_sym
|
49
49
|
raise NotImplementedError
|
50
50
|
end
|
51
|
+
|
52
|
+
# abstract method to be implemented by subclasses
|
53
|
+
def to_s
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
51
56
|
end
|
52
57
|
end
|
@@ -8,11 +8,12 @@ module StimulusReflex
|
|
8
8
|
|
9
9
|
return unless page_html.present?
|
10
10
|
|
11
|
-
document = Nokogiri::HTML(page_html)
|
11
|
+
document = Nokogiri::HTML.parse(page_html)
|
12
12
|
selectors = selectors.select { |s| document.css(s).present? }
|
13
13
|
selectors.each do |selector|
|
14
|
+
operations << [selector, :morph]
|
14
15
|
html = document.css(selector).inner_html
|
15
|
-
cable_ready
|
16
|
+
cable_ready.morph(
|
16
17
|
selector: selector,
|
17
18
|
html: html,
|
18
19
|
children_only: true,
|
@@ -32,5 +33,9 @@ module StimulusReflex
|
|
32
33
|
def page?
|
33
34
|
true
|
34
35
|
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"Page"
|
39
|
+
end
|
35
40
|
end
|
36
41
|
end
|
@@ -11,7 +11,8 @@ module StimulusReflex
|
|
11
11
|
fragment = Nokogiri::HTML.fragment(html)
|
12
12
|
match = fragment.at_css(selector)
|
13
13
|
if match.present?
|
14
|
-
|
14
|
+
operations << [selector, :morph]
|
15
|
+
cable_ready.morph(
|
15
16
|
selector: selector,
|
16
17
|
html: match.inner_html,
|
17
18
|
children_only: true,
|
@@ -21,7 +22,8 @@ module StimulusReflex
|
|
21
22
|
})
|
22
23
|
)
|
23
24
|
else
|
24
|
-
|
25
|
+
operations << [selector, :inner_html]
|
26
|
+
cable_ready.inner_html(
|
25
27
|
selector: selector,
|
26
28
|
html: fragment.to_html,
|
27
29
|
stimulus_reflex: data.merge({
|
@@ -40,6 +42,10 @@ module StimulusReflex
|
|
40
42
|
@morphs ||= []
|
41
43
|
end
|
42
44
|
|
45
|
+
def append_morph(selectors, html)
|
46
|
+
morphs << [selectors, html]
|
47
|
+
end
|
48
|
+
|
43
49
|
def to_sym
|
44
50
|
:selector
|
45
51
|
end
|
@@ -47,5 +53,9 @@ module StimulusReflex
|
|
47
53
|
def selector?
|
48
54
|
true
|
49
55
|
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"Selector"
|
59
|
+
end
|
50
60
|
end
|
51
61
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module StimulusReflex
|
2
|
+
class CableReadyChannels
|
3
|
+
def initialize(stream_name)
|
4
|
+
@cable_ready_channels = CableReady::Channels.instance
|
5
|
+
@stimulus_reflex_channel = @cable_ready_channels[stream_name]
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(name, *args)
|
9
|
+
return @stimulus_reflex_channel.send(name, *args) if @stimulus_reflex_channel.respond_to?(name)
|
10
|
+
@cable_ready_channels.send(name, *args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(name, include_all)
|
14
|
+
@stimulus_reflex_channel.respond_to?(name, include_all) ||
|
15
|
+
@cable_ready_channels.respond_to?(name, include_all)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -14,11 +14,14 @@ module StimulusReflex
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class Configuration
|
17
|
-
attr_accessor :
|
17
|
+
attr_accessor :on_failed_sanity_checks, :parent_channel, :logging
|
18
|
+
|
19
|
+
DEFAULT_LOGGING = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
|
18
20
|
|
19
21
|
def initialize
|
20
|
-
@
|
22
|
+
@on_failed_sanity_checks = :exit
|
21
23
|
@parent_channel = "ApplicationCable::Channel"
|
24
|
+
@logging = DEFAULT_LOGGING
|
22
25
|
end
|
23
26
|
end
|
24
27
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StimulusReflex
|
4
|
+
class Logger
|
5
|
+
attr_accessor :reflex, :current_operation
|
6
|
+
|
7
|
+
def initialize(reflex)
|
8
|
+
@reflex = reflex
|
9
|
+
@current_operation = 1
|
10
|
+
end
|
11
|
+
|
12
|
+
def print
|
13
|
+
return unless config_logging.instance_of?(Proc)
|
14
|
+
|
15
|
+
puts
|
16
|
+
reflex.broadcaster.operations.each do
|
17
|
+
puts instance_eval(&config_logging) + "\e[0m"
|
18
|
+
@current_operation += 1
|
19
|
+
end
|
20
|
+
puts
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def config_logging
|
26
|
+
return @config_logging if @config_logging
|
27
|
+
|
28
|
+
StimulusReflex.config.logging.binding.eval("using StimulusReflex::Utils::Colorize")
|
29
|
+
@config_logging = StimulusReflex.config.logging
|
30
|
+
end
|
31
|
+
|
32
|
+
def session_id_full
|
33
|
+
session = reflex.request&.session
|
34
|
+
session.nil? ? "-" : session.id
|
35
|
+
end
|
36
|
+
|
37
|
+
def session_id
|
38
|
+
session_id_full.to_s[0..7]
|
39
|
+
end
|
40
|
+
|
41
|
+
def reflex_info
|
42
|
+
reflex.class.to_s + "#" + reflex.method_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def reflex_id_full
|
46
|
+
reflex.reflex_id
|
47
|
+
end
|
48
|
+
|
49
|
+
def reflex_id
|
50
|
+
reflex_id_full[0..7]
|
51
|
+
end
|
52
|
+
|
53
|
+
def mode
|
54
|
+
reflex.broadcaster.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def selector
|
58
|
+
reflex.broadcaster.operations[current_operation - 1][0]
|
59
|
+
end
|
60
|
+
|
61
|
+
def operation
|
62
|
+
reflex.broadcaster.operations[current_operation - 1][1].to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def operation_counter
|
66
|
+
current_operation.to_s + "/" + reflex.broadcaster.operations.size.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
def connection_id_full
|
70
|
+
identifier = reflex.connection&.connection_identifier
|
71
|
+
identifier.empty? ? "-" : identifier
|
72
|
+
end
|
73
|
+
|
74
|
+
def connection_id
|
75
|
+
connection_id_full[0..7]
|
76
|
+
end
|
77
|
+
|
78
|
+
def timestamp
|
79
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
80
|
+
end
|
81
|
+
|
82
|
+
def method_missing method
|
83
|
+
return send(method.to_sym) if private_instance_methods.include?(method.to_sym)
|
84
|
+
|
85
|
+
reflex.connection.identifiers.each do |identifier|
|
86
|
+
ident = reflex.connection.send(identifier)
|
87
|
+
return ident.send(method) if ident.respond_to?(:attributes) && ident.attributes.key?(method.to_s)
|
88
|
+
end
|
89
|
+
"-"
|
90
|
+
end
|
91
|
+
|
92
|
+
def respond_to_missing? method
|
93
|
+
return true if private_instance_methods.include?(method.to_sym)
|
94
|
+
|
95
|
+
reflex.connection.identifiers.each do |identifier|
|
96
|
+
ident = reflex.connection.send(identifier)
|
97
|
+
return true if ident.respond_to?(:attributes) && ident.attributes.key?(method.to_s)
|
98
|
+
end
|
99
|
+
false
|
100
|
+
end
|
101
|
+
|
102
|
+
def private_instance_methods
|
103
|
+
StimulusReflex::Logger.private_instance_methods(false)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
ClientAttributes = Struct.new(:reflex_id, :reflex_controller, :xpath, :c_xpath, :permanent_attribute_name, keyword_init: true)
|
4
|
+
|
3
5
|
class StimulusReflex::Reflex
|
4
6
|
include ActiveSupport::Rescuable
|
5
7
|
include ActiveSupport::Callbacks
|
6
|
-
include CableReady::Broadcaster
|
7
8
|
|
8
9
|
define_callbacks :process, skip_after_callbacks_if_terminated: true
|
9
10
|
|
@@ -43,23 +44,26 @@ class StimulusReflex::Reflex
|
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
46
|
-
attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :
|
47
|
+
attr_reader :cable_ready, :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
|
47
48
|
|
48
49
|
alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
|
49
50
|
|
50
51
|
delegate :connection, :stream_name, to: :channel
|
51
52
|
delegate :flash, :session, to: :request
|
52
53
|
delegate :broadcast, :broadcast_message, to: :broadcaster
|
54
|
+
delegate :reflex_id, :reflex_controller, :xpath, :c_xpath, :permanent_attribute_name, to: :client_attributes
|
53
55
|
|
54
|
-
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil,
|
56
|
+
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, params: {}, client_attributes: {})
|
55
57
|
@channel = channel
|
56
58
|
@url = url
|
57
59
|
@element = element
|
58
60
|
@selectors = selectors
|
59
61
|
@method_name = method_name
|
60
62
|
@params = params
|
61
|
-
@permanent_attribute_name = permanent_attribute_name
|
62
63
|
@broadcaster = StimulusReflex::PageBroadcaster.new(self)
|
64
|
+
@logger = StimulusReflex::Logger.new(self)
|
65
|
+
@client_attributes = ClientAttributes.new(client_attributes)
|
66
|
+
@cable_ready = StimulusReflex::CableReadyChannels.new(stream_name)
|
63
67
|
self.params
|
64
68
|
end
|
65
69
|
|
@@ -104,7 +108,7 @@ class StimulusReflex::Reflex
|
|
104
108
|
else
|
105
109
|
raise StandardError.new("Cannot call :selector morph after :nothing morph") if broadcaster.nothing?
|
106
110
|
@broadcaster = StimulusReflex::SelectorBroadcaster.new(self) unless broadcaster.selector?
|
107
|
-
broadcaster.
|
111
|
+
broadcaster.append_morph(selectors, html)
|
108
112
|
end
|
109
113
|
end
|
110
114
|
|
@@ -3,10 +3,28 @@
|
|
3
3
|
class StimulusReflex::SanityChecker
|
4
4
|
JSON_VERSION_FORMAT = /(\d+\.\d+\.\d+.*)"/
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
class << self
|
7
|
+
def check!
|
8
|
+
return if StimulusReflex.config.on_failed_sanity_checks == :ignore
|
9
|
+
return if called_by_installer?
|
10
|
+
return if called_by_generate_config?
|
11
|
+
|
12
|
+
instance = new
|
13
|
+
instance.check_caching_enabled
|
14
|
+
instance.check_javascript_package_version
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def called_by_installer?
|
20
|
+
Rake.application.top_level_tasks.include? "stimulus_reflex:install"
|
21
|
+
rescue
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def called_by_generate_config?
|
26
|
+
ARGV.include? "stimulus_reflex:config"
|
27
|
+
end
|
10
28
|
end
|
11
29
|
|
12
30
|
def check_caching_enabled
|
@@ -80,19 +98,50 @@ class StimulusReflex::SanityChecker
|
|
80
98
|
Rails.root.join("node_modules", "stimulus_reflex", "package.json")
|
81
99
|
end
|
82
100
|
|
101
|
+
def initializer_path
|
102
|
+
@_initializer_path ||= Rails.root.join("config", "initializers", "stimulus_reflex.rb")
|
103
|
+
end
|
104
|
+
|
83
105
|
def warn_and_exit(text)
|
84
106
|
puts "WARNING:"
|
85
107
|
puts text
|
86
|
-
exit_with_info if StimulusReflex.config.
|
108
|
+
exit_with_info if StimulusReflex.config.on_failed_sanity_checks == :exit
|
87
109
|
end
|
88
110
|
|
89
111
|
def exit_with_info
|
90
112
|
puts
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
113
|
+
|
114
|
+
# bundle exec rails generate stimulus_reflex:config
|
115
|
+
if File.exist?(initializer_path)
|
116
|
+
puts <<~INFO
|
117
|
+
If you know what you are doing and you want to start the application anyway,
|
118
|
+
you can add the following directive to the StimulusReflex initializer,
|
119
|
+
which is located at #{initializer_path}
|
120
|
+
|
121
|
+
StimulusReflex.configure do |config|
|
122
|
+
config.on_failed_sanity_checks = :warn
|
123
|
+
end
|
124
|
+
|
125
|
+
INFO
|
126
|
+
else
|
127
|
+
puts <<~INFO
|
128
|
+
If you know what you are doing and you want to start the application anyway,
|
129
|
+
you can create a StimulusReflex initializer with the command:
|
130
|
+
|
131
|
+
bundle exec rails generate stimulus_reflex:config
|
132
|
+
|
133
|
+
Then open your initializer at
|
134
|
+
|
135
|
+
<RAILS_ROOT>/config/initializers/stimulus_reflex.rb
|
136
|
+
|
137
|
+
and then add the following directive:
|
138
|
+
|
139
|
+
StimulusReflex.configure do |config|
|
140
|
+
config.on_failed_sanity_checks = :warn
|
141
|
+
end
|
142
|
+
|
143
|
+
INFO
|
144
|
+
end
|
145
|
+
exit false
|
97
146
|
end
|
98
147
|
end
|