view_component_reflex 2.3.14 → 3.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ad19988c11a33e1cc66d09f768f2d74218db3c04e16c8625c4624ce5a223c9f
4
- data.tar.gz: b994c4d403c7d24be9152f543f9b5e257f11405e8fd6d671921e28f3aaa61d2f
3
+ metadata.gz: 047aa78feb4675abeb3bb5a5ed86ea4071c6a22fef3e06fb2806b04d651898cd
4
+ data.tar.gz: 5c0cc3c3c6fca877e8b2d8a595c601ddea90b892a62367af6abed6099a6b1b81
5
5
  SHA512:
6
- metadata.gz: b4bd5cf4fd21a5dad48247bc6bdc4cd3d149ae67851298a4ed43d747f80ebeb3447589711e16648b8d00fa823e43d18a378caea4d658d35673ac84bebb6a6050
7
- data.tar.gz: 1d5c9965e17767d5c44f6f6b8034569232179008780a0a3f870bf848c81572c70ec747f6acc50579da86a9c16f80eba2d22cfb01037462626cc93d8f04fc5770
6
+ metadata.gz: 0f3f2e6f15c43e47aa3cdcd39632f4e2d7d76dd7065dcfd38b718db0e34ac100fed7268371660994c402a9c0aacd07635a935f088cec49cebbbcbccae6533aea
7
+ data.tar.gz: 56fa191797a8d3f757457a7a8c94f4394304ca0dd201639c0d47225a7f7be7ceb9b349641805783538325e884431e483a5b51714c4f75f26826912c953474d7c
data/README.md CHANGED
@@ -6,7 +6,7 @@ It builds upon [stimulus_reflex](https://github.com/hopsoft/stimulus_reflex) and
6
6
 
7
7
  ## Usage
8
8
 
9
- You can add reflexes to your component by adding inheriting from `ViewComponentReflex::Component`.
9
+ You can add reflexes to your component by inheriting from `ViewComponentReflex::Component`.
10
10
 
11
11
  This will act as if you created a reflex with the method `my_cool_stuff`. To call this reflex, add `data-reflex="click->MyComponentReflex#my_cool_reflex"`, just like you're
12
12
  using stimulus reflex.
@@ -124,7 +124,7 @@ def initialize
124
124
  end
125
125
 
126
126
  def collection_key
127
- @my_model.id
127
+ @my_model.id
128
128
  end
129
129
  ```
130
130
 
@@ -161,7 +161,7 @@ By default, VCR will re-render your component after it executes your method. `pr
161
161
  ```ruby
162
162
  def my_method
163
163
  prevent_refresh!
164
- @foo = Lbar
164
+ @foo = :bar
165
165
  end # the rendered page will not reflect this change
166
166
  ```
167
167
 
@@ -193,8 +193,21 @@ You *must* wrap your component in this for everything to work properly.
193
193
  <% end %>
194
194
  ```
195
195
 
196
+ ### after_state_initialized(parameters_changed)
197
+
198
+ This is called after the state has been inserted in the component. You can use this to run conditional functions after
199
+ some parameter has superseeded whatever's in state
200
+
201
+ ```
202
+ def after_state_initialized(parameters_changed)
203
+ if parameters_changed.include?(:@filter)
204
+ calculate_visible_rows
205
+ end
206
+ end
207
+ ```
208
+
196
209
  ## Custom reflex base class
197
- Reflexes typically inherit from a base ApplicationReflex. You can define the base class for a view_component_reflex by using the `reflex_base_class` method.
210
+ Reflexes typically inherit from a base ApplicationReflex. You can define the base class for a view_component_reflex by using the `reflex_base_class` accessor.
198
211
  The parent class must inherit ViewComponentReflex::Reflex, and will throw an error if it does not.
199
212
 
200
213
  ```ruby
@@ -204,7 +217,7 @@ end
204
217
 
205
218
 
206
219
  class MyComponent < ViewComponentReflex::Component
207
- reflex_base_class ApplicationReflex
220
+ MyComponent.reflex_base_class = ApplicationReflex
208
221
  end
209
222
  ```
210
223
 
@@ -259,6 +272,22 @@ ViewComponentReflex::Engine.configure do |config|
259
272
  end
260
273
  ```
261
274
 
275
+
276
+ ## Existing Fast Redis based State Adapter
277
+
278
+ This adapter uses hmset and hgetall to reduce the number of operations.
279
+ This is the recommended adapter if you are using AnyCable.
280
+
281
+ ```ruby
282
+ ViewComponentReflex::Engine.configure do |config|
283
+ config.state_adapter = ViewComponentReflex::StateAdapter::Redis.new(
284
+ redis_opts: {
285
+ url: "redis://localhost:6379/1", driver: :hiredis
286
+ },
287
+ ttl: 3600)
288
+ end
289
+ ```
290
+
262
291
  `YourAdapter` should implement
263
292
 
264
293
  ```ruby
@@ -319,6 +348,31 @@ $ gem install view_component_reflex
319
348
  ## Uninitialized constants \<component\>Reflex
320
349
  A component needs to be wrapped in `<%= component_controller do %>` in order to properly initialize, otherwise the Reflex class won't get created.
321
350
 
351
+ ## Session is an empty hash
352
+ StimulusReflex 3.3 introduced _selector morphs_, allowing you to render arbitrary strings via `ApplicationController.render`, for example:
353
+
354
+ ```rb
355
+ def test_selector
356
+ morph '#some-container', ApplicationController.render(MyComponent.new(some: :param))
357
+ end
358
+ ```
359
+
360
+ StimulusReflex 3.4 introduced a fix that merges the current `request.env` and provides the CSRF token to fetch the session.
361
+
362
+ ## Anycable
363
+
364
+ @sebyx07 provided a solution to use anycable (https://github.com/joshleblanc/view_component_reflex/issues/23#issue-721786338)
365
+
366
+ Leaving this, might help others:
367
+
368
+ I tried this with any cable and I had to add this to development.rb
369
+ Otherwise @instance_variables were nil after a reflex
370
+
371
+ ```ruby
372
+ config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/1", driver: :hiredis }
373
+ config.session_store :cache_store
374
+ ```
375
+
322
376
  ## License
323
377
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
324
378
 
@@ -1,5 +1,7 @@
1
1
  module ViewComponentReflex
2
2
  class Component < ViewComponent::Base
3
+ class_attribute :reflex_base_class, default: ViewComponentReflex::Reflex
4
+
3
5
  class << self
4
6
  def init_stimulus_reflex
5
7
  factory = ViewComponentReflex::ReflexFactory.new(self)
@@ -7,18 +9,6 @@ module ViewComponentReflex
7
9
  wire_up_callbacks if factory.new?
8
10
  end
9
11
 
10
- def reflex_base_class(new_base_class = nil)
11
- if new_base_class.nil?
12
- @reflex_base_class ||= ViewComponentReflex::Reflex
13
- else
14
- if new_base_class <= ViewComponentReflex::Reflex
15
- @reflex_base_class = new_base_class
16
- else
17
- raise StandardError.new("The reflex base class must inherit from ViewComponentReflex::Reflex")
18
- end
19
- end
20
- end
21
-
22
12
  def queue_callback(key, args, blk)
23
13
  callbacks(key).push({
24
14
  args: args,
@@ -139,40 +129,54 @@ module ViewComponentReflex
139
129
  []
140
130
  end
141
131
 
132
+ def after_state_initialized(parameters_changed)
133
+ # called after state component has been hydrated
134
+ end
135
+
142
136
  # def receive_params(old_state, params)
143
137
  # # no op
144
138
  # end
145
139
 
146
140
  def key
141
+ adapter = ViewComponentReflex::Engine.state_adapter
142
+
147
143
  # initialize session state
148
- if !stimulus_reflex? || ViewComponentReflex::Engine.state_adapter.state(request, @key).empty?
144
+ if (!stimulus_reflex? || adapter.state(request, @key).empty?) && !@initialized_state
149
145
 
150
146
  new_state = create_safe_state
151
147
 
152
- ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
153
- ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
148
+ adapter.wrap_write_async do
149
+ adapter.store_state(request, @key, new_state)
150
+ adapter.store_state(request, "#{@key}_initial", new_state)
151
+ end
154
152
  elsif !@initialized_state
155
- initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
153
+ initial_state = adapter.state(request, "#{@key}_initial")
156
154
 
157
155
  # incoming_params = safe_instance_variables.each_with_object({}) { |var, obj| obj[var] = instance_variable_get(var) }
158
156
  # receive_params(ViewComponentReflex::Engine.state_adapter.state(request, @key), incoming_params)
159
157
 
160
- ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
158
+ parameters_changed = []
159
+ adapter.state(request, @key).each do |k, v|
161
160
  instance_value = instance_variable_get(k)
162
161
  if permit_parameter?(initial_state[k], instance_value)
163
- ViewComponentReflex::Engine.state_adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
164
- ViewComponentReflex::Engine.state_adapter.set_state(request, controller, @key, {k => instance_value})
162
+ parameters_changed << k
163
+ adapter.wrap_write_async do
164
+ adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
165
+ adapter.set_state(request, controller, @key, {k => instance_value})
166
+ end
165
167
  else
166
168
  instance_variable_set(k, v)
167
169
  end
168
170
  end
169
- @initialized_state = true
171
+ after_state_initialized(parameters_changed)
170
172
  end
173
+
174
+ @initialized_state = true
171
175
  @key
172
176
  end
173
177
 
174
178
  def safe_instance_variables
175
- instance_variables - unsafe_instance_variables
179
+ instance_variables - unsafe_instance_variables - omitted_from_state
176
180
  end
177
181
 
178
182
  private
@@ -190,7 +194,7 @@ module ViewComponentReflex
190
194
 
191
195
  # this will almost certainly break
192
196
  safe_instance_variables.each do |k|
193
- new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
197
+ new_state[k] = instance_variable_get(k)
194
198
  end
195
199
  new_state
196
200
  end
@@ -2,6 +2,7 @@ require "stimulus_reflex"
2
2
  require 'view_component_reflex/reflex_factory'
3
3
  require "view_component_reflex/state_adapter/session"
4
4
  require "view_component_reflex/state_adapter/memory"
5
+ require "view_component_reflex/state_adapter/redis"
5
6
  require "view_component_reflex/reflex"
6
7
  require "view_component_reflex/engine"
7
8
 
@@ -1,10 +1,8 @@
1
1
  module ViewComponentReflex
2
2
  class Engine < ::Rails::Engine
3
- class << self
4
- mattr_accessor :state_adapter
5
3
 
6
- self.state_adapter = StateAdapter::Session
7
- end
4
+ mattr_accessor :state_adapter
5
+ Engine.state_adapter = StateAdapter::Session
8
6
 
9
7
  config.to_prepare do
10
8
  StimulusReflex::Channel.class_eval do
@@ -1,6 +1,5 @@
1
1
  module ViewComponentReflex
2
2
  class Reflex < StimulusReflex::Reflex
3
- include CableReady::Broadcaster
4
3
 
5
4
  class << self
6
5
  attr_accessor :component_class
@@ -15,16 +14,26 @@ module ViewComponentReflex
15
14
  if primary_selector
16
15
  prevent_refresh!
17
16
 
18
- controller.process(url_params[:action])
17
+ controller.process(params[:action])
19
18
  document = Nokogiri::HTML(controller.response.body)
20
19
  [primary_selector, *rest].each do |s|
21
20
  html = document.css(s)
22
21
  if html.present?
23
- cable_ready[channel.stream_name].morph(
22
+ CableReady::Channels.instance[stream_name].morph(
24
23
  selector: s,
25
24
  html: html.inner_html,
26
25
  children_only: true,
27
- permanent_attribute_name: "data-reflex-permanent"
26
+ permanent_attribute_name: "data-reflex-permanent",
27
+ stimulus_reflex: {
28
+ reflex_id: reflex_id,
29
+ xpath: xpath,
30
+ c_xpath: c_xpath,
31
+ target: target,
32
+ reflex_controller: reflex_controller,
33
+ url: url,
34
+ morph: :page,
35
+ attrs: {key: element.dataset[:key]}
36
+ }
28
37
  )
29
38
  end
30
39
  end
@@ -40,15 +49,29 @@ module ViewComponentReflex
40
49
  element.dataset[:key]
41
50
  end
42
51
  end
43
- document = Nokogiri::HTML(controller.render_component_to_string(component))
44
- cable_ready[channel.stream_name].morph(
52
+ document = Nokogiri::HTML(component.render_in(controller.view_context))
53
+ CableReady::Channels.instance[stream_name].morph(
45
54
  selector: selector,
46
55
  children_only: true,
47
56
  html: document.css(selector).inner_html,
48
- permanent_attribute_name: "data-reflex-permanent"
57
+ permanent_attribute_name: "data-reflex-permanent",
58
+ stimulus_reflex: {
59
+ reflex_id: reflex_id,
60
+ xpath: xpath,
61
+ target: target,
62
+ c_xpath: c_xpath,
63
+ reflex_controller: reflex_controller,
64
+ url: url,
65
+ morph: :page,
66
+ attrs: {key: element.dataset[:key]}
67
+ }
49
68
  )
50
69
  end
51
70
 
71
+ def target
72
+ "#{component_class}##{method_name}"
73
+ end
74
+
52
75
  def refresh_all!
53
76
  refresh!("body")
54
77
  end
@@ -35,11 +35,17 @@ module ViewComponentReflex
35
35
  end
36
36
 
37
37
  def reflex_from_nested_component
38
- if @component.module_parent.const_defined?(reflex_name)
39
- @component.module_parent.const_get(reflex_name)
38
+ parent = if @component.respond_to? :module_parent
39
+ @component.module_parent
40
+ else
41
+ @component.parent
42
+ end
43
+
44
+ if parent.const_defined?(reflex_name)
45
+ parent.const_get(reflex_name)
40
46
  else
41
47
  @new = true
42
- @component.module_parent.const_set(reflex_name, reflex_instance)
48
+ parent.const_set(reflex_name, reflex_instance)
43
49
  end
44
50
  end
45
51
 
@@ -20,6 +20,10 @@ module ViewComponentReflex
20
20
  VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s][key][k] = v
21
21
  end
22
22
  end
23
+
24
+ def self.wrap_write_async
25
+ yield
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -0,0 +1,74 @@
1
+ # A redis based adapter for component_reflex
2
+ #
3
+ # ViewComponentReflex::Engine.configure do |config|
4
+ # config.state_adapter = ViewComponentReflex::StateAdapter::Redis.new(
5
+ # redis_opts: {
6
+ # url: "redis://localhost:6379/1", driver: :hiredis
7
+ # },
8
+ # ttl: 3600)
9
+ # end
10
+
11
+ module ViewComponentReflex
12
+ module StateAdapter
13
+ class Redis
14
+ attr_reader :client, :ttl
15
+
16
+ def initialize(redis_opts:, ttl: 3600)
17
+ @client = ::Redis.new(redis_opts)
18
+ @ttl = ttl
19
+ end
20
+
21
+ def state(request, key)
22
+ cache_key = get_key(request, key)
23
+ value = client.hgetall(cache_key)
24
+
25
+ return {} if value.nil?
26
+
27
+ value.map do |k, v|
28
+ [k, Marshal.load(v)]
29
+ end.to_h
30
+ end
31
+
32
+ def set_state(request, _, key, new_state)
33
+ cache_key = get_key(request, key)
34
+ save_to_redis(cache_key, new_state)
35
+ end
36
+
37
+ def store_state(request, key, new_state = {})
38
+ cache_key = get_key(request, key)
39
+ optimized_store_to_redis(cache_key, new_state, request)
40
+ end
41
+
42
+ def wrap_write_async
43
+ client.pipelined do
44
+ yield
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Reduce number of calls coming from #store_state to save Redis
51
+ # when it's first rendered
52
+ def optimized_store_to_redis(cache_key, new_state, request)
53
+ request.env["CACHE_REDIS_REFLEX"] ||= []
54
+ return if request.env["CACHE_REDIS_REFLEX"].include?(cache_key)
55
+ request.env["CACHE_REDIS_REFLEX"].push(cache_key)
56
+
57
+ save_to_redis(cache_key, new_state)
58
+ end
59
+
60
+ def save_to_redis(cache_key, new_state)
61
+ new_state_json = new_state.map do |k, v|
62
+ [k, Marshal.dump(v)]
63
+ end
64
+
65
+ client.hmset(cache_key, new_state_json.flatten)
66
+ client.expire(cache_key, ttl)
67
+ end
68
+
69
+ def get_key(request, key)
70
+ "#{request.session.id.to_s}_#{key}_session_reflex_redis"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -19,6 +19,10 @@ module ViewComponentReflex
19
19
  request.session[key][k] = v
20
20
  end
21
21
  end
22
+
23
+ def self.wrap_write_async
24
+ yield
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -1,3 +1,3 @@
1
1
  module ViewComponentReflex
2
- VERSION = '2.3.14'
2
+ VERSION = '3.0.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component_reflex
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.14
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua LeBlanc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-13 00:00:00.000000000 Z
11
+ date: 2020-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -34,16 +34,16 @@ dependencies:
34
34
  name: stimulus_reflex
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 3.3.0
39
+ version: 3.4.0.pre8
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: 3.3.0
46
+ version: 3.4.0.pre8
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: view_component
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -74,6 +74,7 @@ files:
74
74
  - lib/view_component_reflex/reflex.rb
75
75
  - lib/view_component_reflex/reflex_factory.rb
76
76
  - lib/view_component_reflex/state_adapter/memory.rb
77
+ - lib/view_component_reflex/state_adapter/redis.rb
77
78
  - lib/view_component_reflex/state_adapter/session.rb
78
79
  - lib/view_component_reflex/version.rb
79
80
  homepage: https://github.com/joshleblanc/view_component_reflex