view_component_reflex 2.3.12 → 2.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd84d332d4e687111e200c5ce5ee21714aff7b96e3911527b2a6b0fe1957fa4c
4
- data.tar.gz: 3402b8dd84bf2b1dd4e3d11053916aaa62c0e583f3605a0c2a89c321668673ef
3
+ metadata.gz: 707610d40c14c49d025779455b65bc00e5a62f6ac5efb45b67465ddaf877e037
4
+ data.tar.gz: d968005df1c10fc89d6a596acccaa114a1c7fda23d159f1e49f7a065591c55ce
5
5
  SHA512:
6
- metadata.gz: 515a31e4d242bdd381e537d5b4dac20322a8318895cba609ef39cba068940a3b3e931b5862cf6404364794bea1bfbf0ed6ad6ce71cef9f9032bc7ddf7fc2499a
7
- data.tar.gz: cd1040ec6bac0ee57ef3050c2cd1d0cfaa79daa21cbe141efec01ca97f5bdcba6553cb384a752294ba7149e299eb3e2a5631a4ccf2204557d7c3a2eecca41b28
6
+ metadata.gz: 6a852616da1e12e5cc0d54017b738ea97040092a1c9801a8355aca89681846dcc08b6e5e62ab6209d2c48b7328f46e4c42896a20f54cb465af896c05442dc94e
7
+ data.tar.gz: fd389d8e0706b8f7133d01953b31c06e8d08c88dfa3015bb5f443c57b6e34604bb81de16bea00e2861eb39aadd90d0c36a3a733e247af74b0fa9c7f33b9ee8b4
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,6 +193,19 @@ 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
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` method.
198
211
  The parent class must inherit ViewComponentReflex::Reflex, and will throw an error if it does not.
@@ -245,8 +258,8 @@ end
245
258
 
246
259
  ## State
247
260
 
248
- By default, view_component_reflex stores component state in memory. You can optionally set the state adapter
249
- to use the session by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Session`
261
+ By default (since version `2.3.2`), view_component_reflex stores component state in session. You can optionally set the state adapter
262
+ to use the memory by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Memory`.
250
263
 
251
264
  ## Custom State Adapters
252
265
 
@@ -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,17 @@ $ 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
+
322
362
  ## License
323
363
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
324
364
 
@@ -139,33 +139,46 @@ module ViewComponentReflex
139
139
  []
140
140
  end
141
141
 
142
+ def after_state_initialized(parameters_changed)
143
+ # called after state component has been hydrated
144
+ end
145
+
142
146
  # def receive_params(old_state, params)
143
147
  # # no op
144
148
  # end
145
149
 
146
150
  def key
151
+ adapter = ViewComponentReflex::Engine.state_adapter
152
+
147
153
  # initialize session state
148
- if !stimulus_reflex? || ViewComponentReflex::Engine.state_adapter.state(request, @key).empty?
154
+ if !stimulus_reflex? || adapter.state(request, @key).empty?
149
155
 
150
156
  new_state = create_safe_state
151
157
 
152
- ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
153
- ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
158
+ adapter.wrap_write_async do
159
+ adapter.store_state(request, @key, new_state)
160
+ adapter.store_state(request, "#{@key}_initial", new_state)
161
+ end
154
162
  elsif !@initialized_state
155
- initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
163
+ initial_state = adapter.state(request, "#{@key}_initial")
156
164
 
157
165
  # incoming_params = safe_instance_variables.each_with_object({}) { |var, obj| obj[var] = instance_variable_get(var) }
158
166
  # receive_params(ViewComponentReflex::Engine.state_adapter.state(request, @key), incoming_params)
159
167
 
160
- ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
168
+ parameters_changed = []
169
+ adapter.state(request, @key).each do |k, v|
161
170
  instance_value = instance_variable_get(k)
162
171
  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})
172
+ parameters_changed << k
173
+ adapter.wrap_write_async do
174
+ adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
175
+ adapter.set_state(request, controller, @key, {k => instance_value})
176
+ end
165
177
  else
166
178
  instance_variable_set(k, v)
167
179
  end
168
180
  end
181
+ after_state_initialized(parameters_changed)
169
182
  @initialized_state = true
170
183
  end
171
184
  @key
@@ -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
@@ -40,7 +40,7 @@ module ViewComponentReflex
40
40
  element.dataset[:key]
41
41
  end
42
42
  end
43
- document = Nokogiri::HTML(controller.render_component_to_string(component))
43
+ document = Nokogiri::HTML(component.render_in(controller.view_context))
44
44
  cable_ready[channel.stream_name].morph(
45
45
  selector: selector,
46
46
  children_only: true,
@@ -119,7 +119,7 @@ module ViewComponentReflex
119
119
  return @component if @component
120
120
  @component = component_class.allocate
121
121
  reflex = self
122
- exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!, :selector, :stimulate]
122
+ exposed_methods = [:params, :request, :connection, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!, :selector, :stimulate]
123
123
  exposed_methods.each do |meth|
124
124
  @component.define_singleton_method(meth) do |*a|
125
125
  reflex.send(meth, *a)
@@ -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.12'
2
+ VERSION = '2.6.1'
3
3
  end
metadata CHANGED
@@ -1,43 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component_reflex
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.12
4
+ version: 2.6.1
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-09 00:00:00.000000000 Z
11
+ date: 2020-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7.0'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '5.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7.0'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: stimulus_reflex
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - "~>"
37
+ - - ">="
32
38
  - !ruby/object:Gem::Version
33
39
  version: 3.3.0
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '3.4'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
- - - "~>"
47
+ - - ">="
39
48
  - !ruby/object:Gem::Version
40
49
  version: 3.3.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '3.4'
41
53
  - !ruby/object:Gem::Dependency
42
54
  name: view_component
43
55
  requirement: !ruby/object:Gem::Requirement
@@ -68,6 +80,7 @@ files:
68
80
  - lib/view_component_reflex/reflex.rb
69
81
  - lib/view_component_reflex/reflex_factory.rb
70
82
  - lib/view_component_reflex/state_adapter/memory.rb
83
+ - lib/view_component_reflex/state_adapter/redis.rb
71
84
  - lib/view_component_reflex/state_adapter/session.rb
72
85
  - lib/view_component_reflex/version.rb
73
86
  homepage: https://github.com/joshleblanc/view_component_reflex