view_component_reflex 3.1.14.pre9 → 3.2.0.pre

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: 92c0d1350e67ae8b1ae796fc57f4ce22f7cddebb0bce2559ba25df5e81dc191a
4
- data.tar.gz: 39c96a9635ce1d217a6a1bf772743c7e9103ebf30b748cf22756614ea13030d3
3
+ metadata.gz: '076282b09956e12df3434c1be972995da10bdc2a094598f71da922bef7f84f6e'
4
+ data.tar.gz: c250e5e4de40beafa81110a32e9d4ef03b0321d3a1674f2ba4811b56bdffe68a
5
5
  SHA512:
6
- metadata.gz: ceb51a750f14878ecab26760e2c3951259ff9f3c7da3b503f504b7197d7d2163d5faf95b063b55a77bdfc662467422b22a987f3b7adb4f5ae364cfdeb4aee16c
7
- data.tar.gz: c8e96efd021ad1c46deee61537e60a364dc6b5636a54985389deb5de3094774f7b3d941de07cf2a17616aec16b96d161d9ee2f2c619c8dd1fec95f1e2fa180f4
6
+ metadata.gz: 9dd881a3cb9a890f771a5ed21df13dc15eb73db2ed6beb290f43d9fa3967d58a0631833a653ebf0e49eb2e44e3a5651e0855174df42e7bdf093e876959af75fd
7
+ data.tar.gz: 2f801a13e7e1b081b205b7def36cf0f7ef42b44fe4f81f0c55781202252c2eb8548ab4d84895577eab727262e70c0e4377dc54584b9cff656d01c9dfb41dabaa
data/README.md CHANGED
@@ -248,6 +248,27 @@ class MyComponent < ViewComponentReflex::Component
248
248
  end
249
249
  ```
250
250
 
251
+ ## Per-component state adapters
252
+ You can override the default state adapter for a component by using the `state_adapter` helper.
253
+
254
+ ```ruby
255
+ class MyComponent < ViewComponentReflex::Component
256
+ state_adapter :dom # or :session, or :memory
257
+ end
258
+ ```
259
+
260
+ This will also accept a fully qualified constant
261
+
262
+ ```ruby
263
+ class MyComponent < ViewComponentReflex::Component
264
+ state_adapter ViewComponentReflex::StateAdapter::Redis.new(
265
+ redis_opts: {
266
+ url: "redis://localhost:6379/1", driver: :hiredis
267
+ },
268
+ ttl: 3600)
269
+ end
270
+ ```
271
+
251
272
  ## Common patterns
252
273
  A lot of the time, you only need to update specific components when changing instance variables. For example, changing `@loading` might only need
253
274
  to display a spinner somewhere on the page. You can define setters to implicitly render the appropriate pieces of dom whenever that variable is set
@@ -288,6 +309,9 @@ end
288
309
  By default (since version `2.3.2`), view_component_reflex stores component state in session. You can optionally set the state adapter
289
310
  to use the memory by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Memory`.
290
311
 
312
+ Optionally, you can also store state right in the dom with `ViewComponentReflex::StateAdapter::Dom`. Not that the DOM
313
+ adapter requires the `data-reflex-dataset="*"` property to be set on anything firing the reflex.
314
+
291
315
  ## Custom State Adapters
292
316
 
293
317
  ViewComponentReflex uses session for its state by default. To change this, add
@@ -299,7 +323,6 @@ ViewComponentReflex::Engine.configure do |config|
299
323
  end
