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 +4 -4
- data/README.md +59 -5
- data/app/components/view_component_reflex/component.rb +26 -22
- data/lib/view_component_reflex.rb +1 -0
- data/lib/view_component_reflex/engine.rb +2 -4
- data/lib/view_component_reflex/reflex.rb +30 -7
- data/lib/view_component_reflex/reflex_factory.rb +9 -3
- data/lib/view_component_reflex/state_adapter/memory.rb +4 -0
- data/lib/view_component_reflex/state_adapter/redis.rb +74 -0
- data/lib/view_component_reflex/state_adapter/session.rb +4 -0
- data/lib/view_component_reflex/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 047aa78feb4675abeb3bb5a5ed86ea4071c6a22fef3e06fb2806b04d651898cd
|
4
|
+
data.tar.gz: 5c0cc3c3c6fca877e8b2d8a595c601ddea90b892a62367af6abed6099a6b1b81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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 =
|
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`
|
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? ||
|
144
|
+
if (!stimulus_reflex? || adapter.state(request, @key).empty?) && !@initialized_state
|
149
145
|
|
150
146
|
new_state = create_safe_state
|
151
147
|
|
152
|
-
|
153
|
-
|
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 =
|
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
|
-
|
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
|
-
|
164
|
-
|
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
|
-
|
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)
|
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
|
-
|
7
|
-
|
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(
|
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
|
-
|
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.
|
44
|
-
|
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
|
39
|
-
@component.module_parent
|
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
|
-
|
48
|
+
parent.const_set(reflex_name, reflex_instance)
|
43
49
|
end
|
44
50
|
end
|
45
51
|
|
@@ -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
|
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:
|
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-
|
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.
|
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.
|
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
|