stimulus_reflex 3.4.2 → 3.5.0.pre0

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.

Potentially problematic release.


This version of stimulus_reflex might be problematic. Click here for more details.

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +587 -495
  3. data/Gemfile.lock +120 -172
  4. data/LATEST +1 -0
  5. data/README.md +10 -10
  6. data/app/channels/stimulus_reflex/channel.rb +40 -62
  7. data/lib/generators/USAGE +1 -1
  8. data/lib/generators/stimulus_reflex/{config_generator.rb → initializer_generator.rb} +3 -3
  9. data/lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt +3 -2
  10. data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +1 -1
  11. data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +6 -1
  12. data/lib/stimulus_reflex/broadcasters/broadcaster.rb +7 -4
  13. data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +2 -2
  14. data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +12 -5
  15. data/lib/stimulus_reflex/cable_ready_channels.rb +6 -2
  16. data/lib/stimulus_reflex/callbacks.rb +55 -5
  17. data/lib/stimulus_reflex/concern_enhancer.rb +37 -0
  18. data/lib/stimulus_reflex/configuration.rb +2 -1
  19. data/lib/stimulus_reflex/element.rb +31 -7
  20. data/lib/stimulus_reflex/policies/reflex_invocation_policy.rb +28 -0
  21. data/lib/stimulus_reflex/reflex.rb +35 -20
  22. data/lib/stimulus_reflex/reflex_data.rb +79 -0
  23. data/lib/stimulus_reflex/reflex_factory.rb +23 -54
  24. data/lib/stimulus_reflex/request_parameters.rb +19 -0
  25. data/lib/stimulus_reflex/{logger.rb → utils/logger.rb} +0 -2
  26. data/lib/stimulus_reflex/{sanity_checker.rb → utils/sanity_checker.rb} +58 -10
  27. data/lib/stimulus_reflex/version.rb +1 -1
  28. data/lib/stimulus_reflex.rb +8 -2
  29. data/lib/tasks/stimulus_reflex/install.rake +6 -4
  30. data/package.json +6 -5
  31. data/stimulus_reflex.gemspec +5 -5
  32. data/test/broadcasters/broadcaster_test_case.rb +1 -1
  33. data/test/broadcasters/nothing_broadcaster_test.rb +5 -3
  34. data/test/broadcasters/page_broadcaster_test.rb +8 -4
  35. data/test/broadcasters/selector_broadcaster_test.rb +171 -55
  36. data/test/callbacks_test.rb +652 -0
  37. data/test/concern_enhancer_test.rb +54 -0
  38. data/test/element_test.rb +181 -0
  39. data/test/reflex_test.rb +1 -1
  40. data/test/test_helper.rb +4 -34
  41. data/test/tmp/app/reflexes/application_reflex.rb +12 -0
  42. data/test/tmp/app/reflexes/user_reflex.rb +44 -0
  43. data/yarn.lock +1138 -919
  44. metadata +36 -23
  45. data/test/reflex_factory_test.rb +0 -79
@@ -3,7 +3,10 @@
3
3
  module StimulusReflex
4
4
  class Broadcaster
5
5
  attr_reader :reflex, :logger, :operations
6
- delegate :cable_ready, :permanent_attribute_name, to: :reflex
6
+ delegate :cable_ready, :permanent_attribute_name, :payload, to: :reflex
7
+
8
+ DEFAULT_HTML_WITHOUT_FORMAT = Nokogiri::XML::Node::SaveOptions::DEFAULT_HTML &
9
+ ~Nokogiri::XML::Node::SaveOptions::FORMAT
7
10
 
8
11
  def initialize(reflex)
9
12
  @reflex = reflex
@@ -23,13 +26,13 @@ module StimulusReflex
23
26
  false
24
27
  end
25
28
 
26
- def broadcast_message(subject:, body: nil, data: {}, error: nil)
27
- logger.error "\e[31m#{body}\e[0m" if subject == "error"
29
+ def broadcast_message(subject:, data: {}, error: nil)
28
30
  operations << ["document", :dispatch_event]
