view_component_reflex 2.3.14 → 3.0.0

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