stimulus_reflex 3.3.0.pre6 → 3.4.0.pre3

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -4
  3. data/Gemfile.lock +69 -68
  4. data/README.md +6 -3
  5. data/{lib → app/channels}/stimulus_reflex/channel.rb +24 -18
  6. data/lib/generators/stimulus_reflex/config_generator.rb +14 -0
  7. data/lib/generators/{stimulus_reflex_generator.rb → stimulus_reflex/stimulus_reflex_generator.rb} +0 -0
  8. data/lib/generators/{templates → stimulus_reflex/templates}/app/javascript/controllers/%file_name%_controller.js.tt +0 -0
  9. data/lib/generators/{templates → stimulus_reflex/templates}/app/javascript/controllers/application_controller.js.tt +0 -0
  10. data/lib/generators/{templates → stimulus_reflex/templates}/app/reflexes/%file_name%_reflex.rb.tt +0 -0
  11. data/lib/generators/{templates → stimulus_reflex/templates}/app/reflexes/application_reflex.rb.tt +0 -0
  12. data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +10 -0
  13. data/lib/stimulus_reflex.rb +1 -2
  14. data/lib/stimulus_reflex/broadcasters/broadcaster.rb +3 -7
  15. data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +2 -2
  16. data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +2 -2
  17. data/lib/stimulus_reflex/configuration.rb +24 -0
  18. data/lib/stimulus_reflex/element.rb +8 -0
  19. data/lib/stimulus_reflex/reflex.rb +24 -20
  20. data/lib/stimulus_reflex/sanity_checker.rb +69 -18
  21. data/lib/stimulus_reflex/version.rb +1 -1
  22. data/lib/tasks/stimulus_reflex/install.rake +12 -8
  23. data/package.json +63 -0
  24. data/stimulus_reflex.gemspec +40 -0
  25. data/tags +98 -0
  26. data/test/generators/stimulus_reflex_generator_test.rb +1 -0
  27. data/test/tmp/app/reflexes/application_reflex.rb +12 -0
  28. data/test/tmp/app/reflexes/user_reflex.rb +33 -0
  29. data/yarn.lock +6256 -0
  30. metadata +38 -27
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module StimulusReflex
6
+ class ConfigGenerator < Rails::Generators::Base
7
+ desc "Creates an StimulusReflex configuration file in config/initializers"
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def copy_config_file
11
+ copy_file "config/initializers/stimulus_reflex.rb"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ StimulusReflex.configure do |config|
4
+ # Enable/disable exiting / warning when the sanity checks fail options:
5
+ # `:exit` or `:warn` or `:ignore`
6
+ # config.on_failed_sanity_checks = :exit
7
+
8
+ # Override the parent class that the StimulusReflex ActionCable channel inherits from
9
+ # config.parent_channel = "ApplicationCable::Channel"
10
+ end
@@ -9,15 +9,14 @@ require "action_cable"
9
9
  require "nokogiri"
10
10
  require "cable_ready"
11
11
  require "stimulus_reflex/version"
12
+ require "stimulus_reflex/configuration"
12
13
  require "stimulus_reflex/reflex"
13
14
  require "stimulus_reflex/element"
14
- require "stimulus_reflex/channel"
15
15
  require "stimulus_reflex/sanity_checker"
16
16
  require "stimulus_reflex/broadcasters/broadcaster"
17
17
  require "stimulus_reflex/broadcasters/nothing_broadcaster"
18
18
  require "stimulus_reflex/broadcasters/page_broadcaster"
19
19
  require "stimulus_reflex/broadcasters/selector_broadcaster"
20
- require "generators/stimulus_reflex_generator"
21
20
 
22
21
  module StimulusReflex
23
22
  class Engine < Rails::Engine
@@ -24,22 +24,18 @@ module StimulusReflex
24
24
  false
25
25
  end
26
26
 
27
- def enqueue_message(subject:, body: nil, data: {})
27
+ def broadcast_message(subject:, body: nil, data: {}, error: nil)
28
28
  logger.error "\e[31m#{body}\e[0m" if subject == "error"
29
29
  cable_ready[stream_name].dispatch_event(
30
30
  name: "stimulus-reflex:server-message",
31
31
  detail: {
32
32
  reflexId: data["reflexId"],
33
33
  stimulus_reflex: data.merge(
34
- broadcaster: to_sym,
35
- server_message: {subject: subject, body: body}
34
+ morph: to_sym,
35
+ server_message: {subject: subject, body: error&.to_s}
36
36
  )
37
37
  }
38
38
  )
