stimulus_reflex 3.4.0.pre0 → 3.4.0.pre5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of stimulus_reflex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -3
- data/Gemfile.lock +12 -12
- data/README.md +6 -7
- data/Rakefile +1 -1
- data/app/channels/stimulus_reflex/channel.rb +125 -0
- data/bin/standardize +1 -1
- data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +13 -2
- data/lib/stimulus_reflex.rb +2 -0
- data/lib/stimulus_reflex/broadcasters/broadcaster.rb +9 -2
- data/lib/stimulus_reflex/broadcasters/nothing_broadcaster.rb +4 -0
- data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +6 -1
- data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +10 -0
- data/lib/stimulus_reflex/configuration.rb +5 -2
- data/lib/stimulus_reflex/logger.rb +106 -0
- data/lib/stimulus_reflex/reflex.rb +8 -4
- data/lib/stimulus_reflex/sanity_checker.rb +53 -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 +2 -2
- data/test/broadcasters/broadcaster_test.rb +15 -0
- data/test/broadcasters/nothing_broadcaster_test.rb +34 -0
- data/test/broadcasters/page_broadcaster_test.rb +69 -0
- data/test/broadcasters/selector_broadcaster_test.rb +83 -0
- data/test/test_helper.rb +2 -0
- data/test/tmp/app/reflexes/{user_reflex.rb → posts_reflex.rb} +1 -10
- data/yarn.lock +248 -1798
- metadata +21 -10
@@ -8,9 +8,10 @@ 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
16
|
cable_ready[stream_name].morph(
|
16
17
|
selector: selector,
|
@@ -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,6 +11,7 @@ module StimulusReflex
|
|
11
11
|
fragment = Nokogiri::HTML.fragment(html)
|
12
12
|
match = fragment.at_css(selector)
|
13
13
|
if match.present?
|
14
|
+
operations << [selector, :morph]
|
14
15
|
cable_ready[stream_name].morph(
|
15
16
|
selector: selector,
|
16
17
|
html: match.inner_html,
|
@@ -21,6 +22,7 @@ module StimulusReflex
|
|
21
22
|
})
|
22
23
|
)
|
23
24
|
else
|
25
|
+
operations << [selector, :inner_html]
|
24
26
|
cable_ready[stream_name].inner_html(
|
25
27
|
selector: selector,
|
26
28
|
html: fragment.to_html,
|
@@ -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
|
@@ -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,5 +1,7 @@
|
|
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
|
@@ -43,23 +45,25 @@ class StimulusReflex::Reflex
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :
|
48
|
+
attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
|
47
49
|
|
48
50
|
alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
|
49
51
|
|
50
52
|
delegate :connection, :stream_name, to: :channel
|
51
53
|
delegate :flash, :session, to: :request
|
52
54
|
delegate :broadcast, :broadcast_message, to: :broadcaster
|
55
|
+
delegate :reflex_id, :reflex_controller, :xpath, :c_xpath, :permanent_attribute_name, to: :client_attributes
|
53
56
|
|
54
|
-
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil,
|
57
|
+
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, params: {}, client_attributes: {})
|
55
58
|
@channel = channel
|
56
59
|
@url = url
|
57
60
|
@element = element
|
58
61
|
@selectors = selectors
|
59
62
|
@method_name = method_name
|
60
63
|
@params = params
|
61
|
-
@permanent_attribute_name = permanent_attribute_name
|
62
64
|
@broadcaster = StimulusReflex::PageBroadcaster.new(self)
|
65
|
+
@logger = StimulusReflex::Logger.new(self)
|
66
|
+
@client_attributes = ClientAttributes.new(client_attributes)
|
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,21 @@
|
|
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_generate_config?
|
10
|
+
|
11
|
+
instance = new
|
12
|
+
instance.check_caching_enabled
|
13
|
+
instance.check_javascript_package_version
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def called_by_generate_config?
|
19
|
+
ARGV.include? "stimulus_reflex:config"
|
20
|
+
end
|
10
21
|
end
|
11
22
|
|
12
23
|
def check_caching_enabled
|
@@ -80,19 +91,50 @@ class StimulusReflex::SanityChecker
|
|
80
91
|
Rails.root.join("node_modules", "stimulus_reflex", "package.json")
|
81
92
|
end
|
82
93
|
|
94
|
+
def initializer_path
|
95
|
+
@_initializer_path ||= Rails.root.join("config", "initializers", "stimulus_reflex.rb")
|
96
|
+
end
|
97
|
+
|
83
98
|
def warn_and_exit(text)
|
84
99
|
puts "WARNING:"
|
85
100
|
puts text
|
86
|
-
exit_with_info if StimulusReflex.config.
|
101
|
+
exit_with_info if StimulusReflex.config.on_failed_sanity_checks == :exit
|
87
102
|
end
|
88
103
|
|
89
104
|
def exit_with_info
|
90
105
|
puts
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
106
|
+
|
107
|
+
# bundle exec rails generate stimulus_reflex:config
|
108
|
+
if File.exist?(initializer_path)
|
109
|
+
puts <<~INFO
|
110
|
+
If you know what you are doing and you want to start the application anyway,
|
111
|
+
you can add the following directive to the StimulusReflex initializer,
|
112
|
+
which is located at #{initializer_path}
|
113
|
+
|
114
|
+
StimulusReflex.configure do |config|
|
115
|
+
config.on_failed_sanity_checks = :warn
|
116
|
+
end
|
117
|
+
|
118
|
+
INFO
|
119
|
+
else
|
120
|
+
puts <<~INFO
|
121
|
+
If you know what you are doing and you want to start the application anyway,
|
122
|
+
you can create a StimulusReflex initializer with the command:
|
123
|
+
|
124
|
+
bundle exec rails generate stimulus_reflex:config
|
125
|
+
|
126
|
+
Then open your initializer at
|
127
|
+
|
128
|
+
<RAILS_ROOT>/config/initializers/stimulus_reflex.rb
|
129
|
+
|
130
|
+
and then add the following directive:
|
131
|
+
|
132
|
+
StimulusReflex.configure do |config|
|
133
|
+
config.on_failed_sanity_checks = :warn
|
134
|
+
end
|
135
|
+
|
136
|
+
INFO
|
137
|
+
end
|
138
|
+
exit false
|
97
139
|
end
|
98
140
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StimulusReflex
|
4
|
+
module Utils
|
5
|
+
module Colorize
|
6
|
+
COLORS = {
|
7
|
+
red: "31",
|
8
|
+
green: "32",
|
9
|
+
yellow: "33",
|
10
|
+
blue: "34",
|
11
|
+
magenta: "35",
|
12
|
+
cyan: "36",
|
13
|
+
white: "37"
|
14
|
+
}
|
15
|
+
|
16
|
+
refine String do
|
17
|
+
COLORS.each do |name, code|
|
18
|
+
define_method(name) { "\e[#{code}m#{self}\e[0m" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -8,7 +8,7 @@ namespace :stimulus_reflex do
|
|
8
8
|
task install: :environment do
|
9
9
|
system "bundle exec rails webpacker:install:stimulus"
|
10
10
|
gem_version = StimulusReflex::VERSION.gsub(".pre", "-pre")
|
11
|
-
system "yarn add
|
11
|
+
system "yarn add stimulus_reflex@#{gem_version}"
|
12
12
|
main_folder = defined?(Webpacker) ? Webpacker.config.source_path.to_s.gsub("#{Rails.root}/", "") : "app/javascript"
|
13
13
|
|
14
14
|
FileUtils.mkdir_p Rails.root.join("#{main_folder}/controllers"), verbose: true
|
@@ -60,11 +60,12 @@ namespace :stimulus_reflex do
|
|
60
60
|
lines.delete_at 1
|
61
61
|
lines.insert 1, " adapter: redis\n"
|
62
62
|
lines.insert 2, " url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n"
|
63
|
-
lines.insert 3, " channel_prefix: " + Rails.
|
63
|
+
lines.insert 3, " channel_prefix: " + File.basename(Rails.root.to_s).underscore + "_development\n"
|
64
64
|
File.open(filepath, "w") { |f| f.write lines.join }
|
65
65
|
end
|
66
66
|
|
67
67
|
system "bundle exec rails generate stimulus_reflex example"
|
68
|
+
puts "Generating default StimulusReflex configuration file into your application config/initializers directory"
|
68
69
|
system "bundle exec rails generate stimulus_reflex:config"
|
69
70
|
system "rails dev:cache" unless Rails.root.join("tmp", "caching-dev.txt").exist?
|
70
71
|
end
|
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "stimulus_reflex",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.4.0-pre4",
|
4
4
|
"description": "Build reactive applications with the Rails tooling you already know and love.",
|
5
5
|
"keywords": [
|
6
6
|
"ruby",
|
@@ -28,34 +28,28 @@
|
|
28
28
|
},
|
29
29
|
"license": "MIT",
|
30
30
|
"author": "Nathan Hopkins <natehop@gmail.com>",
|
31
|
-
"
|
32
|
-
"
|
33
|
-
"module": "javascript/dist/stimulus_reflex.module.js",
|
34
|
-
"esmodule": "javascript/dist/stimulus_reflex.modern.js",
|
31
|
+
"main": "./javascript/stimulus_reflex.js",
|
32
|
+
"module": "./javascript/stimulus_reflex.js",
|
35
33
|
"scripts": {
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"prettier-standard:
|
39
|
-
"
|
40
|
-
"test": "yarn run mocha --require @babel/register --require esm ./javascript/test",
|
41
|
-
"build": "microbundle --target browser --format modern,es,cjs --no-strict",
|
42
|
-
"dev": "microbundle watch --target browser --format modern,es,cjs --no-strict"
|
34
|
+
"postinstall": "node ./javascript/scripts/post_install.js",
|
35
|
+
"prettier-standard:check": "yarn run prettier-standard --check ./javascript/*.js ./javascript/**/*.js",
|
36
|
+
"prettier-standard:format": "yarn run prettier-standard ./javascript/*.js ./javascript/**/*.js",
|
37
|
+
"test": "yarn run mocha --require @babel/register --require esm ./javascript/test"
|
43
38
|
},
|
44
39
|
"peerDependencies": {
|
45
|
-
"@rails/actioncable": ">= 6.0",
|
46
|
-
"cable_ready": ">= 4.3.0",
|
47
40
|
"stimulus": ">= 1.1"
|
48
41
|
},
|
42
|
+
"dependencies": {
|
43
|
+
"@rails/actioncable": ">= 6.0",
|
44
|
+
"cable_ready": ">= 4.3.0"
|
45
|
+
},
|
49
46
|
"devDependencies": {
|
50
47
|
"@babel/core": "^7.6.2",
|
51
48
|
"@babel/preset-env": "^7.6.2",
|
52
49
|
"@babel/register": "^7.6.2",
|
53
|
-
"@rails/actioncable": "^6.0.3-3",
|
54
50
|
"assert": "^2.0.0",
|
55
|
-
"cable_ready": "^4.4.0-pre2",
|
56
51
|
"esm": "^3.2.25",
|
57
52
|
"jsdom": "^16.0.1",
|
58
|
-
"microbundle": "^0.12.3",
|
59
53
|
"mocha": "^8.0.1",
|
60
54
|
"prettier-standard": "^16.1.0",
|
61
55
|
"stimulus": "^1.1.1"
|
data/stimulus_reflex.gemspec
CHANGED
@@ -23,14 +23,14 @@ Gem::Specification.new do |gem|
|
|
23
23
|
"source_code_uri" => gem.homepage
|
24
24
|
}
|
25
25
|
|
26
|
-
gem.files = Dir["lib/**/*", "bin/*", "[A-Z]*"]
|
26
|
+
gem.files = Dir["app/**/*", "lib/**/*", "bin/*", "[A-Z]*"]
|
27
27
|
gem.test_files = Dir["test/**/*.rb"]
|
28
28
|
|
29
29
|
gem.add_dependency "rack"
|
30
30
|
gem.add_dependency "nokogiri"
|
31
31
|
gem.add_dependency "rails", ">= 5.2"
|
32
|
-
gem.add_dependency "cable_ready", ">= 4.3.0"
|
33
32
|
gem.add_dependency "redis"
|
33
|
+
gem.add_dependency "cable_ready", ">= 4.3.0"
|
34
34
|
|
35
35
|
gem.add_development_dependency "bundler", "~> 2.0"
|
36
36
|
gem.add_development_dependency "pry-nav"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
|
3
|
+
class StimulusReflex::BroadcasterTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@reflex = Minitest::Mock.new
|
6
|
+
@reflex.expect :stream_name, "TestStream"
|
7
|
+
end
|
8
|
+
|
9
|
+
test "raises a NotImplementedError if called directly" do
|
10
|
+
broadcaster = StimulusReflex::Broadcaster.new(@reflex)
|
11
|
+
|
12
|
+
assert_raises(NotImplementedError) { broadcaster.broadcast }
|
13
|
+
assert_raises(NotImplementedError) { broadcaster.broadcast_message(subject: "Test") }
|
14
|
+
end
|
15
|
+
end
|