29
31
  cable_ready.dispatch_event(
30
32
  name: "stimulus-reflex:server-message",
31
33
  detail: {
32
- reflexId: data["reflexId"],
34
+ reflexId: data.delete("reflexId"),
35
+ payload: payload,
33
36
  stimulus_reflex: data.merge(
34
37
  morph: to_sym,
35
38
  server_message: {subject: subject, body: error&.to_s}
@@ -12,10 +12,11 @@ module StimulusReflex
12
12
  selectors = selectors.select { |s| document.css(s).present? }
13
13
  selectors.each do |selector|
14
14
  operations << [selector, :morph]
15
- html = document.css(selector).inner_html
15
+ html = document.css(selector).inner_html(save_with: Broadcaster::DEFAULT_HTML_WITHOUT_FORMAT)
16
16
  cable_ready.morph(
17
17
  selector: selector,
18
18
  html: html,
19
+ payload: payload,
19
20
  children_only: true,
20
21
  permanent_attribute_name: permanent_attribute_name,
21
22
  stimulus_reflex: data.merge({
@@ -23,7 +24,6 @@ module StimulusReflex
23
24
  })
24
25
  )
25
26
  end
26
-
27
27
  cable_ready.broadcast
28
28
  end
29
29
 
@@ -2,19 +2,25 @@
2
2
 
3
3
  module StimulusReflex
4
4
  class SelectorBroadcaster < Broadcaster
5
+ include CableReady::Identifiable
6
+
5
7
  def broadcast(_, data = {})
6
8
  morphs.each do |morph|
7
9
  selectors, html = morph
8
- updates = selectors.is_a?(Hash) ? selectors : Hash[selectors, html]
9
- updates.each do |selector, html|
10
- html = html.to_s
11
- fragment = Nokogiri::HTML.fragment(html)
10
+ updates = selectors.is_a?(Hash) ? selectors : {selectors => html}
11
+ updates.each do |key, value|
12
+ html = reflex.render(key) if key.is_a?(ActiveRecord::Base) && value.nil?
13
+ html = reflex.render_collection(key) if key.is_a?(ActiveRecord::Relation) && value.nil?
14
+ html ||= value
15
+ fragment = Nokogiri::HTML.fragment(html.to_s)
16
+ selector = key.is_a?(ActiveRecord::Base) || key.is_a?(ActiveRecord::Relation) ? dom_id(key) : key.to_s
12
17
  match = fragment.at_css(selector)
13
18
  if match.present?
14
19
  operations << [selector, :morph]
15
20
  cable_ready.morph(
16
21
  selector: selector,
17
- html: match.inner_html,
22
+ html: match.inner_html(save_with: Broadcaster::DEFAULT_HTML_WITHOUT_FORMAT),
23
+ payload: payload,
18
24
  children_only: true,
19
25
  permanent_attribute_name: permanent_attribute_name,
20
26
  stimulus_reflex: data.merge({
@@ -26,6 +32,7 @@ module StimulusReflex
26
32
  cable_ready.inner_html(
27
33
  selector: selector,
28
34
  html: fragment.to_html,
35
+ payload: payload,
29
36
  stimulus_reflex: data.merge({
30
37
  morph: to_sym
31
38
  })
@@ -4,8 +4,9 @@ module StimulusReflex
4
4
  class CableReadyChannels
5
5
  delegate :[], to: "cable_ready_channels"
6
6
 
7
- def initialize(stream_name)
7
+ def initialize(stream_name, reflex_id)
8
8
  @stream_name = stream_name
9
+ @reflex_id = reflex_id
9
10
  end
10
11
 
11
12
  def cable_ready_channels
@@ -17,7 +18,10 @@ module StimulusReflex
17
18
  end
18
19
 
19
20
  def method_missing(name, *args)
20
- return stimulus_reflex_channel.public_send(name, *args) if stimulus_reflex_channel.respond_to?(name)
21
+ if stimulus_reflex_channel.respond_to?(name)
22
+ args[0][:reflex_id] = @reflex_id if args.any?
23
+ return stimulus_reflex_channel.public_send(name, *args)
24
+ end
21
25
  super
22
26
  end
23
27
 
@@ -22,24 +22,74 @@ module StimulusReflex
22
22
  add_callback(:around, *args, &block)
23
23
  end
24
24
 
25
+ def prepend_before_reflex(*args, &block)
26
+ prepend_callback(:before, *args, &block)
27
+ end
28
+
29
+ def prepend_after_reflex(*args, &block)
30
+ prepend_callback(:after, *args, &block)
31
+ end
32
+
33
+ def prepend_around_reflex(*args, &block)
34
+ prepend_callback(:around, *args, &block)
35
+ end
36
+
37
+ def skip_before_reflex(*args, &block)
38
+ omit_callback(:before, *args, &block)
39
+ end
40
+
41
+ def skip_after_reflex(*args, &block)
42
+ omit_callback(:after, *args, &block)
43
+ end
44
+
45
+ def skip_around_reflex(*args, &block)
46
+ omit_callback(:around, *args, &block)
47
+ end
48
+
49
+ alias_method :append_before_reflex, :before_reflex
50
+ alias_method :append_around_reflex, :around_reflex
51
+ alias_method :append_after_reflex, :after_reflex
52
+
25
53
  private
26
54
 
27
55
  def add_callback(kind, *args, &block)
28
- options = args.extract_options!
29
- options.assert_valid_keys :if, :unless, :only, :except
30
- set_callback(*[:process, kind, args, normalize_callback_options!(options)].flatten, &block)
56
+ insert_callbacks(args, block) do |name, options|
57
+ set_callback(:process, kind, name, options)
58
+ end
59
+ end
60
+
61
+ def prepend_callback(kind, *args, &block)
62
+ insert_callbacks(args, block) do |name, options|
63
+ set_callback(:process, kind, name, options.merge(prepend: true))
64
+ end
65
+ end
66
+
67
+ def omit_callback(kind, *args, &block)
68
+ insert_callbacks(args) do |name, options|
69
+ skip_callback(:process, kind, name, options)
70
+ end
71
+ end
72
+
73
+ def insert_callbacks(callbacks, block = nil)
74
+ options = callbacks.extract_options!
75
+ normalize_callback_options!(options)
76
+
77
+ callbacks.push(block) if block
78
+
79
+ callbacks.each do |callback|
80
+ yield callback, options
81
+ end
31
82
  end
32
83
 
33
84
  def normalize_callback_options!(options)
34
85
  normalize_callback_option! options, :only, :if
35
86
  normalize_callback_option! options, :except, :unless
36
- options
37
87
  end
38
88
 
39
89
  def normalize_callback_option!(options, from, to)
40
90
  if (from = options.delete(from))
41
91
  from_set = Array(from).map(&:to_s).to_set
42
- from = proc { |reflex| from_set.include? reflex.method_name }
92
+ from = proc { |reflex| from_set.include? reflex.method_name.to_s }
43
93
  options[to] = Array(options[to]).unshift(from)
44
94
  end
45
95
  end
@@ -0,0 +1,37 @@
1
+ module StimulusReflex
2
+ module ConcernEnhancer
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def method_missing(name, *args)
7
+ case ancestors
8
+ when ->(a) { !(a & [StimulusReflex::Reflex]).empty? }
9
+ if (ActiveRecord::Base.public_methods + ActionController::Base.public_methods).include? name
10
+ nil
11
+ else
12
+ super
13
+ end
14
+ when ->(a) { !(a & [ActiveRecord::Base, ActionController::Base]).empty? }
15
+ if StimulusReflex::Reflex.public_methods.include? name
16
+ nil
17
+ else
18
+ super
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def respond_to_missing?(name, include_all = false)
26
+ case ancestors
27
+ when ->(a) { !(a & [StimulusReflex::Reflex]).empty? }
28
+ (ActiveRecord::Base.public_methods + ActionController::Base.public_methods).include?(name) || super
29
+ when ->(a) { !(a & [ActiveRecord::Base, ActionController::Base]).empty? }
30
+ StimulusReflex::Reflex.public_methods.include?(name) || super
31
+ else
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -14,12 +14,13 @@ module StimulusReflex
14
14
  end
15
15
 
16
16
  class Configuration
17
- attr_accessor :on_failed_sanity_checks, :parent_channel, :logging, :middleware
17
+ attr_accessor :on_failed_sanity_checks, :on_new_version_available, :parent_channel, :logging, :middleware
18
18
 
19
19
  DEFAULT_LOGGING = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
20
20
 
21
21
  def initialize
22
22
  @on_failed_sanity_checks = :exit
23
+ @on_new_version_available = :ignore
23
24
  @parent_channel = "ApplicationCable::Channel"
24
25
  @logging = DEFAULT_LOGGING
25
26
  @middleware = ActionDispatch::MiddlewareStack.new
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StimulusReflex::Element < OpenStruct
4
- attr_reader :attributes, :data_attributes
4
+ attr_reader :attrs, :data_attrs
5
5
 
6
6
  def initialize(data = {})
7
- @attributes = HashWithIndifferentAccess.new(data["attrs"] || {})
8
- @data_attributes = HashWithIndifferentAccess.new(data["dataset"] || {})
9
- all_attributes = @attributes.merge(@data_attributes)
10
- super all_attributes.merge(all_attributes.transform_keys(&:underscore))
11
- @data_attributes.transform_keys! { |key| key.delete_prefix "data-" }
7
+ @attrs = HashWithIndifferentAccess.new(data["attrs"] || {})
8
+ datasets = data["dataset"] || {}
9
+ regular_dataset = datasets["dataset"] || {}
10
+ @data_attrs = build_data_attrs(regular_dataset, datasets["datasetAll"] || {})
11
+ all_attributes = @attrs.merge(@data_attrs)
12
+ super build_underscored(all_attributes)
13
+ @data_attrs.transform_keys! { |key| key.delete_prefix "data-" }
12
14
  end
13
15
 
14
16
  def signed
@@ -19,7 +21,29 @@ class StimulusReflex::Element < OpenStruct
19
21
  @unsigned ||= ->(accessor) { GlobalID::Locator.locate(dataset[accessor]) }
20
22
  end
21
23
 
24
+ def attributes
25
+ @attributes ||= OpenStruct.new(build_underscored(attrs))
26
+ end
27
+
22
28
  def dataset
23
- @dataset ||= OpenStruct.new(data_attributes.merge(data_attributes.transform_keys(&:underscore)))
29
+ @dataset ||= OpenStruct.new(build_underscored(data_attrs))
30
+ end
31
+
32
+ alias_method :data_attributes, :dataset
33
+
34
+ private
35
+
36
+ def build_data_attrs(dataset, dataset_all)
37
+ dataset_all.transform_keys! { |key| "data-#{key.delete_prefix("data-").pluralize}" }
38
+
39
+ dataset.each { |key, value| dataset_all[key]&.prepend(value) }
40
+
41
+ data_attrs = dataset.merge(dataset_all)
42
+
43
+ HashWithIndifferentAccess.new(data_attrs || {})
44
+ end
45
+
46
+ def build_underscored(attrs)
47
+ attrs.merge(attrs.transform_keys(&:underscore))
24
48
  end
25
49
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusReflex
4
+ class ReflexMethodInvocationPolicy
5
+ attr_reader :arguments, :required_params, :optional_params
6
+
7
+ def initialize(method, arguments)
8
+ @arguments = arguments
9
+ @required_params = method.parameters.select { |(kind, _)| kind == :req }
10
+ @optional_params = method.parameters.select { |(kind, _)| kind == :opt }
11
+ end
12
+
13
+ def no_arguments?
14
+ arguments.size == 0 && required_params.size == 0
15
+ end
16
+
17
+ def arguments?
18
+ arguments.size >= required_params.size && arguments.size <= required_params.size + optional_params.size
19
+ end
20
+
21
+ def unknown?
22
+ return false if no_arguments?
23
+ return false if arguments?
24
+
25
+ true
26
+ end
27
+ end
28
+ end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ClientAttributes = Struct.new(:reflex_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, keyword_init: true)
3
+ ClientAttributes = Struct.new(:reflex_id, :tab_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
7
  include StimulusReflex::Callbacks
8
+ include ActionView::Helpers::TagHelper
9
+ include CableReady::Identifiable
8
10
 
11
+ attr_accessor :payload
9
12
  attr_reader :cable_ready, :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
10
13
 
11
14
  alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
@@ -13,8 +16,7 @@ class StimulusReflex::Reflex
13
16
  delegate :connection, :stream_name, to: :channel
14
17
  delegate :controller_class, :flash, :session, to: :request
15
18
  delegate :broadcast, :broadcast_message, to: :broadcaster
16
- delegate :reflex_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, to: :client_attributes
17
- delegate :render, to: :controller_class
19
+ delegate :reflex_id, :tab_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, to: :client_attributes
18
20
 
19
21
  def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, params: {}, client_attributes: {})
20
22
  if is_a? CableReady::Broadcaster
@@ -37,7 +39,8 @@ class StimulusReflex::Reflex
37
39
  @broadcaster = StimulusReflex::PageBroadcaster.new(self)
38
40
  @logger = StimulusReflex::Logger.new(self)
39
41
  @client_attributes = ClientAttributes.new(client_attributes)
40
- @cable_ready = StimulusReflex::CableReadyChannels.new(stream_name)
42
+ @cable_ready = StimulusReflex::CableReadyChannels.new(stream_name, reflex_id)
43
+ @payload = {}
41
44
  self.params
42
45
  end
43
46
 
@@ -70,17 +73,15 @@ class StimulusReflex::Reflex
70
73
 
71
74
  req = ActionDispatch::Request.new(env)
72
75
 
73
- path_params = Rails.application.routes.recognize_path_with_request(req, url, req.env[:extras] || {})
74
- path_params[:controller] = path_params[:controller].force_encoding("UTF-8")
75
- path_params[:action] = path_params[:action].force_encoding("UTF-8")
76
+ # fetch path params (controller, action, ...) and apply them
77
+ request_params = StimulusReflex::RequestParameters.new(params: @params, req: req, url: url)
78
+ req = request_params.apply!
76
79
 
77
- req.env.merge(ActionDispatch::Http::Parameters::PARAMETERS_KEY => path_params)
78
- req.env["action_dispatch.request.parameters"] = req.parameters.merge(@params)
79
- req.tap { |r| r.session.send :load! }
80
+ req
80
81
  end
81
82
  end
82
83
 
83
- def morph(selectors, html = "")
84
+ def morph(selectors, html = nil)
84
85
  case selectors
85
86
  when :page
86
87
  raise StandardError.new("Cannot call :page morph after :#{broadcaster.to_sym} morph") unless broadcaster.page?
@@ -95,16 +96,25 @@ class StimulusReflex::Reflex
95
96
  end
96
97
 
97
98
  def controller
98
- @controller ||= begin
99
- controller_class.new.tap do |c|
100
- c.instance_variable_set :"@stimulus_reflex", true
101
- instance_variables.each { |name| c.instance_variable_set name, instance_variable_get(name) }
102
- c.set_request! request
103
- c.set_response! controller_class.make_response!(request)
104
- end
99
+ @controller ||= controller_class.new.tap do |c|
100
+ c.instance_variable_set :@stimulus_reflex, true
101
+ c.set_request! request
102
+ c.set_response! controller_class.make_response!(request)
105
103
  end
104
+
105
+ instance_variables.each { |name| @controller.instance_variable_set name, instance_variable_get(name) }
106
+ @controller
107
+ end
108
+
109
+ def controller?
110
+ !!defined? @controller
111
+ end
112
+
113
+ def render(*args)
114
+ controller_class.renderer.new(connection.env.merge("SCRIPT_NAME" => "")).render(*args)
106
115
  end
107
116
 
117
+ # Invoke the reflex action specified by `name` and run all callbacks
108
118
  def process(name, *args)
109
119
  reflex_invoked = false
110
120
  result = run_callbacks(:process) {
@@ -129,7 +139,12 @@ class StimulusReflex::Reflex
129
139
  @_params ||= ActionController::Parameters.new(request.parameters)
130
140
  end
131
141
 
132
- def dom_id(record_or_class, prefix = nil)
133
- "#" + ActionView::RecordIdentifier.dom_id(record_or_class, prefix).to_s
142
+ # morphdom needs content to be wrapped in an element with the same id when children_only: true
143
+ # Oddly, it doesn't matter if the target element is a div! See: https://docs.stimulusreflex.com/appendices/troubleshooting#different-element-type-altogether-who-cares-so-long-as-the-css-selector-matches
144
+ # Used internally to allow automatic partial collection rendering, but also useful to library users
145
+ # eg. `morph dom_id(@posts), render_collection(@posts)`
146
+ def render_collection(resource, content = nil)
147
+ content ||= render(resource)
148
+ tag.div(content.html_safe, id: dom_id(resource).from(1))
134
149
  end
135
150
  end
@@ -0,0 +1,79 @@
1
+ class StimulusReflex::ReflexData
2
+ attr_reader :data
3
+
4
+ def initialize(data)
5
+ @data = data
6
+ end
7
+
8
+ def reflex_name
9
+ reflex_name = target.split("#").first
10
+ reflex_name = reflex_name.camelize
11
+ reflex_name.end_with?("Reflex") ? reflex_name : "#{reflex_name}Reflex"
12
+ end
13
+
14
+ def selectors
15
+ selectors = (data["selectors"] || []).select(&:present?)
16
+ selectors = data["selectors"] = ["body"] if selectors.blank?
17
+ selectors
18
+ end
19
+
20
+ def target
21
+ data["target"].to_s
22
+ end
23
+
24
+ def method_name
25
+ target.split("#").second
26
+ end
27
+
28
+ def arguments
29
+ (data["args"] || []).map { |arg| object_with_indifferent_access arg } || []
30
+ end
31
+
32
+ def url
33
+ data["url"].to_s
34
+ end
35
+
36
+ def element
37
+ StimulusReflex::Element.new(data)
38
+ end
39
+
40
+ def permanent_attribute_name
41
+ data["permanentAttributeName"]
42
+ end
43
+
44
+ def form_data
45
+ Rack::Utils.parse_nested_query(data["formData"])
46
+ end
47
+
48
+ def form_params
49
+ form_data.deep_merge(data["params"] || {})
50
+ end
51
+
52
+ def reflex_id
53
+ data["reflexId"]
54
+ end
55
+
56
+ def tab_id
57
+ data["tabId"]
58
+ end
59
+
60
+ def xpath_controller
61
+ data["xpathController"]
62
+ end
63
+
64
+ def xpath_element
65
+ data["xpathElement"]
66
+ end
67
+
68
+ def reflex_controller
69
+ data["reflexController"]
70
+ end
71
+
72
+ private
73
+
74
+ def object_with_indifferent_access(object)
75
+ return object.with_indifferent_access if object.respond_to?(:with_indifferent_access)
76
+ object.map! { |obj| object_with_indifferent_access obj } if object.is_a?(Array)
77
+ object
78
+ end
79
+ end
@@ -1,62 +1,31 @@
1
- # frozen_string_literal: true
2
-
3
1
  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
2
+ class << self
3
+ attr_reader :reflex_data
4
+
5
+ def create_reflex_from_data(channel, reflex_data)
6
+ @reflex_data = reflex_data
7
+ reflex_class.new(channel,
8
+ url: reflex_data.url,
9
+ element: reflex_data.element,
10
+ selectors: reflex_data.selectors,
11
+ method_name: reflex_data.method_name,
12
+ params: reflex_data.form_params,
13
+ client_attributes: {
14
+ reflex_id: reflex_data.reflex_id,
15
+ tab_id: reflex_data.tab_id,
16
+ xpath_controller: reflex_data.xpath_controller,
17
+ xpath_element: reflex_data.xpath_element,
18
+ reflex_controller: reflex_data.reflex_controller,
19
+ permanent_attribute_name: reflex_data.permanent_attribute_name
20
+ })
30
21
  end
31
22
 
32
- if !safe_ancestors.include?(reflex_method.owner)
33
- raise argument_error
23
+ def reflex_class
24
+ reflex_data.reflex_name.constantize.tap { |klass| raise ArgumentError.new("#{reflex_name} is not a StimulusReflex::Reflex") unless is_reflex?(klass) }
34
25
  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
26
 
45
- def reflex_method
46
- if reflex_class.public_instance_methods.include?(method_name.to_sym)
47
- reflex_class.public_instance_method(method_name)
27
+ def is_reflex?(klass)
28
+ klass.ancestors.include? StimulusReflex::Reflex
48
29
  end
49
30
  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
31
  end
@@ -0,0 +1,19 @@
1
+ module StimulusReflex
2
+ class RequestParameters
3
+ def initialize(params:, req:, url:)
4
+ @params = params
5
+ @req = req
6
+ @url = url
7
+ end
8
+
9
+ def apply!
10
+ path_params = Rails.application.routes.recognize_path_with_request(@req, @url, @req.env[:extras] || {})
11
+ path_params[:controller] = path_params[:controller].force_encoding("UTF-8")
12
+ path_params[:action] = path_params[:action].force_encoding("UTF-8")
13
+
14
+ @req.env.merge(ActionDispatch::Http::Parameters::PARAMETERS_KEY => path_params)
15
+ @req.env["action_dispatch.request.parameters"] = @req.parameters.merge(@params)
16
+ @req.tap { |r| r.session.send :load! }
17
+ end
18
+ end
19
+ end
@@ -12,12 +12,10 @@ module StimulusReflex
12
12
  def print
13
13
  return unless config_logging.instance_of?(Proc)
14
14
 
15
- puts
16
15
  reflex.broadcaster.operations.each do
17
16
  puts instance_eval(&config_logging) + "\e[0m"
18
17
  @current_operation += 1
19
18
  end
20
- puts
21
19
  end
22
20
 
23
21
  private