39
- end
40
-
41
- def broadcast_message(subject:, body: nil, data: {})
42
- enqueue_message subject: subject, body: body, data: data
43
39
  cable_ready.broadcast
44
40
  end
45
41
 
@@ -3,7 +3,7 @@
3
3
  module StimulusReflex
4
4
  class PageBroadcaster < Broadcaster
5
5
  def broadcast(selectors, data)
6
- reflex.controller.process reflex.url_params[:action]
6
+ reflex.controller.process reflex.params[:action]
7
7
  page_html = reflex.controller.response.body
8
8
 
9
9
  return unless page_html.present?
@@ -18,7 +18,7 @@ module StimulusReflex
18
18
  children_only: true,
19
19
  permanent_attribute_name: permanent_attribute_name,
20
20
  stimulus_reflex: data.merge({
21
- broadcaster: to_sym
21
+ morph: to_sym
22
22
  })
23
23
  )
24
24
  end
@@ -17,7 +17,7 @@ module StimulusReflex
17
17
  children_only: true,
18
18
  permanent_attribute_name: permanent_attribute_name,
19
19
  stimulus_reflex: data.merge({
20
- broadcaster: to_sym
20
+ morph: to_sym
21
21
  })
22
22
  )
23
23
  else
@@ -25,7 +25,7 @@ module StimulusReflex
25
25
  selector: selector,
26
26
  html: fragment.to_html,
27
27
  stimulus_reflex: data.merge({
28
- broadcaster: to_sym
28
+ morph: to_sym
29
29
  })
30
30
  )
31
31
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusReflex
4
+ class << self
5
+ def configure
6
+ yield configuration
7
+ end
8
+
9
+ def configuration
10
+ @configuration ||= Configuration.new
11
+ end
12
+
13
+ alias_method :config, :configuration
14
+ end
15
+
16
+ class Configuration
17
+ attr_accessor :on_failed_sanity_checks, :parent_channel
18
+
19
+ def initialize
20
+ @on_failed_sanity_checks = :exit
21
+ @parent_channel = "ApplicationCable::Channel"
22
+ end
23
+ end
24
+ end
@@ -11,6 +11,14 @@ class StimulusReflex::Element < OpenStruct
11
11
  @data_attributes.transform_keys! { |key| key.delete_prefix "data-" }
12
12
  end
13
13
 
14
+ def signed
15
+ @signed ||= ->(accessor) { GlobalID::Locator.locate_signed(dataset[accessor]) }
16
+ end
17
+
18
+ def unsigned
19
+ @unsigned ||= ->(accessor) { GlobalID::Locator.locate(dataset[accessor]) }
20
+ end
21
+
14
22
  def dataset
15
23
  @dataset ||= OpenStruct.new(data_attributes.merge(data_attributes.transform_keys(&:underscore)))
16
24
  end
@@ -43,13 +43,15 @@ class StimulusReflex::Reflex
43
43
  end
44
44
  end
45
45
 
46
- attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :permanent_attribute_name
46
+ attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :permanent_attribute_name, :reflex_id
47
+
48
+ alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
47
49
 
48
50
  delegate :connection, :stream_name, to: :channel
49
- delegate :session, to: :request
51
+ delegate :flash, :session, to: :request
50
52
  delegate :broadcast, :broadcast_message, to: :broadcaster
51
53
 
52
- def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, permanent_attribute_name: nil, params: {})
54
+ def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, permanent_attribute_name: nil, params: {}, reflex_id: nil)
53
55
  @channel = channel
54
56
  @url = url
55
57
  @element = element
@@ -58,6 +60,7 @@ class StimulusReflex::Reflex
58
60
  @params = params
59
61
  @permanent_attribute_name = permanent_attribute_name
60
62
  @broadcaster = StimulusReflex::PageBroadcaster.new(self)
63
+ @reflex_id = reflex_id
61
64
  self.params
62
65
  end
63
66
 
@@ -66,21 +69,26 @@ class StimulusReflex::Reflex
66
69
  uri = URI.parse(url)
67
70
  path = ActionDispatch::Journey::Router::Utils.normalize_path(uri.path)
68
71
  query_hash = Rack::Utils.parse_nested_query(uri.query)
