stimulus_reflex 3.4.0.pre6 → 3.4.1

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.

@@ -40,8 +40,8 @@ class StimulusReflex::Channel < StimulusReflex.configuration.parent_channel.cons
40
40
  params: params,
41
41
  client_attributes: {
42
42
  reflex_id: data["reflexId"],
43
- xpath: data["xpath"],
44
- c_xpath: data["cXpath"],
43
+ xpath_controller: data["xpathController"],
44
+ xpath_element: data["xpathElement"],
45
45
  reflex_controller: data["reflexController"],
46
46
  permanent_attribute_name: permanent_attribute_name
47
47
  })
@@ -49,11 +49,33 @@ class StimulusReflex::Channel < StimulusReflex.configuration.parent_channel.cons
49
49
  rescue => invoke_error
50
50
  message = exception_message_with_backtrace(invoke_error)
51
51
  body = "Reflex #{target} failed: #{message} [#{url}]"
52
+
52
53
  if reflex
53
54
  reflex.rescue_with_handler(invoke_error)
54
55
  reflex.broadcast_message subject: "error", body: body, data: data, error: invoke_error
55
56
  else
56
57
  puts "\e[31m#{body}\e[0m"
58
+
59
+ if body.to_s.include? "No route matches"
60
+ initializer_path = Rails.root.join("config", "initializers", "stimulus_reflex.rb")
61
+
62
+ puts <<~NOTE
63
+ \e[33mNOTE: StimulusReflex failed to locate a matching route and could not re-render the page.
64
+
65
+ If your app uses Rack middleware to rewrite part of the request path, you must enable those middleware modules in StimulusReflex.
66
+ The StimulusReflex initializer should be located at #{initializer_path}, or you can generate it with:
67
+
68
+ $ bundle exec rails generate stimulus_reflex:config
69
+
70
+ Configure any required middleware:
71
+
72
+ StimulusReflex.configure do |config|
73
+ config.middleware.use FirstRackMiddleware
74
+ config.middleware.use SecondRackMiddleware
75
+ end\e[0m
76
+
77
+ NOTE
78
+ end
57
79
  end
58
80
  return
59
81
  end
@@ -16,6 +16,7 @@ class <%= class_name %>Reflex < ApplicationReflex
16
16
  # - signed - use a signed Global ID to map dataset attribute to a model eg. element.signed[:foo]
17
17
  # - unsigned - use an unsigned Global ID to map dataset attribute to a model eg. element.unsigned[:foo]
18
18
  # - cable_ready - a special cable_ready that can broadcast to the current visitor (no brackets needed)
19
+ # - reflex_id - a UUIDv4 that uniquely identies each Reflex
19
20
  #
20
21
  # Example:
21
22
  #
@@ -12,9 +12,18 @@ StimulusReflex.configure do |config|
12
12
 
13
13
  # Customize server-side Reflex logging format, with optional colorization:
14
14
  # Available tokens: session_id, session_id_full, reflex_info, operation, reflex_id, reflex_id_full, mode, selector, operation_counter, connection_id, connection_id_full, timestamp
15
- # Available colors: green, yellow, blue, magenta, cyan, white
15
+ # Available colors: red, green, yellow, blue, magenta, cyan, white
16
16
  # You can also use attributes from your ActionCable Connection's identifiers that resolve to valid ActiveRecord models
17
17
  # eg. if your connection is `identified_by :current_user` and your User model has an email attribute, you can access r.email (it will display `-` if the user isn't logged in)
18
+ # Learn more at: https://docs.stimulusreflex.com/troubleshooting#stimulusreflex-logging
18
19
 
19
20
  # config.logging = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
21
+
22
+ # Optimized for speed, StimulusReflex doesn't enable Rack middleware by default.
23
+ # If you are using Page Morphs and your app uses Rack middleware to rewrite part of the request path, you must enable those middleware modules in StimulusReflex.
24
+ #
25
+ # Learn more about registering Rack middleware in Rails here: https://guides.rubyonrails.org/rails_on_rack.html#configuring-middleware-stack
26
+
27
+ # config.middleware.use FirstRackMiddleware
28
+ # config.middleware.use SecondRackMiddleware
20
29
  end
@@ -11,6 +11,7 @@ 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"
15
16
  require "stimulus_reflex/element"
16
17
  require "stimulus_reflex/sanity_checker"
@@ -24,7 +25,7 @@ require "stimulus_reflex/logger"
24
25
  module StimulusReflex
25
26
  class Engine < Rails::Engine
26
27
  initializer "stimulus_reflex.sanity_check" do
27
- SanityChecker.check!
28
+ SanityChecker.check! unless Rails.env.production?
28
29
  end
29
30
  end
30
31
  end
@@ -23,6 +23,7 @@ module StimulusReflex
23
23
  })
