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 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
@@ -0,0 +1,10 @@
1
+ # 0.0.2 - December 10, 2021
2
+
3
+ [0.0.2]: https://github.com/easydatawarehousing/rails_redhot/compare/v0.0.1...v0.0.2
4
+
5
+ - Added testset
6
+ - Polished demo application
7
+
8
+ # 0.0.1 - November 16, 2021
9
+
10
+ - Initial release, not pulished to rubygems
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
@@ -0,0 +1,4 @@
1
+ module RailsRedhot
2
+ # Gem version
3
+ VERSION = "0.0.2"
4
+ end
@@ -0,0 +1,6 @@
1
+ require "rails_redhot/version"
2
+ require "rails_redhot/acts_as_redux"
3
+
4
+ # Module containing all rails_redhot functionality
5
+ module RailsRedhot
6
+ end
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: []