rails_redhot 0.1.1 → 0.2.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/CHANGELOG.md +10 -1
- data/README.md +94 -17
- data/lib/rails_redhot/acts_as_redux.rb +29 -7
- data/lib/rails_redhot/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f5c72d2cc5029c74afb124b5bbc728d007f4d635ad9569781cdd5b2fa3f6e25
|
4
|
+
data.tar.gz: eaca540b27f70101cefaee8d29e78ab843c12d873ee89bc382b28a0af963ee28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3029222021c8639cba0a7d60e9684e4571bbcd391c1a8884fdf2386b0a0c0b6b28afb4e85816a9f6f6f95d50a80825de754e2dc74939ca4511f0c6eb6c57fa3b
|
7
|
+
data.tar.gz: 6a27cf8634d4ccb20e833a79352ab810d2fe674c2e077a2a9c672d993657d7eb90a9ce2af7bbbd6e72981f03f11c22b4953c92e636bea25c6c2cabe1f8b70940
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# 0.2.0 - March 14, 2023
|
2
|
+
|
3
|
+
- Demo application: Use Ruby 3.2.0, Rails 7.0.4.2
|
4
|
+
- Added simplecov and improve testcoverage
|
5
|
+
- Added reducer errors (ActiveModel::Errors), separate from the models own error object
|
6
|
+
- Use deep_dup before dispatching an action to make sure the original action is never
|
7
|
+
modified by reducer methods. Use deep_symbolize_keys on an action for convenience
|
8
|
+
- Added after_change callback
|
9
|
+
|
1
10
|
# 0.1.1 - April 14, 2022
|
2
11
|
|
3
12
|
- Fix calling flatten! multiple times erased initial state
|
@@ -16,4 +25,4 @@
|
|
16
25
|
|
17
26
|
# 0.0.1 - November 16, 2021
|
18
27
|
|
19
|
-
- Initial release, not
|
28
|
+
- Initial release, not published to rubygems
|
data/README.md
CHANGED
@@ -109,15 +109,17 @@ class Foobar < ApplicationRecord
|
|
109
109
|
private
|
110
110
|
|
111
111
|
def my_redux_reducers
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
112
|
+
[
|
113
|
+
->(state, action) {
|
114
|
+
case action[:type]
|
115
|
+
when :add
|
116
|
+
state[:total] += 1
|
117
|
+
when :remove
|
118
|
+
state[:total] -= 1
|
119
|
+
end
|
120
|
+
state
|
121
|
+
}
|
122
|
+
]
|
121
123
|
end
|
122
124
|
end
|
123
125
|
```
|
@@ -128,7 +130,7 @@ Or specify your own reducer method:
|
|
128
130
|
acts_as_redux :my_redux, reducers: :my_list_of_reducers
|
129
131
|
|
130
132
|
def my_list_of_reducers
|
131
|
-
# ...
|
133
|
+
# ...
|
132
134
|
```
|
133
135
|
|
134
136
|
### Undo/redo
|
@@ -148,7 +150,8 @@ In the controller action use the `undo!` method to perform the action.
|
|
148
150
|
For redoing actions the similar methods `redo?`, `redo_action` and `redo!` are available.
|
149
151
|
|
150
152
|
### Flatten
|
151
|
-
You can 'save' the current state. Essentially this
|
153
|
+
You can 'save' the current state. Essentially this copies the current view state to the initial state
|
154
|
+
and truncates the list of actions. Redo and undo are not possible until new actions are added.
|
152
155
|
Methods `flatten?` and `flatten!` can be used in a view and controller:
|
153
156
|
|
154
157
|
```ruby
|
@@ -192,11 +195,86 @@ on the reducer functions you have implemented.
|
|
192
195
|
For a full working example see the demo applications [view](test/dummy/app/views/foobars/_editor.html.erb)
|
193
196
|
and [controller](test/dummy/app/controllers/foobars_controller.rb).
|
194
197
|
|
198
|
+
### Adding errors
|
199
|
+
Just like you can add validation errors on a model, you can add errors inside your reducer methods.
|
200
|
+
There is an ActiveModel::Errors object for redux errors. It is separate from the one for the model and
|
201
|
+
can be accessed via `reduce_errors`. Use it like this:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
def my_redux_reducers
|
205
|
+
@my_redux_reducers ||= [
|
206
|
+
-> (state, action) {
|
207
|
+
case action[:type]&.to_sym
|
208
|
+
when :add
|
209
|
+
if action[:item].length <= 6
|
210
|
+
state[:items] << { id: next_seq_id, value: CGI.escape(action[:item]) }
|
211
|
+
else
|
212
|
+
reduce_errors.add(:item, :too_long, { count: 6 })
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
state
|
217
|
+
}
|
218
|
+
]
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
Since ActiveModel does not know anything about attributes living inside your redux store
|
223
|
+
using `reduce_errors.full_messages` won't work. You can create you own error to message translation or
|
224
|
+
supply `:base` as the attribute name plus a message.
|
225
|
+
See the [Rails documentation](https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add).
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
reduce_errors.add(:base, message: 'Item should have between 1 and 6 characters')
|
229
|
+
```
|
230
|
+
|
231
|
+
If any reduce error is present the `dispatch!` method will return false.
|
232
|
+
To check if there are any errors present (to prevent saving the model) use:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
@foobar.reduce_valid?
|
236
|
+
```
|
237
|
+
|
238
|
+
In the controller you may want to reload the model if the dispatch action gave an error,
|
239
|
+
so the old state is rendered.
|
240
|
+
|
241
|
+
### After change callback
|
242
|
+
Sometimes you want to do something after the redux store has changed.
|
243
|
+
For instance to manipulate the view state based on all entries
|
244
|
+
(the reducer methods only handle one action at the time).
|
245
|
+
This callback method is called after `dispatch!`, `undo!` and `redo!`.
|
246
|
+
In the model:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
class AnotherFooBar < ApplicationRecord
|
250
|
+
include RailsRedhot::ActsAsRedux
|
251
|
+
|
252
|
+
acts_as_redux :another_redux_store, after_change: :my_after_change_actions
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def another_redux_store_reducers
|
257
|
+
[
|
258
|
+
-> (state, _action) {
|
259
|
+
state[:items] ||= []
|
260
|
+
state
|
261
|
+
},
|
262
|
+
# ...
|
263
|
+
]
|
264
|
+
end
|
265
|
+
|
266
|
+
def my_after_change_actions
|
267
|
+
# Do something with the view_state
|
268
|
+
# view_state[:items].each { do_something }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
195
273
|
## Security
|
196
|
-
Care must be taken to not introduce any vulnerabilities
|
274
|
+
Care must be taken to not introduce any vulnerabilities!
|
197
275
|
When passing values from the request to the reducer functions treat any string or complex
|
198
|
-
values as potential candidates for SQL injection. Either sanitize
|
199
|
-
|
276
|
+
values as potential candidates for SQL injection. Either sanitize or `CGI.escape`
|
277
|
+
strings before adding them to the redux store.
|
200
278
|
|
201
279
|
## Installation
|
202
280
|
Add this line to your application's Gemfile:
|
@@ -242,10 +320,9 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
242
320
|
|
243
321
|
## Remarks
|
244
322
|
|
245
|
-
- Developed using Ruby 3.0.3
|
246
323
|
- This gem is not designed to handle very large lists of actions and state.
|
247
|
-
When calling `undo` the state is rebuilt from scratch
|
248
|
-
|
324
|
+
When calling `undo` the state is rebuilt from scratch,
|
325
|
+
if the list of actions to process is large this would become slow.
|
249
326
|
One would need add 'savepoints' that regularly save the state and rebuild
|
250
327
|
the current state from that point forward
|
251
328
|
- Stricly speaking, hotwire is not needed for this gem to work. Just using
|
@@ -8,6 +8,8 @@ module RailsRedhot
|
|
8
8
|
class_methods do
|
9
9
|
def acts_as_redux(store_name, options = {})
|
10
10
|
reducers = options.key?(:reducers) ? options[:reducers] : "#{store_name}_reducers".to_sym
|
11
|
+
reducer_errors = nil
|
12
|
+
reducer_after_change = options[:after_change]
|
11
13
|
|
12
14
|
store(store_name, accessors: [ :initial_state, :state, :actions, :head, :seq_id ], coder: JSON)
|
13
15
|
|
@@ -43,8 +45,7 @@ module RailsRedhot
|
|
43
45
|
self.state = initial_state
|
44
46
|
if head > -1
|
45
47
|
actions[0..head].each { |action| perform_reduce(action) }
|
46
|
-
|
47
|
-
perform_reduce(initial_state)
|
48
|
+
self.send(reducer_after_change) if reducer_after_change
|
48
49
|
end
|
49
50
|
true
|
50
51
|
else
|
@@ -56,6 +57,7 @@ module RailsRedhot
|
|
56
57
|
if redo?
|
57
58
|
self.head += 1
|
58
59
|
perform_reduce(actions[head])
|
60
|
+
self.send(reducer_after_change) if reducer_after_change
|
59
61
|
true
|
60
62
|
else
|
61
63
|
false
|
@@ -70,6 +72,14 @@ module RailsRedhot
|
|
70
72
|
true
|
71
73
|
end
|
72
74
|
|
75
|
+
define_method('reduce_errors') do
|
76
|
+
reducer_errors
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method('reduce_valid?') do
|
80
|
+
reducer_errors.details.empty?
|
81
|
+
end
|
82
|
+
|
73
83
|
define_method('next_seq_id') do
|
74
84
|
self.seq_id += 1
|
75
85
|
end
|
@@ -80,19 +90,29 @@ module RailsRedhot
|
|
80
90
|
|
81
91
|
self.actions << action
|
82
92
|
self.head += 1
|
83
|
-
perform_reduce(action)
|
84
|
-
|
93
|
+
perform_reduce(action.deep_dup.deep_symbolize_keys)
|
94
|
+
self.send(reducer_after_change) if reducer_after_change
|
95
|
+
reduce_valid?
|
85
96
|
end
|
86
97
|
|
87
98
|
# private
|
88
99
|
|
100
|
+
define_method('reset_reduce_errors') do
|
101
|
+
reducer_errors = ActiveModel::Errors.new(self)
|
102
|
+
end
|
103
|
+
|
89
104
|
define_method('load_store') do
|
90
105
|
self.initial_state ||= {}
|
91
106
|
# self.state is initially nil: no need to store state twice when there are no actions
|
92
107
|
self.head ||= -1
|
93
108
|
self.actions ||= []
|
94
109
|
self.seq_id ||= 0
|
95
|
-
|
110
|
+
|
111
|
+
if state.blank? && initial_state.blank?
|
112
|
+
perform_reduce({})
|
113
|
+
else
|
114
|
+
reset_reduce_errors
|
115
|
+
end
|
96
116
|
end
|
97
117
|
|
98
118
|
define_method('all_reducers') do
|
@@ -100,10 +120,12 @@ module RailsRedhot
|
|
100
120
|
end
|
101
121
|
|
102
122
|
define_method('perform_reduce') do |action|
|
123
|
+
reset_reduce_errors
|
124
|
+
|
103
125
|
self.state = all_reducers.reduce(
|
104
|
-
view_state.
|
126
|
+
view_state.deep_dup.deep_symbolize_keys
|
105
127
|
) do |current_state, reducer|
|
106
|
-
reducer.call(current_state, action
|
128
|
+
reducer.call(current_state, action)
|
107
129
|
end
|
108
130
|
end
|
109
131
|
|
data/lib/rails_redhot/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_redhot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivo Herweijer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -61,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
61
|
- !ruby/object:Gem::Version
|
62
62
|
version: '0'
|
63
63
|
requirements: []
|
64
|
-
rubygems_version: 3.
|
64
|
+
rubygems_version: 3.4.6
|
65
65
|
signing_key:
|
66
66
|
specification_version: 4
|
67
67
|
summary: REDux pattern for HOTwire == Redhot
|