24
24
  )
25
25
  end
26
+
26
27
  cable_ready.broadcast
27
28
  end
28
29
 
@@ -1,18 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StimulusReflex
2
4
  class CableReadyChannels
5
+ delegate :[], to: "cable_ready_channels"
6
+
3
7
  def initialize(stream_name)
4
- @cable_ready_channels = CableReady::Channels.instance
5
- @stimulus_reflex_channel = @cable_ready_channels[stream_name]
8
+ @stream_name = stream_name
9
+ end
10
+
11
+ def cable_ready_channels
12
+ CableReady::Channels.instance
13
+ end
14
+
15
+ def stimulus_reflex_channel
16
+ CableReady::Channels.instance[@stream_name]
6
17
  end
7
18
 
8
19
  def method_missing(name, *args)
9
- return @stimulus_reflex_channel.send(name, *args) if @stimulus_reflex_channel.respond_to?(name)
10
- @cable_ready_channels.send(name, *args)
20
+ return stimulus_reflex_channel.public_send(name, *args) if stimulus_reflex_channel.respond_to?(name)
21
+ super
11
22
  end
12
23
 
13
24
  def respond_to_missing?(name, include_all)
14
- @stimulus_reflex_channel.respond_to?(name, include_all) ||
15
- @cable_ready_channels.respond_to?(name, include_all)
25
+ stimulus_reflex_channel.respond_to?(name) || super
16
26
  end
17
27
  end
18
28
  end
@@ -0,0 +1,48 @@
1
+ require "active_support/concern"
2
+
3
+ module StimulusReflex
4
+ module Callbacks
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include ActiveSupport::Callbacks
9
+ define_callbacks :process, skip_after_callbacks_if_terminated: true
10
+ end
11
+
12
+ class_methods do
13
+ def before_reflex(*args, &block)
14
+ add_callback(:before, *args, &block)
15
+ end
16
+
17
+ def after_reflex(*args, &block)
18
+ add_callback(:after, *args, &block)
19
+ end
20
+
21
+ def around_reflex(*args, &block)
22
+ add_callback(:around, *args, &block)
23
+ end
24
+
25
+ private
26
+
27
+ 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)
31
+ end
32
+
33
+ def normalize_callback_options!(options)
34
+ normalize_callback_option! options, :only, :if
35
+ normalize_callback_option! options, :except, :unless
36
+ options
37
+ end
38
+
39
+ def normalize_callback_option!(options, from, to)
40
+ if (from = options.delete(from))
41
+ from_set = Array(from).map(&:to_s).to_set
42
+ from = proc { |reflex| from_set.include? reflex.method_name }
43
+ options[to] = Array(options[to]).unshift(from)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -14,7 +14,7 @@ module StimulusReflex
14
14
  end
15
15
 
16
16
  class Configuration
17
- attr_accessor :on_failed_sanity_checks, :parent_channel, :logging
17
+ attr_accessor :on_failed_sanity_checks, :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
 
@@ -22,6 +22,7 @@ module StimulusReflex
22
22
  @on_failed_sanity_checks = :exit
23
23
  @parent_channel = "ApplicationCable::Channel"
