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.
@@ -1,48 +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)
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 ActiveSupport::Callbacks
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, :xpath, :c_xpath, :permanent_attribute_name, to: :client_attributes
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusReflex
4
- VERSION = "3.4.0"
4
+ VERSION = "3.4.2"
5
5
  end
@@ -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.0-pre9",
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": ">= 4.4"
44
+ "cable_ready": "^4.5.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "^7.6.2",
@@ -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", ">= 4.4"
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
@@ -27,6 +27,6 @@ class StimulusReflex::ReflexTest < ActionCable::Channel::TestCase
27
27
  end
28
28
 
29
29
  test "dom_id" do
30
- assert @reflex.dom_id(TestModel.new(id: 123)) == "test_model_123"
30
+ assert @reflex.dom_id(TestModel.new(id: 123)) == "#test_model_123"
31
31
  end
32
32
  end
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