rails_redhot 0.0.2
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 +7 -0
- data/CHANGELOG.md +10 -0
- data/MIT-LICENSE +20 -0
- data/README.md +141 -0
- data/Rakefile +17 -0
- data/lib/rails_redhot/acts_as_redux.rb +116 -0
- data/lib/rails_redhot/version.rb +4 -0
- data/lib/rails_redhot.rb +6 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '059048dabb167e99b923d1021bf334dc4ed59f4fb0d2306aaa199ade9e1a1b67'
|
4
|
+
data.tar.gz: fcdf3b027a5d62b400ae824caeef344d1aaa91593d197d4e48a7b27c963cd4e3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 91d1d21a37b5b6f2b7c9e96a5757e848b53ba92430ffbc794a0ed8313281b474dbfa42369492615822753a6b0667ae235e622a8e896000ab2fc369bef9faaf40
|
7
|
+
data.tar.gz: 17714334908de5714a7e18539ae264d4b937373f3e16888f5c07c91d0f3406485183c12d3bf328d65eb31ab086f8d50f48a6b6899b0f656c2c4da1c3ec9fcb8f
|
data/CHANGELOG.md
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2021 Ivo Herweijer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# RailsRedhot gem
|
2
|
+
__REDux pattern for HOTwire == Redhot__
|
3
|
+
Single page applications using redux (react) are very popular.
|
4
|
+
And with good reason, redux makes maintaining the current state of the app easy.
|
5
|
+
For instance when building some kind of editor, every action of the user is added
|
6
|
+
to the redux store. All actions can be reduced to the determine the current state of
|
7
|
+
the editor. Views are rendered using the current state.
|
8
|
+
Or when building a search page for a webshop. Whenever the user selects a category
|
9
|
+
or price range to filter on this can be an action for the redux store. If the user
|
10
|
+
hits the back button the last action can be deleted and the current state
|
11
|
+
regenerated by reducing all remaining actions. The user only sees the last filter
|
12
|
+
being reverted to what it was before.
|
13
|
+
Sometimes the actions of the user in the frontend should be sent to a backend
|
14
|
+
application. For instance when actions of multiple users should be kept in-sync.
|
15
|
+
Often command-query-responsibility-separation (CQRS) is used for this purpose.
|
16
|
+
These solutions can become very complex
|
17
|
+
([example](https://medium.com/resolvejs/resolve-redux-backend-ebcfc79bbbea)
|
18
|
+
scroll down a bit).
|
19
|
+
|
20
|
+
The Hotwire (Html Over The Wire) approach does an excellent job of removing the need
|
21
|
+
to build single page apps. Hotwire will be the
|
22
|
+
[default tool](https://world.hey.com/dhh/the-time-is-right-for-hotwire-ecdb9b33)
|
23
|
+
for frontend development in Rails 7.
|
24
|
+
However when using hotwire the responsibilty of maintaining frontend state entirely
|
25
|
+
falls to the backend application. So when building your editor or search page
|
26
|
+
you need a way to keep track of that state. The redux (also known as flux) pattern
|
27
|
+
is still very useful for this purpose.
|
28
|
+
|
29
|
+
This gem aims to combine html-over-the-wire approach with the redux pattern to
|
30
|
+
radically reduce complexity of the overall application.
|
31
|
+
(At least when compared to for instance react+cqrs.)
|
32
|
+
Only four components are required:
|
33
|
+
|
34
|
+
1. Views, normal rails views rendering the current state and delivered as turbo frames
|
35
|
+
2. Actions, just (submit) buttons that send a request to the backend
|
36
|
+
3. Store, keeping the list of actions and current state, managed by this gem and stored
|
37
|
+
in an activerecord model
|
38
|
+
4. Reducers, a set of functions (provided by you) that translate actions to changes in state
|
39
|
+
|
40
|
+
Common actions (undo, redo, flatten actions to initial state) are provided by this gem.
|
41
|
+
Combined with turbo frames for rendering partial page updates this makes it easy to
|
42
|
+
create a very smooth user experience.
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
Create a migration to add a 'text' type attribute to a model that should have a redux store.
|
46
|
+
In the model add an `acts_as_redux` line, specifying the name of the text attribute.
|
47
|
+
Add a private method holding all your reducer functions.
|
48
|
+
See [this example](test/dummy/app/models/foobar.rb).
|
49
|
+
Note that all reducer functions must return the state object (a Hash).
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
include RailsRedhot::ActsAsRedux
|
53
|
+
|
54
|
+
acts_as_redux :my_redux
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def my_redux_reducers
|
59
|
+
->(state, action) {
|
60
|
+
case action[:type]
|
61
|
+
when :add
|
62
|
+
state[:total] += 1
|
63
|
+
when :remove
|
64
|
+
state[:total] -= 1
|
65
|
+
end
|
66
|
+
state
|
67
|
+
},
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
Or specify your own reducer method:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
acts_as_redux :my_redux, reducers: :list_of_reducers
|
75
|
+
|
76
|
+
def list_of_reducers
|
77
|
+
# ...
|
78
|
+
```
|
79
|
+
|
80
|
+
In your views add some submit buttons for adding and undoing actions.
|
81
|
+
See [this example](test/dummy/app/views/foobars/_editor.html.erb).
|
82
|
+
And let your controller ([example](test/dummy/app/controllers/foobars_controller.rb))
|
83
|
+
process these actions.
|
84
|
+
See section [Demo application](https://github.com/easydatawarehousing/rails_redhot#demo-application)
|
85
|
+
for a working example.
|
86
|
+
|
87
|
+
## Installation
|
88
|
+
Add this line to your application's Gemfile:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
gem "rails_redhot"
|
92
|
+
```
|
93
|
+
|
94
|
+
And then execute:
|
95
|
+
```bash
|
96
|
+
$ bundle
|
97
|
+
```
|
98
|
+
|
99
|
+
Or install it yourself as:
|
100
|
+
```bash
|
101
|
+
$ gem install rails_redhot
|
102
|
+
```
|
103
|
+
|
104
|
+
## Demo application
|
105
|
+
To use the demo application, clone the repo and run rails:
|
106
|
+
|
107
|
+
```bash
|
108
|
+
git clone https://github.com/easydatawarehousing/rails_redhot.git
|
109
|
+
cd rails_redhot
|
110
|
+
bundle install
|
111
|
+
cd test/dummy
|
112
|
+
rails db:setup
|
113
|
+
rails server
|
114
|
+
```
|
115
|
+
|
116
|
+
Open the [application](http://localhost:3000/foobars).
|
117
|
+
Click on 'New foobar', 'Add a new foobar' and 'Edit this foobar'.
|
118
|
+
|
119
|
+
## Test
|
120
|
+
Run:
|
121
|
+
|
122
|
+
```bash
|
123
|
+
rails test/dummy/test
|
124
|
+
```
|
125
|
+
|
126
|
+
## License
|
127
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
128
|
+
|
129
|
+
## Remarks
|
130
|
+
|
131
|
+
- Developed using Ruby 3.0.3
|
132
|
+
- This gem is not designed to handle very large lists of actions and state.
|
133
|
+
When calling `undo` the state is rebuilt from scratch
|
134
|
+
If the list of actions to process is large this would become slow.
|
135
|
+
One would need add 'savepoints' that regularly save the state and rebuild
|
136
|
+
the current state from that point forward
|
137
|
+
- No checking on the size of the text attribute used for the store is done
|
138
|
+
- Currently only one redux store can be added to a model
|
139
|
+
- Redux store code inspired by:
|
140
|
+
- https://gist.github.com/eadz/31c87375722397be861a0dbcf7fb7408
|
141
|
+
- https://github.com/janlelis/redux.rb
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
4
|
+
load "rails/tasks/engine.rake"
|
5
|
+
|
6
|
+
load "rails/tasks/statistics.rake"
|
7
|
+
|
8
|
+
require "bundler/gem_tasks"
|
9
|
+
require "rake/testtask"
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << "test"
|
13
|
+
t.pattern = "test/**/*_test.rb"
|
14
|
+
t.verbose = false
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: :test
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsRedhot
|
4
|
+
# Include ActAsRedux module to add redux functionality to Rails
|
5
|
+
module ActsAsRedux
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def acts_as_redux(store_name, options = {})
|
10
|
+
reducers = options.key?(:reducers) ? options[:reducers] : "#{store_name}_reducers".to_sym
|
11
|
+
|
12
|
+
store(store_name, accessors: [ :initial_state, :state, :actions, :head, :seq_id ], coder: JSON)
|
13
|
+
|
14
|
+
after_initialize :load_store
|
15
|
+
|
16
|
+
define_method('view_state') do
|
17
|
+
state || initial_state || {}
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method('undo?') do
|
21
|
+
head > -1
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method('redo?') do
|
25
|
+
(head + 1) < actions.length
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method('flatten?') do
|
29
|
+
undo?
|
30
|
+
end
|
31
|
+
|
32
|
+
define_method('undo_action') do
|
33
|
+
undo? ? actions[head] : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
define_method('redo_action') do
|
37
|
+
redo? ? actions[head + 1] : nil
|
38
|
+
end
|
39
|
+
|
40
|
+
define_method('undo!') do
|
41
|
+
if undo?
|
42
|
+
self.head -= 1
|
43
|
+
self.state = initial_state
|
44
|
+
if head > -1
|
45
|
+
actions[0..head].each { |action| perform_reduce(action) }
|
46
|
+
else
|
47
|
+
perform_reduce(initial_state)
|
48
|
+
end
|
49
|
+
true
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
define_method('redo!') do
|
56
|
+
if redo?
|
57
|
+
self.head += 1
|
58
|
+
perform_reduce(actions[head])
|
59
|
+
true
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
define_method('flatten!') do
|
66
|
+
self.initial_state = state
|
67
|
+
self.state = nil
|
68
|
+
self.head = -1
|
69
|
+
self.actions = []
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
define_method('next_seq_id') do
|
74
|
+
self.seq_id += 1
|
75
|
+
end
|
76
|
+
|
77
|
+
define_method('dispatch!') do |action|
|
78
|
+
# Destroy any redo actions
|
79
|
+
self.actions.slice!(head + 1, actions.length - head - 1) if redo?
|
80
|
+
|
81
|
+
self.actions << action
|
82
|
+
self.head += 1
|
83
|
+
perform_reduce(action)
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
# private
|
88
|
+
|
89
|
+
define_method('load_store') do
|
90
|
+
self.initial_state ||= {}
|
91
|
+
# self.state is initially nil: no need to store state twice when there are no actions
|
92
|
+
self.head ||= -1
|
93
|
+
self.actions ||= []
|
94
|
+
self.seq_id ||= 0
|
95
|
+
perform_reduce({}) if state.blank? && initial_state.blank?
|
96
|
+
end
|
97
|
+
|
98
|
+
define_method('all_reducers') do
|
99
|
+
send(reducers)
|
100
|
+
end
|
101
|
+
|
102
|
+
define_method('perform_reduce') do |action|
|
103
|
+
self.state = all_reducers.reduce(
|
104
|
+
view_state.dup.deep_symbolize_keys
|
105
|
+
) do |current_state, reducer|
|
106
|
+
reducer.call(current_state, action.deep_symbolize_keys)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private :load_store, :all_reducers, :perform_reduce,
|
111
|
+
:initial_state, :state, :actions, :head,
|
112
|
+
:initial_state=, :state=, :actions=, :head=, :seq_id=
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/rails_redhot.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_redhot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ivo Herweijer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 7.0.0.rc1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 7.0.0.rc1
|
27
|
+
description: REDux pattern for HOTwire == Redhot
|
28
|
+
email:
|
29
|
+
- info@edwhs.nl
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- CHANGELOG.md
|
35
|
+
- MIT-LICENSE
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- lib/rails_redhot.rb
|
39
|
+
- lib/rails_redhot/acts_as_redux.rb
|
40
|
+
- lib/rails_redhot/version.rb
|
41
|
+
homepage: https://github.com/easydatawarehousing/rails_redhot
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata:
|
45
|
+
allowed_push_host: https://rubygems.org/
|
46
|
+
homepage_uri: https://github.com/easydatawarehousing/rails_redhot
|
47
|
+
source_code_uri: https://github.com/easydatawarehousing/rails_redhot
|
48
|
+
changelog_uri: https://github.com/easydatawarehousing/rails_redhot/blob/master/CHANGELOG.md
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubygems_version: 3.2.32
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: REDux pattern for HOTwire == Redhot
|
68
|
+
test_files: []
|