24
24
  @logging = DEFAULT_LOGGING
25
+ @middleware = ActionDispatch::MiddlewareStack.new
25
26
  end
26
27
  end
27
28
  end
@@ -79,22 +79,22 @@ module StimulusReflex
79
79
  Time.now.strftime("%Y-%m-%d %H:%M:%S")
80
80
  end
81
81
 
82
- def method_missing method
83
- return send(method.to_sym) if private_instance_methods.include?(method.to_sym)
82
+ def method_missing(name, *args)
83
+ return send(name) if private_instance_methods.include?(name.to_sym)
84
84
 
85
85
  reflex.connection.identifiers.each do |identifier|
86
86
  ident = reflex.connection.send(identifier)
87
- return ident.send(method) if ident.respond_to?(:attributes) && ident.attributes.key?(method.to_s)
87
+ return ident.send(name) if ident.respond_to?(:attributes) && ident.attributes.key?(name.to_s)
88
88
  end
89
89
  "-"
90
90
  end
91
91
 
92
- def respond_to_missing? method
93
- return true if private_instance_methods.include?(method.to_sym)
92
+ def respond_to_missing?(name, include_all)
93
+ return true if private_instance_methods.include?(name.to_sym)
94
94
 
95
95
  reflex.connection.identifiers.each do |identifier|
96
96
  ident = reflex.connection.send(identifier)
97
- return true if ident.respond_to?(:attributes) && ident.attributes.key?(method.to_s)
97
+ return true if ident.respond_to?(:attributes) && ident.attributes.key?(name.to_s)
98
98
  end
99
99
  false
100
100
  end
@@ -1,59 +1,33 @@
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
 
49
11
  alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
50
12
 
51
13
  delegate :connection, :stream_name, to: :channel
52
- delegate :flash, :session, to: :request
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
17
+ delegate :render, to: :controller_class
55
18
 
56
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
+
57
31
  @channel = channel
58
32
  @url = url
59
33
  @element = element
@@ -86,6 +60,14 @@ class StimulusReflex::Reflex
86
60
  )
87
61
 
88
62
  env = connection.env.merge(mock_env)
63
+
64
+ middleware = StimulusReflex.config.middleware
65
+
66
+ if middleware.any?
67
+ stack = middleware.build(Rails.application.routes)
68
+ stack.call(env)
69
+ end
70
+
89
71
  req = ActionDispatch::Request.new(env)
90
72
 
91
73
  path_params = Rails.application.routes.recognize_path_with_request(req, url, req.env[:extras] || {})
@@ -114,11 +96,11 @@ class StimulusReflex::Reflex
114
96
 
115
97
  def controller
116
98
  @controller ||= begin
117
- request.controller_class.new.tap do |c|
99
+ controller_class.new.tap do |c|
118
100
  c.instance_variable_set :"@stimulus_reflex", true
119
101
  instance_variables.each { |name| c.instance_variable_set name, instance_variable_get(name) }
120
- c.request = request
121
- c.response = ActionDispatch::Response.new
102
+ c.set_request! request
103
+ c.set_response! controller_class.make_response!(request)
122
104
  end
123
105
  end
124
106
  end
@@ -146,4 +128,8 @@ class StimulusReflex::Reflex
146
128
  def params
147
129
  @_params ||= ActionController::Parameters.new(request.parameters)
148
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
149
135
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StimulusReflex::SanityChecker
4
+ NODE_VERSION_FORMAT = /(\d+\.\d+\.\d+.*):/
4
5
  JSON_VERSION_FORMAT = /(\d+\.\d+\.\d+.*)"/
5
6
 
6
7
  class << self
@@ -86,6 +87,8 @@ class StimulusReflex::SanityChecker
86
87
  def find_javascript_package_version
87
88
  if (match = search_file(package_json_path, regex: /version/))
88
89
  match[JSON_VERSION_FORMAT, 1]
