Skip to content

Commit d823d73

Browse files
authored
Merge pull request from GHSA-f78j-4w3g-4q65
1 parent 1733422 commit d823d73

5 files changed

Lines changed: 179 additions & 8 deletions

File tree

app/channels/stimulus_reflex/channel.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ def receive(data)
2020
selectors = (data["selectors"] || []).select(&:present?)
2121
selectors = data["selectors"] = ["body"] if selectors.blank?
2222
target = data["target"].to_s
23-
reflex_name, method_name = target.split("#")
24-
reflex_name = reflex_name.camelize
25-
reflex_name = reflex_name.end_with?("Reflex") ? reflex_name : "#{reflex_name}Reflex"
23+
factory = StimulusReflex::ReflexFactory.new(target)
24+
reflex_class = factory.call
25+
method_name = factory.method_name
2626
arguments = (data["args"] || []).map { |arg| object_with_indifferent_access arg }
2727
element = StimulusReflex::Element.new(data)
2828
permanent_attribute_name = data["permanentAttributeName"]
@@ -31,7 +31,6 @@ def receive(data)
3131

3232
begin
3333
begin
34-
reflex_class = reflex_name.constantize.tap { |klass| raise ArgumentError.new("#{reflex_name} is not a StimulusReflex::Reflex") unless is_reflex?(klass) }
3534
reflex = reflex_class.new(self,
3635
url: url,
3736
element: element,
@@ -109,10 +108,6 @@ def object_with_indifferent_access(object)
109108
object
110109
end
111110

112-
def is_reflex?(reflex_class)
113-
reflex_class.ancestors.include? StimulusReflex::Reflex
114-
end
115-
116111
def delegate_call_to_reflex(reflex, method_name, arguments = [])
117112
method = reflex.method(method_name)
118113
required_params = method.parameters.select { |(kind, _)| kind == :req }

lib/stimulus_reflex.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require "stimulus_reflex/configuration"
1414
require "stimulus_reflex/callbacks"
1515
require "stimulus_reflex/reflex"
16+
require "stimulus_reflex/reflex_factory"
1617
require "stimulus_reflex/element"
1718
require "stimulus_reflex/sanity_checker"
1819
require "stimulus_reflex/broadcasters/broadcaster"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

test/reflex_factory_test.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

test/test_helper.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,40 @@ def load!
2929
end
3030
end
3131

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+
3266
class ActionDispatch::Request
3367
def session
3468
@session ||= SessionMock.new

0 commit comments

Comments
 (0)