stimulus_reflex 3.4.0 → 3.4.2
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/CHANGELOG.md +34 -3
- data/CODE_OF_CONDUCT.md +6 -0
- data/Gemfile.lock +172 -120
- data/README.md +3 -2
- data/app/channels/stimulus_reflex/channel.rb +5 -10
- data/lib/stimulus_reflex/cable_ready_channels.rb +9 -2
- data/lib/stimulus_reflex/callbacks.rb +48 -0
- data/lib/stimulus_reflex/logger.rb +6 -6
- data/lib/stimulus_reflex/reflex.rb +18 -42
- data/lib/stimulus_reflex/reflex_factory.rb +62 -0
- data/lib/stimulus_reflex/version.rb +1 -1
- data/lib/stimulus_reflex.rb +2 -0
- data/package.json +2 -2
- data/stimulus_reflex.gemspec +1 -1
- data/test/reflex_factory_test.rb +79 -0
- data/test/reflex_test.rb +1 -1
- data/test/test_helper.rb +34 -0
- data/yarn.lock +134 -131
- metadata +16 -17
- data/tags +0 -156
- data/test/tmp/app/reflexes/application_reflex.rb +0 -12
- data/test/tmp/app/reflexes/demo_reflex.rb +0 -34
|
@@ -1,48 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
ClientAttributes = Struct.new(:reflex_id, :reflex_controller, :
|
|
3
|
+
ClientAttributes = Struct.new(:reflex_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, keyword_init: true)
|
|
4
4
|
|
|
5
5
|
class StimulusReflex::Reflex
|
|
6
6
|
include ActiveSupport::Rescuable
|
|
7
|
-
include
|
|
8
|
-
|
|
9
|
-
define_callbacks :process, skip_after_callbacks_if_terminated: true
|
|
10
|
-
|
|
11
|
-
class << self
|
|
12
|
-
def before_reflex(*args, &block)
|
|
13
|
-
add_callback(:before, *args, &block)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def after_reflex(*args, &block)
|
|
17
|
-
add_callback(:after, *args, &block)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def around_reflex(*args, &block)
|
|
21
|
-
add_callback(:around, *args, &block)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def add_callback(kind, *args, &block)
|
|
27
|
-
options = args.extract_options!
|
|
28
|
-
options.assert_valid_keys :if, :unless, :only, :except
|
|
29
|
-
set_callback(*[:process, kind, args, normalize_callback_options!(options)].flatten, &block)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def normalize_callback_options!(options)
|
|
33
|
-
normalize_callback_option! options, :only, :if
|
|
34
|
-
normalize_callback_option! options, :except, :unless
|
|
35
|
-
options
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def normalize_callback_option!(options, from, to)
|
|
39
|
-
if (from = options.delete(from))
|
|
40
|
-
from_set = Array(from).map(&:to_s).to_set
|
|
41
|
-
from = proc { |reflex| from_set.include? reflex.method_name }
|
|
42
|
-
options[to] = Array(options[to]).unshift(from)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
7
|
+
include StimulusReflex::Callbacks
|
|
46
8
|
|
|
47
9
|
attr_reader :cable_ready, :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
|
|
48
10
|
|
|
@@ -51,11 +13,21 @@ class StimulusReflex::Reflex
|
|
|
51
13
|
delegate :connection, :stream_name, to: :channel
|
|
52
14
|
delegate :controller_class, :flash, :session, to: :request
|
|
53
15
|
delegate :broadcast, :broadcast_message, to: :broadcaster
|
|
54
|
-
delegate :reflex_id, :reflex_controller, :
|
|
16
|
+
delegate :reflex_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, to: :client_attributes
|
|
55
17
|
delegate :render, to: :controller_class
|
|
56
|
-
delegate :dom_id, to: "ActionView::RecordIdentifier"
|
|
57
18
|
|
|
58
19
|
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, params: {}, client_attributes: {})
|
|
20
|
+
if is_a? CableReady::Broadcaster
|
|
21
|
+
message = <<~MSG
|
|
22
|
+
|
|
23
|
+
#{self.class.name} includes CableReady::Broadcaster, and you need to remove it.
|
|
24
|
+
Reflexes have their own CableReady interface. You can just assume that it's present.
|
|
25
|
+
See https://docs.stimulusreflex.com/rtfm/cableready#using-cableready-inside-a-reflex-action for more details.
|
|
26
|
+
|
|
27
|
+
MSG
|
|
28
|
+
raise TypeError.new(message.strip)
|
|
29
|
+
end
|
|
30
|
+
|
|
59
31
|
@channel = channel
|
|
60
32
|
@url = url
|
|
61
33
|
@element = element
|
|
@@ -156,4 +128,8 @@ class StimulusReflex::Reflex
|
|
|
156
128
|
def params
|
|
157
129
|
@_params ||= ActionController::Parameters.new(request.parameters)
|
|
158
130
|
end
|
|
131
|
+
|
|
132
|
+
def dom_id(record_or_class, prefix = nil)
|
|
133
|
+
"#" + ActionView::RecordIdentifier.dom_id(record_or_class, prefix).to_s
|
|
134
|
+
end
|
|
159
135
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class StimulusReflex::ReflexFactory
|
|
4
|
+
attr_reader :reflex_name, :method_name
|
|
5
|
+
|
|
6
|
+
def initialize(target)
|
|
7
|
+
reflex_name, method_name = target.split("#")
|
|
8
|
+
reflex_name = reflex_name.camelize
|
|
9
|
+
reflex_name = reflex_name.end_with?("Reflex") ? reflex_name : "#{reflex_name}Reflex"
|
|
10
|
+
|
|
11
|
+
@method_name = method_name
|
|
12
|
+
@reflex_name = reflex_name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
verify_method_name!
|
|
17
|
+
|
|
18
|
+
reflex_class
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def verify_method_name!
|
|
24
|
+
return if default_reflex?
|
|
25
|
+
|
|
26
|
+
argument_error = ArgumentError.new("Reflex method '#{method_name}' is not defined on class '#{reflex_name}' or on any of its ancestors")
|
|
27
|
+
|
|
28
|
+
if reflex_method.nil?
|
|
29
|
+
raise argument_error
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if !safe_ancestors.include?(reflex_method.owner)
|
|
33
|
+
raise argument_error
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reflex_class
|
|
38
|
+
@reflex_class ||= reflex_name.constantize.tap do |klass|
|
|
39
|
+
unless klass.ancestors.include?(StimulusReflex::Reflex)
|
|
40
|
+
raise ArgumentError.new("#{reflex_name} is not a StimulusReflex::Reflex")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reflex_method
|
|
46
|
+
if reflex_class.public_instance_methods.include?(method_name.to_sym)
|
|
47
|
+
reflex_class.public_instance_method(method_name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def default_reflex?
|
|
52
|
+
method_name == "default_reflex" && reflex_method.owner == ::StimulusReflex::Reflex
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def safe_ancestors
|
|
56
|
+
# We want to include every class and module up to the `StimulusReflex::Reflex` class,
|
|
57
|
+
# but not the StimulusReflex::Reflex itself
|
|
58
|
+
reflex_class_index = reflex_class.ancestors.index(StimulusReflex::Reflex) - 1
|
|
59
|
+
|
|
60
|
+
reflex_class.ancestors.to(reflex_class_index)
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/stimulus_reflex.rb
CHANGED
|
@@ -11,7 +11,9 @@ require "cable_ready"
|
|
|
11
11
|
require "stimulus_reflex/version"
|
|
12
12
|
require "stimulus_reflex/cable_ready_channels"
|
|
13
13
|
require "stimulus_reflex/configuration"
|
|
14
|
+
require "stimulus_reflex/callbacks"
|
|
14
15
|
require "stimulus_reflex/reflex"
|
|
16
|
+
require "stimulus_reflex/reflex_factory"
|
|
15
17
|
require "stimulus_reflex/element"
|
|
16
18
|
require "stimulus_reflex/sanity_checker"
|
|
17
19
|
require "stimulus_reflex/broadcasters/broadcaster"
|
data/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stimulus_reflex",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"description": "Build reactive applications with the Rails tooling you already know and love.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ruby",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@rails/actioncable": ">= 6.0",
|
|
44
|
-
"cable_ready": "
|
|
44
|
+
"cable_ready": "^4.5.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@babel/core": "^7.6.2",
|
data/stimulus_reflex.gemspec
CHANGED
|
@@ -32,7 +32,7 @@ Gem::Specification.new do |gem|
|
|
|
32
32
|
gem.add_dependency "nokogiri"
|
|
33
33
|
gem.add_dependency "rails", ">= 5.2"
|
|
34
34
|
gem.add_dependency "redis"
|
|
35
|
-
gem.add_dependency "cable_ready", "
|
|
35
|
+
gem.add_dependency "cable_ready", "~> 4.5"
|
|
36
36
|
|
|
37
37
|
gem.add_development_dependency "bundler", "~> 2.0"
|
|
38
38
|
gem.add_development_dependency "pry-nav"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class StimulusReflex::ReflexFactoryTest < ActionCable::Channel::TestCase
|
|
6
|
+
tests StimulusReflex::Channel
|
|
7
|
+
|
|
8
|
+
test "reflex class needs to be an ancestor of StimulusReflex::Reflex" do
|
|
9
|
+
exception = assert_raises(NameError) { StimulusReflex::ReflexFactory.new("Object#inspect").call }
|
|
10
|
+
assert_equal "uninitialized constant ObjectReflex Did you mean? ObjectSpace", exception.message.squish
|
|
11
|
+
|
|
12
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("NoReflex#no_reflex").call }
|
|
13
|
+
assert_equal "NoReflex is not a StimulusReflex::Reflex", exception.message
|
|
14
|
+
|
|
15
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("No#no_reflex").call }
|
|
16
|
+
assert_equal "NoReflex is not a StimulusReflex::Reflex", exception.message
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "doesn't raise if owner of method is ancestor of reflex class and descendant of StimulusReflex::Reflex" do
|
|
20
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("ApplicationReflex#default_reflex").call }
|
|
21
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("ApplicationReflex#application_reflex").call }
|
|
22
|
+
|
|
23
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("PostReflex#default_reflex").call }
|
|
24
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("PostReflex#application_reflex").call }
|
|
25
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("PostReflex#post_reflex").call }
|
|
26
|
+
|
|
27
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("CounterReflex#default_reflex").call }
|
|
28
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("CounterReflex#application_reflex").call }
|
|
29
|
+
assert_nothing_raised { StimulusReflex::ReflexFactory.new("CounterReflex#increment").call }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "raises if method is not owned by a descendant of StimulusReflex::Reflex" do
|
|
33
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("ApplicationReflex#itself").call }
|
|
34
|
+
assert_equal "Reflex method 'itself' is not defined on class 'ApplicationReflex' or on any of its ancestors", exception.message
|
|
35
|
+
|
|
36
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("ApplicationReflex#itself").call }
|
|
37
|
+
assert_equal "Reflex method 'itself' is not defined on class 'ApplicationReflex' or on any of its ancestors", exception.message
|
|
38
|
+
|
|
39
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("PostReflex#itself").call }
|
|
40
|
+
assert_equal "Reflex method 'itself' is not defined on class 'PostReflex' or on any of its ancestors", exception.message
|
|
41
|
+
|
|
42
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("PostReflex#binding").call }
|
|
43
|
+
assert_equal "Reflex method 'binding' is not defined on class 'PostReflex' or on any of its ancestors", exception.message
|
|
44
|
+
|
|
45
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("PostReflex#byebug").call }
|
|
46
|
+
assert_equal "Reflex method 'byebug' is not defined on class 'PostReflex' or on any of its ancestors", exception.message
|
|
47
|
+
|
|
48
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("PostReflex#debug").call }
|
|
49
|
+
assert_equal "Reflex method 'debug' is not defined on class 'PostReflex' or on any of its ancestors", exception.message
|
|
50
|
+
|
|
51
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("ApplicationReflex#post_reflex").call }
|
|
52
|
+
assert_equal "Reflex method 'post_reflex' is not defined on class 'ApplicationReflex' or on any of its ancestors", exception.message
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
test "raises if method is a private method" do
|
|
56
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("ApplicationReflex#private_application_reflex").call }
|
|
57
|
+
assert_equal "Reflex method 'private_application_reflex' is not defined on class 'ApplicationReflex' or on any of its ancestors", exception.message
|
|
58
|
+
|
|
59
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("PostReflex#private_application_reflex").call }
|
|
60
|
+
assert_equal "Reflex method 'private_application_reflex' is not defined on class 'PostReflex' or on any of its ancestors", exception.message
|
|
61
|
+
|
|
62
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("PostReflex#private_post_reflex").call }
|
|
63
|
+
assert_equal "Reflex method 'private_post_reflex' is not defined on class 'PostReflex' or on any of its ancestors", exception.message
|
|
64
|
+
|
|
65
|
+
exception = assert_raises(ArgumentError) { StimulusReflex::ReflexFactory.new("CounterReflex#private_post_reflex").call }
|
|
66
|
+
assert_equal "Reflex method 'private_post_reflex' is not defined on class 'CounterReflex' or on any of its ancestors", exception.message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
test "safe_ancestors" do
|
|
70
|
+
reflex_factory = StimulusReflex::ReflexFactory.new("ApplicationReflex#default_reflex")
|
|
71
|
+
assert_equal [ApplicationReflex], reflex_factory.send(:safe_ancestors)
|
|
72
|
+
|
|
73
|
+
reflex_factory = StimulusReflex::ReflexFactory.new("PostReflex#default_reflex")
|
|
74
|
+
assert_equal [PostReflex, ApplicationReflex], reflex_factory.send(:safe_ancestors)
|
|
75
|
+
|
|
76
|
+
reflex_factory = StimulusReflex::ReflexFactory.new("CounterReflex#increment")
|
|
77
|
+
assert_equal [CounterReflex, CounterConcern, ApplicationReflex], reflex_factory.send(:safe_ancestors)
|
|
78
|
+
end
|
|
79
|
+
end
|
data/test/reflex_test.rb
CHANGED
data/test/test_helper.rb
CHANGED
|
@@ -29,6 +29,40 @@ class SessionMock
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
class ApplicationReflex < StimulusReflex::Reflex
|
|
33
|
+
def application_reflex
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def private_application_reflex
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class PostReflex < ApplicationReflex
|
|
43
|
+
def post_reflex
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def private_post_reflex
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class NoReflex
|
|
53
|
+
def no_reflex
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module CounterConcern
|
|
58
|
+
def increment
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class CounterReflex < ApplicationReflex
|
|
63
|
+
include CounterConcern
|
|
64
|
+
end
|
|
65
|
+
|
|
32
66
|
class ActionDispatch::Request
|
|
33
67
|
def session
|
|
34
68
|
@session ||= SessionMock.new
|