300
324
  ```
301
325
 
302
-
303
326
  ## Existing Fast Redis based State Adapter
304
327
 
305
328
  This adapter uses hmset and hgetall to reduce the number of operations.
data/Rakefile CHANGED
@@ -14,8 +14,8 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
14
  rdoc.rdoc_files.include('lib/**/*.rb')
15
15
  end
16
16
 
17
- APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
- load 'rails/tasks/engine.rake'
17
+ # APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ # load 'rails/tasks/engine.rake'
19
19
 
20
20
  load 'rails/tasks/statistics.rake'
21
21
 
@@ -4,6 +4,8 @@ module ViewComponentReflex
4
4
  attr_reader :key
5
5
 
6
6
  class << self
7
+ attr_reader :_state_adapter
8
+
7
9
  def init_stimulus_reflex
8
10
  factory = ViewComponentReflex::ReflexFactory.new(self)
9
11
  @stimulus_reflex ||= factory.reflex
@@ -57,6 +59,15 @@ module ViewComponentReflex
57
59
  register_callbacks(:after)
58
60
  register_callbacks(:around)
59
61
  end
62
+
63
+ def state_adapter(what)
64
+ if what.is_a?(Symbol) || what.is_a?(String)
65
+ class_name = what.to_s.camelize
66
+ @_state_adapter = StateAdapter.const_get class_name
67
+ else
68
+ @_state_adapter = what
69
+ end
70
+ end
60
71
  end
61
72
 
62
73
  def self.stimulus_controller
@@ -67,6 +78,10 @@ module ViewComponentReflex
67
78
  helpers.controller.instance_variable_get(:@stimulus_reflex)
68
79
  end
69
80
 
81
+ def before_render
82
+ adapter.extend_component(self)
83
+ end
84
+
70
85
  def component_controller(opts_or_tag = :div, opts = {}, &blk)
71
86
  initialize_component
72
87
 
@@ -91,10 +106,10 @@ module ViewComponentReflex
91
106
  end
92
107
 
93
108
  # We can't truly initialize the component without the view_context,
94
- # which isn't available in the `initialize` method. We require the
109
+ # which isn't available in the `initialize` method. We require the
95
110
  # developer to wrap components in `component_controller`, so this is where
96
111
  # we truly initialize the component.
97
- # This method is overridden in reflex.rb when the component is re-rendered. The
112
+ # This method is overridden in reflex.rb when the component is re-rendered. The
98
113
  # override simply sets @key to element.dataset[:key]
99
114
  # We don't want it to initialize the state again, and since we're rendering the component
100
115
  # outside of the view, we need to skip the initialize_key method as well
@@ -109,30 +124,31 @@ module ViewComponentReflex
109
124
  # so we can't rely on the component created by the reflex
110
125
  def initialize_state
111
126
  return if state_initialized?
112
- adapter = ViewComponentReflex::Engine.state_adapter
113
-
127
+
128
+ adapter.extend_component(self)
129
+
114
130
  # newly mounted
115
- if !stimulus_reflex? || adapter.state(request, @key).empty?
131
+ if !stimulus_reflex? || state(@key).empty?
116
132
 
117
133
  new_state = create_safe_state
118
134
 
119
- adapter.wrap_write_async do
120
- adapter.store_state(request, @key, new_state)
121
- adapter.store_state(request, "#{@key}_initial", new_state)
135
+ wrap_write_async do
136
+ store_state(@key, new_state)
137
+ store_state("#{@key}_initial", new_state)
122
138
  end
123
139
 
124
140
  # updating a mounted component
125
141
  else
126
- initial_state = adapter.state(request, "#{@key}_initial")
142
+ initial_state = state("#{@key}_initial")
127
143
 
128
144
  parameters_changed = []
129
- adapter.state(request, @key).each do |k, v|
145
+ state(@key).each do |k, v|
130
146
  instance_value = instance_variable_get(k)
131
147
  if permit_parameter?(initial_state[k], instance_value)
132
148
  parameters_changed << k
133
- adapter.wrap_write_async do
134
- adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
135
- adapter.set_state(request, controller, @key, {k => instance_value})
149
+ wrap_write_async do
150
+ set_state("#{@key}_initial", { k => instance_value })
151
+ set_state(@key, { k => instance_value })
136
152
  end
137
153
  else
138
154
  instance_variable_set(k, v)
@@ -143,6 +159,26 @@ module ViewComponentReflex
143
159
  @state_initialized = true
144
160
  end
145
161
 
162
+ def adapter
163
+ self.class._state_adapter || ViewComponentReflex::Engine.state_adapter
164
+ end
165
+
166
+ def wrap_write_async(&blk)
167
+ adapter.wrap_write_async(&blk)
168
+ end
169
+
170
+ def set_state(key, new_state)
171
+ adapter.set_state(request, controller, key, new_state)
172
+ end
173
+
174
+ def state(key)
175
+ adapter.state(request, key)
176
+ end
177
+
178
+ def store_state(key, new_state = {})
179
+ adapter.store_state(request, key, new_state)
180
+ end
181
+
146
182
  def state_initialized?
147
183
  @state_initialized
148
184
  end
@@ -152,10 +188,10 @@ module ViewComponentReflex
152
188
  # and line number, which should be unique. We hash it to make it a nice number
153
189
  erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
154
190
  key = if erb_file
155
- Digest::SHA2.hexdigest(erb_file.split(":in")[0])
156
- else
157
- ""
158
- end
191
+ Digest::SHA2.hexdigest(erb_file.split(":in")[0])
192
+ else
193
+ ""
194
+ end
159
195
  key += collection_key.to_s if collection_key
160
196
  @key = key
161
197
  end
@@ -0,0 +1,40 @@
1
+ module ViewComponentReflex
2
+ module Dom
3
+ module Component
4
+
5
+ def component_controller(opts_or_tag = :div, opts = {}, &blk)
6
+ initialize_component
7
+
8
+ tag = :div
9
+ options = if opts_or_tag.is_a? Hash
10
+ opts_or_tag
11
+ else
12
+ tag = opts_or_tag
13
+ opts
14
+ end
15
+
16
+ data = {
17
+ "#{key}_state" => Verifier.generate(state(key)),
18
+ "#{key}_initial" => Verifier.generate(state("#{key}_initial")),
19
+ }
20
+
21
+ options[:data] = {
22
+ controller: self.class.stimulus_controller,
23
+ key: key,
24
+ **(options[:data] || {})
25
+ }
26
+
27
+ content_tag tag, options do
28
+ concat(content_tag(:span, nil, { data: data, style: "display: none;" }))
29
+ concat(capture(&blk))
30
+ end
31
+ end
32
+
33
+ def reflex_data_attributes(reflex)
34
+ super(reflex).tap do |attr|
35
+ attr["reflex-dataset"] = "*"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ ##
2
+ # State adapters assume that the data lives outside of the rendering pipeline
3
+ # By moving the state to the DOM, we need to "hydrate" the state when the reflex comes in with the data
4
+ # We're overriding `inject_key_into_component` for this purpose, since it's run once during a reflex,
5
+ # and _just_ after the component is initialized, but before it actually does anything.
6
+ # This ensures that any actions that the component is taking is going to be operating on the correct
7
+ # state
8
+
9
+ module ViewComponentReflex
10
+ module Dom
11
+ module Reflex
12
+ def state
13
+ if element.dataset[:"#{key}_state"]
14
+ Verifier.verify(element.dataset[:"#{key}_state"])
15
+ else
16
+ {}
17
+ end
18
+ end
19
+
20
+ def inject_key_into_component
21
+ super
22
+
23
+ p initial_state[:@count]
24
+ state_adapter.store_state(request, key, state)
25
+ state_adapter.store_state(request, "#{key}_initial", initial_state)
26
+ end
27
+
28
+ def initial_state
29
+ if element.dataset[:"#{key}_initial"]
30
+ Verifier.verify(element.dataset[:"#{key}_initial"])
31
+ else
32
+ {}
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ module ViewComponentReflex
2
+ module Dom
3
+ class Verifier
4
+ class << self
5
+ attr_reader :verifier
6
+
7
+ delegate :generate, :verified, :verify, :valid_message?, to: :verifier
8
+
9
+ def verifier
10
+ Rails.application.message_verifier(:view_component_reflex)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,6 +1,5 @@
1
1
  module ViewComponentReflex
2
2
  class Reflex < StimulusReflex::Reflex
3
-
4
3
  class << self
5
4
  attr_accessor :component_class
6
5
  end
@@ -48,21 +47,23 @@ module ViewComponentReflex
48
47
  @stream = channel
49
48
  end
50
49
 
51
- def component_document
50
+ def inject_key_into_component
52
51
  component.tap do |k|
53
52
  k.define_singleton_method(:initialize_component) do
54
53
  @key = element.dataset[:key]
55
54
  end
56
55
  end
56
+ end
57
57
 
58
- document = Nokogiri::HTML(component.render_in(controller.view_context))
58
+ def component_document
59
+ Nokogiri::HTML(component.render_in(controller.view_context))
59
60
  end
60
61
 
61
62
  def refresh_component!
62
63
  CableReady::Channels.instance[stream].morph(
63
64
  selector: selector,
64
65
  children_only: true,
65
- html: component_document.css(selector).inner_html,
66
+ html: component_document.css(selector).to_html,
66
67
  permanent_attribute_name: "data-reflex-permanent",
67
68
  reflex_id: reflex_id
68
69
  )
@@ -109,6 +110,7 @@ module ViewComponentReflex
109
110
  # uses method to gather the method parameters, but since we're abusing
110
111
  # method_missing here, that'll always fail
111
112
  def method(name)
113
+ component.adapter.extend_reflex(self)
112
114
  component.method(name.to_sym)
113
115
  end
114
116
 
@@ -124,11 +126,11 @@ module ViewComponentReflex
124
126
  end
125
127
 
126
128
  component.send(name, *args, &blk)
127
-
129
+
128
130
  if @prevent_refresh
129
131
  morph :nothing
130
132
  else
131
- default_morph
133
+ default_morph
132
134
  end
133
135
  end
134
136
 
@@ -196,6 +198,8 @@ module ViewComponentReflex
196
198
  reflex
197
199
  end
198
200
 
201
+ inject_key_into_component
202
+
199
203
  @component
200
204
  end
201
205
 
@@ -208,7 +212,7 @@ module ViewComponentReflex
208
212
  end
209
213
 
210
214
  def state_adapter
211
- ViewComponentReflex::Engine.state_adapter
215
+ component.adapter
212
216
  end
213
217
 
214
218
  def state
@@ -2,6 +2,30 @@ module ViewComponentReflex
2
2
  module StateAdapter
3
3
  class Base
4
4
 
5
+ def self.state(request, key)
6
+ # stub
7
+ end
8
+
9
+ def self.set_state(request, controller, key, new_state)
10
+ # stub
11
+ end
12
+
13
+ def self.store_state(request, key, new_state = {})
14
+ # stub
15
+ end
16
+
17
+ def self.wrap_write_async
18
+ yield
19
+ end
20
+
21
+ def self.extend_component(component)
22
+ # stub
23
+ end
24
+
25
+ def self.extend_reflex(component)
26
+ # stub
27
+ end
28
+
5
29
  private
6
30
 
7
31
  def self.extract_id(request)
@@ -0,0 +1,56 @@
1
+ ##
2
+ # This adapter exists solely to allow switching to dom-based state storage, even
3
+ # though dom-based state storage doesn't abide by the same standards as the other adapters
4
+ #
5
+ # The dom-based state storage is handled by the Dom::Component and Dom::Reflex modules that are prepended
6
+ # to Component and Reflex in Engine, if the dom adapter is selected
7
+ #
8
+ # These modules override various methods to inject state into the dom, as well as encoding/decoding the state from the dom
9
+
10
+ class CurrentState < ActiveSupport::CurrentAttributes
11
+ attribute :state
12
+ end
13
+
14
+ module ViewComponentReflex
15
+ module StateAdapter
16
+ class Dom < Base
17
+ def self.state(request, key)
18
+ id = extract_id(request)
19
+
20
+ CurrentState.state ||= {}
21
+ CurrentState.state[id] ||= {}
22
+ CurrentState.state[id][key] ||= {}
23
+ end
24
+
25
+ def self.set_state(request, _, key, new_state)
26
+ new_state.each do |k, v|
27
+ state(request, key)[k] = v
28
+ end
29
+ end
30
+
31
+ def self.store_state(request, key, new_state = {})
32
+ id = extract_id(request)
33
+
34
+ CurrentState.state ||= {}
35
+ CurrentState.state[id] ||= {}
36
+ CurrentState.state[id][key] = {}
37
+
38
+ new_state.each do |k, v|
39
+ CurrentState.state[id][key][k] = v
40
+ end
41
+ end
42
+
43
+ def self.extend_component(component)
44
+ component.extend ViewComponentReflex::Dom::Component
45
+ end
46
+
47
+ def self.extend_reflex(reflex)
48
+ reflex.extend ViewComponentReflex::Dom::Reflex
49
+ end
50
+
51
+ def self.wrap_write_async
52
+ yield
53
+ end
54
+ end
55
+ end
56
+ end
@@ -11,7 +11,8 @@
11
11
  module ViewComponentReflex
12
12
  module StateAdapter
13
13
  class Redis < Base
14
- attr_reader :client, :ttl
14
+ attr_reader :ttl
15
+ attr_accessor :client
15
16
 
16
17
  def initialize(redis_opts:, ttl: 3600)
17
18
  @client = ::Redis.new(redis_opts)
@@ -40,11 +41,22 @@ module ViewComponentReflex
40
41
  end
41
42
 
42
43
  def wrap_write_async
43
- client.pipelined do
44
+ client.pipelined do |pipeline|
45
+ original_client = client
46
+ @client = pipeline
44
47
  yield
48
+ @client = original_client
45
49
  end
46
50
  end
47
51
 
52
+ def extend_component(_)
53
+ #stub
54
+ end
55
+
56
+ def extend_reflex(_)
57
+ #stub
58
+ end
59
+
48
60
  private
49
61
 
50
62
  # Reduce number of calls coming from #store_state to save Redis
@@ -1,3 +1,3 @@
1
1
  module ViewComponentReflex
2
- VERSION = '3.1.14.pre9'
2
+ VERSION = '3.2.0.pre'
3
3
  end
@@ -3,9 +3,14 @@ require 'view_component_reflex/reflex_factory'
3
3
  require "view_component_reflex/state_adapter/base"
4
4
  require "view_component_reflex/state_adapter/session"
5
5
  require "view_component_reflex/state_adapter/memory"
6
+ require "view_component_reflex/state_adapter/dom"
6
7
  require "view_component_reflex/state_adapter/redis"
7
- require "view_component_reflex/reflex"
8
+ require "view_component_reflex/dom/verifier"
9
+ require "view_component_reflex/dom/reflex"
10
+ require "view_component_reflex/dom/component"
8
11
  require "view_component_reflex/engine"
12
+ require "view_component_reflex/reflex"
13
+ require "view_component_reflex/component"
9
14
 
10
15
  module ViewComponentReflex
11
16
  # Your code goes here...
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: 3.1.14.pre9
4
+ version: 3.2.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua LeBlanc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-10 00:00:00.000000000 Z
11
+ date: 2022-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -82,12 +82,16 @@ files:
82
82
  - MIT-LICENSE
83
83
  - README.md
84
84
  - Rakefile
85
- - app/components/view_component_reflex/component.rb
86
85
  - lib/view_component_reflex.rb
86
+ - lib/view_component_reflex/component.rb
87
+ - lib/view_component_reflex/dom/component.rb
88
+ - lib/view_component_reflex/dom/reflex.rb
89
+ - lib/view_component_reflex/dom/verifier.rb
87
90
  - lib/view_component_reflex/engine.rb
88
91
  - lib/view_component_reflex/reflex.rb
89
92
  - lib/view_component_reflex/reflex_factory.rb
90
93
  - lib/view_component_reflex/state_adapter/base.rb
94
+ - lib/view_component_reflex/state_adapter/dom.rb
91
95
  - lib/view_component_reflex/state_adapter/memory.rb
92
96
  - lib/view_component_reflex/state_adapter/redis.rb
93
97
  - lib/view_component_reflex/state_adapter/session.rb
@@ -112,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
116
  - !ruby/object:Gem::Version
113
117
  version: 1.3.1
114
118
  requirements: []
115
- rubygems_version: 3.2.9
119
+ rubygems_version: 3.3.3
116
120
  signing_key:
117
121
  specification_version: 4
118
122
  summary: Allow stimulus reflexes in a view component