90
+ elsif (match = search_file(yarn_lock_path, regex: /^stimulus_reflex/))
91
+ match[NODE_VERSION_FORMAT, 1]
89
92
  end
90
93
  end
91
94
 
@@ -98,6 +101,10 @@ class StimulusReflex::SanityChecker
98
101
  Rails.root.join("node_modules", "stimulus_reflex", "package.json")
99
102
  end
100
103
 
104
+ def yarn_lock_path
105
+ Rails.root.join("yarn.lock")
106
+ end
107
+
101
108
  def initializer_path
102
109
  @_initializer_path ||= Rails.root.join("config", "initializers", "stimulus_reflex.rb")
103
110
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusReflex
4
- VERSION = "3.4.0.pre6"
4
+ VERSION = "3.4.1"
5
5
  end
@@ -6,9 +6,10 @@ require "stimulus_reflex/version"
6
6
  namespace :stimulus_reflex do
7
7
  desc "Install StimulusReflex in this application"
8
8
  task install: :environment do
9
- system "bundle exec rails webpacker:install:stimulus"
9
+ system "rails dev:cache" unless Rails.root.join("tmp", "caching-dev.txt").exist?
10
10
  gem_version = StimulusReflex::VERSION.gsub(".pre", "-pre")
11
11
  system "yarn add stimulus_reflex@#{gem_version}"
12
+ system "bundle exec rails webpacker:install:stimulus"
12
13
  main_folder = defined?(Webpacker) ? Webpacker.config.source_path.to_s.gsub("#{Rails.root}/", "") : "app/javascript"
13
14
 
14
15
  FileUtils.mkdir_p Rails.root.join("#{main_folder}/controllers"), verbose: true
@@ -39,7 +40,7 @@ namespace :stimulus_reflex do
39
40
 
40
41
  unless lines.find { |line| line.start_with?("import controller") }
41
42
  matches = lines.select { |line| line =~ /\A(require|import)/ }
42
- lines.insert lines.index(matches.last).to_i + 1, "import controller from './application_controller'\n"
43
+ lines.insert lines.index(matches.last).to_i + 1, "import controller from '../controllers/application_controller'\n"
43
44
  end
44
45
 
45
46
  initialize_line = lines.find { |line| line.start_with?("StimulusReflex.initialize") }
@@ -67,6 +68,10 @@ namespace :stimulus_reflex do
67
68
  system "bundle exec rails generate stimulus_reflex example"
68
69
  puts "Generating default StimulusReflex configuration file into your application config/initializers directory"
69
70
  system "bundle exec rails generate stimulus_reflex:config"
70
- system "rails dev:cache" unless Rails.root.join("tmp", "caching-dev.txt").exist?
71
+
72
+ puts
73
+ puts "StimulusReflex and CableReady have been successfully installed!"
74
+ puts "Go to https://docs.stimulusreflex.com/quickstart if you need help getting started."
75
+ puts
71
76
  end
72
77
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stimulus_reflex",
3
- "version": "3.4.0-pre5",
3
+ "version": "3.4.0",
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",
@@ -52,6 +52,6 @@
52
52
  "jsdom": "^16.0.1",
53
53
  "mocha": "^8.0.1",
54
54
  "prettier-standard": "^16.1.0",
55
- "stimulus": "^1.1.1"
55
+ "stimulus": ">= 1.1"
56
56
  }
57
57
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path("../lib/stimulus_reflex/version", __FILE__)
2
4
 
3
5
  Gem::Specification.new do |gem|
@@ -30,7 +32,7 @@ Gem::Specification.new do |gem|
30
32
  gem.add_dependency "nokogiri"
31
33
  gem.add_dependency "rails", ">= 5.2"
32
34
  gem.add_dependency "redis"
33
- gem.add_dependency "cable_ready", ">= 4.4"
35
+ gem.add_dependency "cable_ready", ">= 4.5"
34
36
 
35
37
  gem.add_development_dependency "bundler", "~> 2.0"
36
38
  gem.add_development_dependency "pry-nav"