69
- req = ActionDispatch::Request.new(
70
- connection.env.merge(
71
- Rack::MockRequest.env_for(uri.to_s).merge(
72
- "rack.request.query_hash" => query_hash,
73
- "rack.request.query_string" => uri.query,
74
- "ORIGINAL_SCRIPT_NAME" => "",
75
- "ORIGINAL_FULLPATH" => path,
76
- Rack::SCRIPT_NAME => "",
77
- Rack::PATH_INFO => path,
78
- Rack::REQUEST_PATH => path,
79
- Rack::QUERY_STRING => uri.query
80
- )
81
- )
72
+ mock_env = Rack::MockRequest.env_for(uri.to_s)
73
+
74
+ mock_env.merge!(
75
+ "rack.request.query_hash" => query_hash,
76
+ "rack.request.query_string" => uri.query,
77
+ "ORIGINAL_SCRIPT_NAME" => "",
78
+ "ORIGINAL_FULLPATH" => path,
79
+ Rack::SCRIPT_NAME => "",
80
+ Rack::PATH_INFO => path,
81
+ Rack::REQUEST_PATH => path,
82
+ Rack::QUERY_STRING => uri.query
82
83
  )
84
+
85
+ env = connection.env.merge(mock_env)
86
+ req = ActionDispatch::Request.new(env)
87
+
83
88
  path_params = Rails.application.routes.recognize_path_with_request(req, url, req.env[:extras] || {})
89
+ path_params[:controller] = path_params[:controller].force_encoding("UTF-8")
90
+ path_params[:action] = path_params[:action].force_encoding("UTF-8")
91
+
84
92
  req.env.merge(ActionDispatch::Http::Parameters::PARAMETERS_KEY => path_params)
85
93
  req.env["action_dispatch.request.parameters"] = req.parameters.merge(@params)
86
94
  req.tap { |r| r.session.send :load! }
@@ -112,10 +120,6 @@ class StimulusReflex::Reflex
112
120
  end
113
121
  end
114
122
 
115
- def url_params
116
- @url_params ||= Rails.application.routes.recognize_path_with_request(request, request.path, request.env[:extras] || {})
117
- end
118
-
119
123
  def process(name, *args)
120
124
  reflex_invoked = false
121
125
  result = run_callbacks(:process) {
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StimulusReflex::SanityChecker
4
- NODE_VERSION_FORMAT = /(\d+\.\d+\.\d+.*):/
5
4
  JSON_VERSION_FORMAT = /(\d+\.\d+\.\d+.*)"/
6
5
 
7
6
  def self.check!
7
+ return if StimulusReflex.config.on_failed_sanity_checks == :ignore
8
+
8
9
  instance = new
9
10
  instance.check_caching_enabled
10
11
  instance.check_javascript_package_version
@@ -12,25 +13,32 @@ class StimulusReflex::SanityChecker
12
13
 
13
14
  def check_caching_enabled
14
15
  unless caching_enabled?
15
- puts <<~WARN
16
- WARNING: Stimulus Reflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
16
+ warn_and_exit <<~WARN
17
+ Stimulus Reflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
17
18
  To enable caching in development, run:
18
19
  rails dev:cache
19
20
  WARN
20
21
  end
22
+
23
+ unless not_null_store?
24
+ warn_and_exit <<~WARN
25
+ Stimulus Reflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
26
+ But your config.cache_store is set to :null_store, so it won't work.
27
+ WARN
28
+ end
21
29
  end
22
30
 
23
31
  def check_javascript_package_version
24
32
  if javascript_package_version.nil?
25
- puts <<~WARN
26
- WARNING: Can't locate the stimulus_reflex NPM package.
33
+ warn_and_exit <<~WARN
34
+ Can't locate the stimulus_reflex NPM package.
27
35
  Either add it to your package.json as a dependency or use "yarn link stimulus_reflex" if you are doing development.
28
36
  WARN
29
37
  end
30
38
 
31
39
  unless javascript_version_matches?
32
- puts <<~WARN
33
- WARNING: The Stimulus Reflex javascript package version (#{javascript_package_version}) does not match the Rubygem version (#{gem_version}).
40
+ warn_and_exit <<~WARN
41
+ The Stimulus Reflex javascript package version (#{javascript_package_version}) does not match the Rubygem version (#{gem_version}).
34
42
  To update the Stimulus Reflex npm package:
35
43
  yarn upgrade stimulus_reflex@#{gem_version}
36
44
  WARN
@@ -40,8 +48,11 @@ class StimulusReflex::SanityChecker
40
48
  private
41
49
 
42
50
  def caching_enabled?
43
- Rails.application.config.action_controller.perform_caching &&
44
- Rails.application.config.cache_store != :null_store
51
+ Rails.application.config.action_controller.perform_caching
52
+ end
53
+
54
+ def not_null_store?
55
+ Rails.application.config.cache_store != :null_store
45
56
  end
46
57
 
47
58
  def javascript_version_matches?
@@ -53,14 +64,11 @@ class StimulusReflex::SanityChecker
53
64
  end
54
65
 
55
66
  def javascript_package_version
56
- return @_js_version if defined?(@_js_version)
57
- @_js_version = find_javascript_package_version
67
+ @_js_version ||= find_javascript_package_version
58
68
  end
59
69
 
60
70
  def find_javascript_package_version
61
- if (match = search_file(yarn_lock_path, regex: /^stimulus_reflex/))
62
- match[NODE_VERSION_FORMAT, 1]
63
- elsif (match = search_file(yarn_link_path, regex: /version/))
71
+ if (match = search_file(package_json_path, regex: /version/))
64
72
  match[JSON_VERSION_FORMAT, 1]
65
73
  end
66
74
  end
@@ -70,11 +78,54 @@ class StimulusReflex::SanityChecker
70
78
  File.foreach(path).grep(regex).first
71
79
  end
72
80
 
73
- def yarn_lock_path
74
- Rails.root.join("yarn.lock")
81
+ def package_json_path
82
+ Rails.root.join("node_modules", "stimulus_reflex", "package.json")
75
83
  end
76
84
 
77
- def yarn_link_path
78
- Rails.root.join("node_modules", "stimulus_reflex", "package.json")
85
+ def initializer_path
86
+ @_initializer_path ||= Rails.root.join("config", "initializers", "stimulus_reflex.rb")
87
+ end
88
+
89
+ def warn_and_exit(text)
90
+ puts "WARNING:"
91
+ puts text
92
+ exit_with_info if StimulusReflex.config.on_failed_sanity_checks == :exit
93
+ end
94
+
95
+ def exit_with_info
96
+ puts
97
+
98
+ # bundle exec rails generate stimulus_reflex:config
99
+ if File.exist?(initializer_path)
100
+ puts <<~INFO
101
+ If you know what you are doing and you want to start the application anyway,
102
+ you can add the following directive to the StimulusReflex initializer,
103
+ which is located at #{initializer_path}
104
+
105
+ StimulusReflex.configure do |config|
106
+ config.on_failed_sanity_checks = :warn
107
+ end
108
+
109
+ INFO
110
+ else
111
+ puts <<~INFO
112
+ If you know what you are doing and you want to start the application anyway,
113
+ you can create a StimulusReflex initializer with the command:
114
+
115
+ bundle exec rails generate stimulus_reflex:config
116
+
117
+ Then open your initializer at
118
+
119
+ <RAILS_ROOT>/config/initializers/stimulus_reflex.rb
120
+
121
+ and then add the following directive:
122
+
123
+ StimulusReflex.configure do |config|
124
+ config.on_failed_sanity_checks = :warn
125
+ end
126
+
127
+ INFO
128
+ end
129
+ exit
79
130
  end
80
131
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusReflex
4
- VERSION = "3.3.0.pre6"
4
+ VERSION = "3.4.0.pre3"
5
5
  end
@@ -6,18 +6,20 @@ 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 add cable_ready"
9
10
  system "bundle exec rails webpacker:install:stimulus"
10
11
  gem_version = StimulusReflex::VERSION.gsub(".pre", "-pre")
11
- system "yarn add stimulus_reflex@#{gem_version}"
12
+ system "yarn add cable_ready stimulus_reflex@#{gem_version}"
13
+ main_folder = defined?(Webpacker) ? Webpacker.config.source_path.to_s.gsub("#{Rails.root}/", "") : "app/javascript"
12
14
 
13
- FileUtils.mkdir_p Rails.root.join("app/javascript/controllers"), verbose: true
15
+ FileUtils.mkdir_p Rails.root.join("#{main_folder}/controllers"), verbose: true
14
16
  FileUtils.mkdir_p Rails.root.join("app/reflexes"), verbose: true
15
17
 
16
- filepath = %w[
17
- app/javascript/controllers/index.js
18
- app/javascript/controllers/index.ts
19
- app/javascript/packs/application.js
20
- app/javascript/packs/application.ts
18
+ filepath = [
19
+ "#{main_folder}/controllers/index.js",
20
+ "#{main_folder}/controllers/index.ts",
21
+ "#{main_folder}/packs/application.js",
22
+ "#{main_folder}/packs/application.ts"
21
23
  ]
22
24
  .select { |path| File.exist?(path) }
23
25
  .map { |path| Rails.root.join(path) }
@@ -42,7 +44,8 @@ namespace :stimulus_reflex do
42
44
  end
43
45
 
44
46
  initialize_line = lines.find { |line| line.start_with?("StimulusReflex.initialize") }
45
- lines << "StimulusReflex.initialize(application, { consumer, controller, debug: false })\n" unless initialize_line
47
+ lines << "StimulusReflex.initialize(application, { consumer, controller, isolate: true })\n" unless initialize_line
48
+ lines << "StimulusReflex.debug = process.env.RAILS_ENV === 'development'\n" unless initialize_line
46
49
  File.open(filepath, "w") { |f| f.write lines.join }
47
50
 
48
51
  filepath = Rails.root.join("config/environments/development.rb")
@@ -63,6 +66,7 @@ namespace :stimulus_reflex do
63
66
  end
64
67
 
65
68
  system "bundle exec rails generate stimulus_reflex example"
69
+ system "bundle exec rails generate stimulus_reflex:config"
66
70
  system "rails dev:cache" unless Rails.root.join("tmp", "caching-dev.txt").exist?
67
71
  end
68
72
  end
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "stimulus_reflex",
3
+ "version": "3.4.0-pre2",
4
+ "description": "Build reactive applications with the Rails tooling you already know and love.",
5
+ "keywords": [
6
+ "ruby",
7
+ "rails",
8
+ "websockets",
9
+ "actioncable",
10
+ "turbolinks",
11
+ "reactive",
12
+ "cable",
13
+ "ujs",
14
+ "ssr",
15
+ "stimulus",
16
+ "reflex",
17
+ "stimulus_reflex",
18
+ "dom",
19
+ "morphdom"
20
+ ],
21
+ "homepage": "https://docs.stimulusreflex.com/",
22
+ "bugs": {
23
+ "url": "https://github.com/hopsoft/stimulus_reflex/issues"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com:hopsoft/stimulus_reflex.git"
28
+ },
29
+ "license": "MIT",
30
+ "author": "Nathan Hopkins <natehop@gmail.com>",
31
+ "source": "javascript/stimulus_reflex.js",
32
+ "main": "javascript/dist/stimulus_reflex.js",
33
+ "module": "javascript/dist/stimulus_reflex.module.js",
34
+ "esmodule": "javascript/dist/stimulus_reflex.modern.js",
35
+ "scripts": {
36
+ "prepare": "yarn build",
37
+ "postinstall": "node javascript/scripts/post_install.js",
38
+ "prettier-standard:check": "yarn run prettier-standard --check *.js **/*.js",
39
+ "prettier-standard:format": "yarn run prettier-standard *.js **/*.js",
40
+ "test": "yarn run mocha --require @babel/register --require esm ./javascript/test",
41
+ "build": "microbundle --target browser --format modern,es,cjs --no-strict",
42
+ "dev": "microbundle watch --target browser --format modern,es,cjs --no-strict"
43
+ },
44
+ "peerDependencies": {
45
+ "@rails/actioncable": ">= 6.0",
46
+ "cable_ready": ">= 4.3.0",
47
+ "stimulus": ">= 1.1"
48
+ },
49
+ "devDependencies": {
50
+ "@babel/core": "^7.6.2",
51
+ "@babel/preset-env": "^7.6.2",
52
+ "@babel/register": "^7.6.2",
53
+ "@rails/actioncable": "^6.0.3-3",
54
+ "assert": "^2.0.0",
55
+ "cable_ready": "^4.4.0-pre2",
56
+ "esm": "^3.2.25",
57
+ "jsdom": "^16.0.1",
58
+ "microbundle": "^0.12.3",
59
+ "mocha": "^8.0.1",
60
+ "prettier-standard": "^16.1.0",
61
+ "stimulus": "^1.1.1"
62
+ }
63
+ }