stimulus_reflex 3.4.0 → 3.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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