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 +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
|