├── .gitignore
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── rails_redhot_manifest.js
│ ├── images
│ │ └── rails_redhot
│ │ │ └── .keep
│ └── stylesheets
│ │ └── rails_redhot
│ │ └── .keep
├── controllers
│ ├── .keep
│ └── concerns
│ │ └── .keep
├── helpers
│ └── .keep
├── jobs
│ └── .keep
├── models
│ ├── .keep
│ └── concerns
│ │ └── .keep
└── views
│ └── .keep
├── bin
└── rails
├── config
└── routes.rb
├── lib
├── rails_redhot.rb
└── rails_redhot
│ ├── acts_as_redux.rb
│ └── version.rb
├── rails_redhot.gemspec
└── test
├── controllers
└── .keep
├── dummy
├── Procfile.dev
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ └── stylesheets
│ │ │ └── application.tailwind.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── foobars_controller.rb
│ ├── helpers
│ │ ├── application_helper.rb
│ │ └── foobars_helper.rb
│ ├── javascript
│ │ └── application.js
│ ├── jobs
│ │ └── application_job.rb
│ ├── models
│ │ ├── another_foo_bar.rb
│ │ ├── application_record.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── foobar.rb
│ └── views
│ │ ├── foobars
│ │ ├── _editor.html.erb
│ │ ├── _foobar.html.erb
│ │ ├── _form.html.erb
│ │ ├── _notice.html.erb
│ │ ├── edit.html.erb
│ │ ├── index.html.erb
│ │ ├── new.html.erb
│ │ └── show.html.erb
│ │ └── layouts
│ │ └── application.html.erb
├── bin
│ ├── dev
│ ├── importmap
│ ├── rails
│ ├── rake
│ └── setup
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── importmap.rb
│ ├── initializers
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── content_security_policy.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── permissions_policy.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ ├── storage.yml
│ └── tailwind.config.js
├── db
│ ├── migrate
│ │ ├── 20211110151849_create_foobars.rb
│ │ └── 20230314093029_create_another_foo_bars.rb
│ └── schema.rb
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ └── favicon.ico
├── test
│ ├── fixtures
│ │ ├── another_foo_bars.yml
│ │ └── foobars.yml
│ └── models
│ │ ├── another_foo_bar_test.rb
│ │ └── foobar_test.rb
└── vendor
│ └── javascript
│ └── .keep
├── fixtures
└── files
│ └── .keep
├── helpers
└── .keep
├── integration
├── .keep
└── navigation_test.rb
├── mailers
└── .keep
├── models
└── .keep
├── rails_redhot_test.rb
└── test_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /doc/
3 | /log/*.log
4 | /pkg/
5 | /tmp/
6 | /test/dummy/db/*.sqlite3
7 | /test/dummy/db/*.sqlite3-*
8 | /test/dummy/log/*.log
9 | /test/dummy/storage/
10 | /test/dummy/tmp/
11 | /test/dummy/app/assets/builds/
12 | /coverage/
13 | .ruby-version
14 | *.gem
15 | *__
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.3.0 - November 26, 2024
2 |
3 | - Make gem compatible with current Rails 7 and 8 versions
4 |
5 | # 0.2.0 - March 14, 2023
6 |
7 | - Demo application: Use Ruby 3.2.0, Rails 7.0.4.2
8 | - Added simplecov and improve testcoverage
9 | - Added reducer errors (ActiveModel::Errors), separate from the models own error object
10 | - Use deep_dup before dispatching an action to make sure the original action is never
11 | modified by reducer methods. Use deep_symbolize_keys on an action for convenience
12 | - Added after_change callback
13 |
14 | # 0.1.1 - April 14, 2022
15 |
16 | - Fix calling flatten! multiple times erased initial state
17 | - Documentation updates
18 | - Demo application: Use Ruby 3.1.2, Rails 7.0.2
19 |
20 | # 0.1.0 - December 30, 2021
21 |
22 | - Documentation updates
23 | - Use Rails 7.0.0
24 |
25 | # 0.0.2 - December 10, 2021
26 |
27 | - Added testset
28 | - Polished demo application
29 |
30 | # 0.0.1 - November 16, 2021
31 |
32 | - Initial release, not published to rubygems
33 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | # Specify your gem's dependencies in redhot.gemspec.
5 | gemspec
6 |
7 | group :development do
8 | # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
9 | gem "importmap-rails", ">= 0.9.4"
10 |
11 | # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
12 | gem "turbo-rails", ">= 0.9.0"
13 |
14 | # Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]
15 | gem "tailwindcss-rails", ">= 0.5.4"
16 |
17 | gem "sprockets-rails"
18 | end
19 |
20 | # Use sqlite3 as the database for Active Record
21 | gem "sqlite3"
22 | # Use the Puma web server [https://github.com/puma/puma]
23 | gem "puma"
24 | # Use Redis adapter to run Action Cable in production
25 | # gem "redis", "~> 4.0"
26 | # Start debugger with binding.b -- Read more: https://github.com/ruby/debug
27 | # gem "debug", ">= 1.0.0", group: %i[ development test ]
28 | gem "simplecov"
29 |
30 | gem "base64"
31 | gem "bigdecimal"
32 | gem "mutex_m"
33 | gem "drb"
34 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | rails_redhot (0.3.0)
5 | rails (>= 7.0.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (8.0.0)
11 | actionpack (= 8.0.0)
12 | activesupport (= 8.0.0)
13 | nio4r (~> 2.0)
14 | websocket-driver (>= 0.6.1)
15 | zeitwerk (~> 2.6)
16 | actionmailbox (8.0.0)
17 | actionpack (= 8.0.0)
18 | activejob (= 8.0.0)
19 | activerecord (= 8.0.0)
20 | activestorage (= 8.0.0)
21 | activesupport (= 8.0.0)
22 | mail (>= 2.8.0)
23 | actionmailer (8.0.0)
24 | actionpack (= 8.0.0)
25 | actionview (= 8.0.0)
26 | activejob (= 8.0.0)
27 | activesupport (= 8.0.0)
28 | mail (>= 2.8.0)
29 | rails-dom-testing (~> 2.2)
30 | actionpack (8.0.0)
31 | actionview (= 8.0.0)
32 | activesupport (= 8.0.0)
33 | nokogiri (>= 1.8.5)
34 | rack (>= 2.2.4)
35 | rack-session (>= 1.0.1)
36 | rack-test (>= 0.6.3)
37 | rails-dom-testing (~> 2.2)
38 | rails-html-sanitizer (~> 1.6)
39 | useragent (~> 0.16)
40 | actiontext (8.0.0)
41 | actionpack (= 8.0.0)
42 | activerecord (= 8.0.0)
43 | activestorage (= 8.0.0)
44 | activesupport (= 8.0.0)
45 | globalid (>= 0.6.0)
46 | nokogiri (>= 1.8.5)
47 | actionview (8.0.0)
48 | activesupport (= 8.0.0)
49 | builder (~> 3.1)
50 | erubi (~> 1.11)
51 | rails-dom-testing (~> 2.2)
52 | rails-html-sanitizer (~> 1.6)
53 | activejob (8.0.0)
54 | activesupport (= 8.0.0)
55 | globalid (>= 0.3.6)
56 | activemodel (8.0.0)
57 | activesupport (= 8.0.0)
58 | activerecord (8.0.0)
59 | activemodel (= 8.0.0)
60 | activesupport (= 8.0.0)
61 | timeout (>= 0.4.0)
62 | activestorage (8.0.0)
63 | actionpack (= 8.0.0)
64 | activejob (= 8.0.0)
65 | activerecord (= 8.0.0)
66 | activesupport (= 8.0.0)
67 | marcel (~> 1.0)
68 | activesupport (8.0.0)
69 | base64
70 | benchmark (>= 0.3)
71 | bigdecimal
72 | concurrent-ruby (~> 1.0, >= 1.3.1)
73 | connection_pool (>= 2.2.5)
74 | drb
75 | i18n (>= 1.6, < 2)
76 | logger (>= 1.4.2)
77 | minitest (>= 5.1)
78 | securerandom (>= 0.3)
79 | tzinfo (~> 2.0, >= 2.0.5)
80 | uri (>= 0.13.1)
81 | base64 (0.2.0)
82 | benchmark (0.4.0)
83 | bigdecimal (3.1.8)
84 | builder (3.3.0)
85 | concurrent-ruby (1.3.4)
86 | connection_pool (2.4.1)
87 | crass (1.0.6)
88 | date (3.4.0)
89 | docile (1.4.1)
90 | drb (2.2.1)
91 | erubi (1.13.0)
92 | globalid (1.2.1)
93 | activesupport (>= 6.1)
94 | i18n (1.14.6)
95 | concurrent-ruby (~> 1.0)
96 | importmap-rails (2.0.3)
97 | actionpack (>= 6.0.0)
98 | activesupport (>= 6.0.0)
99 | railties (>= 6.0.0)
100 | io-console (0.7.2)
101 | irb (1.14.1)
102 | rdoc (>= 4.0.0)
103 | reline (>= 0.4.2)
104 | logger (1.6.1)
105 | loofah (2.23.1)
106 | crass (~> 1.0.2)
107 | nokogiri (>= 1.12.0)
108 | mail (2.8.1)
109 | mini_mime (>= 0.1.1)
110 | net-imap
111 | net-pop
112 | net-smtp
113 | marcel (1.0.4)
114 | mini_mime (1.1.5)
115 | minitest (5.25.2)
116 | mutex_m (0.3.0)
117 | net-imap (0.5.1)
118 | date
119 | net-protocol
120 | net-pop (0.1.2)
121 | net-protocol
122 | net-protocol (0.2.2)
123 | timeout
124 | net-smtp (0.5.0)
125 | net-protocol
126 | nio4r (2.7.4)
127 | nokogiri (1.16.7-x86_64-linux)
128 | racc (~> 1.4)
129 | psych (5.2.0)
130 | stringio
131 | puma (6.5.0)
132 | nio4r (~> 2.0)
133 | racc (1.8.1)
134 | rack (3.1.8)
135 | rack-session (2.0.0)
136 | rack (>= 3.0.0)
137 | rack-test (2.1.0)
138 | rack (>= 1.3)
139 | rackup (2.2.1)
140 | rack (>= 3)
141 | rails (8.0.0)
142 | actioncable (= 8.0.0)
143 | actionmailbox (= 8.0.0)
144 | actionmailer (= 8.0.0)
145 | actionpack (= 8.0.0)
146 | actiontext (= 8.0.0)
147 | actionview (= 8.0.0)
148 | activejob (= 8.0.0)
149 | activemodel (= 8.0.0)
150 | activerecord (= 8.0.0)
151 | activestorage (= 8.0.0)
152 | activesupport (= 8.0.0)
153 | bundler (>= 1.15.0)
154 | railties (= 8.0.0)
155 | rails-dom-testing (2.2.0)
156 | activesupport (>= 5.0.0)
157 | minitest
158 | nokogiri (>= 1.6)
159 | rails-html-sanitizer (1.6.0)
160 | loofah (~> 2.21)
161 | nokogiri (~> 1.14)
162 | railties (8.0.0)
163 | actionpack (= 8.0.0)
164 | activesupport (= 8.0.0)
165 | irb (~> 1.13)
166 | rackup (>= 1.0.0)
167 | rake (>= 12.2)
168 | thor (~> 1.0, >= 1.2.2)
169 | zeitwerk (~> 2.6)
170 | rake (13.2.1)
171 | rdoc (6.8.1)
172 | psych (>= 4.0.0)
173 | reline (0.5.11)
174 | io-console (~> 0.5)
175 | securerandom (0.3.2)
176 | simplecov (0.22.0)
177 | docile (~> 1.1)
178 | simplecov-html (~> 0.11)
179 | simplecov_json_formatter (~> 0.1)
180 | simplecov-html (0.13.1)
181 | simplecov_json_formatter (0.1.4)
182 | sprockets (4.2.1)
183 | concurrent-ruby (~> 1.0)
184 | rack (>= 2.2.4, < 4)
185 | sprockets-rails (3.5.2)
186 | actionpack (>= 6.1)
187 | activesupport (>= 6.1)
188 | sprockets (>= 3.0.0)
189 | sqlite3 (2.3.1-x86_64-linux-gnu)
190 | stringio (3.1.2)
191 | tailwindcss-rails (3.0.0)
192 | railties (>= 7.0.0)
193 | tailwindcss-ruby
194 | tailwindcss-ruby (3.4.15-x86_64-linux)
195 | thor (1.3.2)
196 | timeout (0.4.2)
197 | turbo-rails (2.0.11)
198 | actionpack (>= 6.0.0)
199 | railties (>= 6.0.0)
200 | tzinfo (2.0.6)
201 | concurrent-ruby (~> 1.0)
202 | uri (1.0.2)
203 | useragent (0.16.10)
204 | websocket-driver (0.7.6)
205 | websocket-extensions (>= 0.1.0)
206 | websocket-extensions (0.1.5)
207 | zeitwerk (2.7.1)
208 |
209 | PLATFORMS
210 | x86_64-linux
211 |
212 | DEPENDENCIES
213 | base64
214 | bigdecimal
215 | drb
216 | importmap-rails (>= 0.9.4)
217 | mutex_m
218 | puma
219 | rails_redhot!
220 | simplecov
221 | sprockets-rails
222 | sqlite3
223 | tailwindcss-rails (>= 0.5.4)
224 | turbo-rails (>= 0.9.0)
225 |
226 | BUNDLED WITH
227 | 2.5.23
228 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
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.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
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 complex 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 |
14 | ### What is redux?
15 | ```
16 | It is a store containing a list of changes.
17 | All changes combined determine the current view state through one or more reducer functions.
18 | The current view state is also stored.
19 | If a new change arrives it only needs to be applied to the current view state.
20 | To undo a change apply all but the last action again to rebuild te view state.
21 |
22 | For example a view contains a number counting the likes for an article.
23 | There are two buttons: increase- and decrease the likes counter.
24 | Clicking on a button adds an action to the store.
25 | The action is passed on to all reducer functions,
26 | each function passes the new computed state on to the next function:
27 | - If the counter value is nil in the current state set it to zero
28 | - If the action was 'increase' then increment the counter value
29 | - If the action was 'decrease' then decrement the counter value
30 | - If the counter value is lower than zero set it to zero
31 | Save the new state in the store.
32 | Render the view showing the updated counter value.
33 | ```
34 |
35 | ### What are the advantages of redux?
36 | From the [redux website](https://redux.js.org/) (minus the part about plugins):
37 | ```
38 | Predictable
39 | Redux helps you write applications that behave consistently,
40 | run in different environments (client, server, and native)
41 | and are easy to test.
42 |
43 | Centralized
44 | Centralizing your application's state and logic enables powerful capabilities
45 | like undo/redo, state persistence, and much more.
46 |
47 | Debuggable
48 | The Redux DevTools make it easy to trace when, where, why
49 | and how your application's state changed.
50 | Redux's architecture lets you log changes, use 'time-travel debugging'
51 | and even send complete error reports to a server.
52 | ```
53 |
54 | ### Remove complexity
55 | Sometimes the actions of the user in the frontend should be sent to a backend
56 | application. For instance when actions of multiple users should be kept in-sync.
57 | In react applications command-query-responsibility-separation (CQRS) is often
58 | used for this purpose. These solutions can become very complex
59 | ([example](https://medium.com/resolvejs/resolve-redux-backend-ebcfc79bbbea),
60 | scroll down a bit for a full architecture picture).
61 |
62 | The Hotwire (Html Over The Wire) approach does an excellent job of removing the need
63 | to build single page apps. Hotwire is the
64 | [default tool](https://world.hey.com/dhh/the-time-is-right-for-hotwire-ecdb9b33)
65 | for frontend development in Rails 7.
66 | However when using hotwire the responsibilty of maintaining frontend state entirely
67 | falls to the backend application. So when building your editor or search page
68 | you need a way to keep track of that state. The redux (also known as flux- or observer-)
69 | pattern is very useful for this purpose.
70 |
71 | ### Hotwire
72 | This gem aims to combine html-over-the-wire approach with the redux pattern to
73 | radically reduce overall complexity of an application.
74 | (At least when compared to for instance react+cqrs application stacks.)
75 | Only four components are required:
76 |
77 | 1. Views, normal rails views rendering the current state and delivered as turbo frames
78 | 2. Actions, just submit buttons that send a request to the backend handled by a controller
79 | 3. Store, keeping the list of actions and current state, managed by this gem and stored
80 | in an activerecord model
81 | 4. Reducers, a set of functions (provided by you) that translate actions to changes in
82 | state. The state can be used again in step 1
83 |
84 | ### Benefits
85 |
86 | - Straightforward workflow
87 | - Common actions (undo, redo, flatten actions to initial state) are provided by this gem.
88 | Combined with turbo frames for rendering partial page updates this makes it easy to
89 | create a very smooth user experience
90 | - You can create a store of attributes within a single ActiveRecord model.
91 | In a Single Page App (SPA) lots of settings may be needed for a good user
92 | experience. It may be a lot of work to store these in multiple models.
93 | A redux store can hold an arbitrary amount of attributes
94 |
95 | ## Usage
96 | ### Model
97 | Create a migration to add a 'text' type attribute to a model that should have a redux store.
98 | In the model add an `acts_as_redux` line, specifying the name of the text attribute.
99 | Add a private method holding all your reducer functions.
100 | See [this example](test/dummy/app/models/foobar.rb).
101 | Note that all reducer functions must return the state object (a Hash).
102 |
103 | ```ruby
104 | class Foobar < ApplicationRecord
105 | include RailsRedhot::ActsAsRedux
106 |
107 | acts_as_redux :my_redux
108 |
109 | private
110 |
111 | def my_redux_reducers
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 | ]
123 | end
124 | end
125 | ```
126 |
127 | Or specify your own reducer method:
128 |
129 | ```ruby
130 | acts_as_redux :my_redux, reducers: :my_list_of_reducers
131 |
132 | def my_list_of_reducers
133 | # ...
134 | ```
135 |
136 | ### Undo/redo
137 | Every instance of the model now has access to several methods.
138 | For undoing actions there are: `undo?`, `undo_action` and `undo!`,
139 | which you might use in a view like this:
140 |
141 | ```ruby
142 | <%- if foobar.undo? %>
143 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
144 | <%= form.hidden_field :action, value: :undo %>
145 | <%= form.submit "Undo: #{foobar.undo_action['type']}" %>
146 | <% end %>
147 | <% end %>
148 | ```
149 | In the controller action use the `undo!` method to perform the action.
150 | For redoing actions the similar methods `redo?`, `redo_action` and `redo!` are available.
151 |
152 | ### Flatten
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.
155 | Methods `flatten?` and `flatten!` can be used in a view and controller:
156 |
157 | ```ruby
158 | <%- if foobar.flatten? %>
159 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
160 | <%= form.hidden_field :action, value: :flatten %>
161 | <%= form.submit "Save changes" %>
162 | <% end %>
163 | <% end %>
164 | ```
165 |
166 | ### Sequence ID
167 | As a convenience a sequence ID id is available which should always return a unique id
168 | (within the context of the model instance). To get the next sequence id use `next_seq_id`,
169 | to get the current sequence value use `seq_id`.
170 | You could use a sequence in a reducer function to make sure every added item is assigned a unique id.
171 |
172 | ### Dispatch actions and view state
173 | To add an action to the store you can use the `dispatch!` method, passing a hash with the details of the action.
174 | What the content of that hash should be is up to you.
175 | As long as your reducer fuctions can handle the action anything is possible.
176 |
177 | Finally to get the current state the `view_state` method is available.
178 | In a view it can be used like so:
179 |
180 | ```ruby
181 |
182 | There are <%= foobar.view_state['total'] %> items
183 |
184 |
185 | <%- foobar.view_state['items'].each do |item| %>
186 |
187 | <%= CGI.unescape(item['value']) %>
188 |
189 | <% end %>
190 | ```
191 |
192 | The `view_state` method returns a Hash. What the content of this hash looks like depends
193 | on the reducer functions you have implemented.
194 |
195 | For a full working example see the demo applications [view](test/dummy/app/views/foobars/_editor.html.erb)
196 | and [controller](test/dummy/app/controllers/foobars_controller.rb).
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 |
273 | ## Security
274 | Care must be taken to not introduce any vulnerabilities!
275 | When passing values from the request to the reducer functions treat any string or complex
276 | values as potential candidates for SQL injection. Either sanitize or `CGI.escape`
277 | strings before adding them to the redux store.
278 |
279 | ## Installation
280 | Add this line to your application's Gemfile:
281 |
282 | ```ruby
283 | gem "rails_redhot"
284 | ```
285 |
286 | And then execute:
287 | ```bash
288 | $ bundle
289 | ```
290 |
291 | Or install it yourself as:
292 | ```bash
293 | $ gem install rails_redhot
294 | ```
295 |
296 | ## Demo application
297 | To use the demo application, clone the repo and run rails:
298 |
299 | ```bash
300 | git clone https://github.com/easydatawarehousing/rails_redhot.git
301 | cd rails_redhot
302 | bundle install
303 | cd test/dummy
304 | rails db:setup
305 | bin/dev
306 | ```
307 |
308 | Then open the [application](http://localhost:3000/foobars).
309 | Click on 'New foobar', 'Add a new foobar' and 'Edit this foobar'.
310 |
311 | ## Test
312 | Run:
313 |
314 | ```bash
315 | rails test test/dummy/test
316 | ```
317 |
318 | ## License
319 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
320 |
321 | ## Remarks
322 |
323 | - This gem is not designed to handle very large lists of actions and state.
324 | When calling `undo` the state is rebuilt from scratch,
325 | if the list of actions to process is large this would become slow.
326 | One would need add 'savepoints' that regularly save the state and rebuild
327 | the current state from that point forward
328 | - Stricly speaking, hotwire is not needed for this gem to work. Just using
329 | plain old rails views and controllers is fine. Hotwire certainly makes
330 | an application using this gem a lot faster
331 | - No checking on the size of the text attribute used for the store is done
332 | - Currently only one redux store can be added to a model
333 | - Redux store code inspired by:
334 | - https://gist.github.com/eadz/31c87375722397be861a0dbcf7fb7408
335 | - https://github.com/janlelis/redux.rb
336 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
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
18 |
--------------------------------------------------------------------------------
/app/assets/config/rails_redhot_manifest.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/assets/config/rails_redhot_manifest.js
--------------------------------------------------------------------------------
/app/assets/images/rails_redhot/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/assets/images/rails_redhot/.keep
--------------------------------------------------------------------------------
/app/assets/stylesheets/rails_redhot/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/assets/stylesheets/rails_redhot/.keep
--------------------------------------------------------------------------------
/app/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/controllers/.keep
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/helpers/.keep
--------------------------------------------------------------------------------
/app/jobs/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/jobs/.keep
--------------------------------------------------------------------------------
/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/models/.keep
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/views/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/app/views/.keep
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails gems
3 | # installed from the root of your application.
4 |
5 | ENGINE_ROOT = File.expand_path("..", __dir__)
6 | ENGINE_PATH = File.expand_path("../lib/rails_redhot/engine", __dir__)
7 | APP_PATH = File.expand_path("../test/dummy/config/application", __dir__)
8 |
9 | # Set up gems listed in the Gemfile.
10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
11 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
12 |
13 | require "rails"
14 | # Pick the frameworks you want:
15 | require "active_model/railtie"
16 | require "active_job/railtie"
17 | require "active_record/railtie"
18 | require "active_storage/engine"
19 | require "action_controller/railtie"
20 | # require "action_mailer/railtie"
21 | require "action_view/railtie"
22 | require "action_cable/engine"
23 | require "sprockets/railtie"
24 | require "rails/test_unit/railtie"
25 | require "rails/engine/commands"
26 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | end
3 |
--------------------------------------------------------------------------------
/lib/rails_redhot.rb:
--------------------------------------------------------------------------------
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
7 |
--------------------------------------------------------------------------------
/lib/rails_redhot/acts_as_redux.rb:
--------------------------------------------------------------------------------
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 | reducer_errors = nil
12 | reducer_after_change = options[:after_change]
13 |
14 | store(store_name, accessors: [ :initial_state, :state, :actions, :head, :seq_id ], coder: JSON)
15 |
16 | after_initialize :load_store
17 |
18 | define_method('view_state') do
19 | state || initial_state || {}
20 | end
21 |
22 | define_method('undo?') do
23 | head > -1
24 | end
25 |
26 | define_method('redo?') do
27 | (head + 1) < actions.length
28 | end
29 |
30 | define_method('flatten?') do
31 | undo?
32 | end
33 |
34 | define_method('undo_action') do
35 | undo? ? actions[head] : nil
36 | end
37 |
38 | define_method('redo_action') do
39 | redo? ? actions[head + 1] : nil
40 | end
41 |
42 | define_method('undo!') do
43 | if undo?
44 | self.head -= 1
45 | self.state = initial_state
46 | if head > -1
47 | actions[0..head].each { |action| perform_reduce(action) }
48 | self.send(reducer_after_change) if reducer_after_change
49 | end
50 | true
51 | else
52 | false
53 | end
54 | end
55 |
56 | define_method('redo!') do
57 | if redo?
58 | self.head += 1
59 | perform_reduce(actions[head])
60 | self.send(reducer_after_change) if reducer_after_change
61 | true
62 | else
63 | false
64 | end
65 | end
66 |
67 | define_method('flatten!') do
68 | self.initial_state = view_state
69 | self.state = nil
70 | self.head = -1
71 | self.actions = []
72 | true
73 | end
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 |
83 | define_method('next_seq_id') do
84 | self.seq_id += 1
85 | end
86 |
87 | define_method('dispatch!') do |action|
88 | # Destroy any redo actions
89 | self.actions.slice!(head + 1, actions.length - head - 1) if redo?
90 |
91 | self.actions << action
92 | self.head += 1
93 | perform_reduce(action.deep_dup.deep_symbolize_keys)
94 | self.send(reducer_after_change) if reducer_after_change
95 | reduce_valid?
96 | end
97 |
98 | # private
99 |
100 | define_method('reset_reduce_errors') do
101 | reducer_errors = ActiveModel::Errors.new(self)
102 | end
103 |
104 | define_method('load_store') do
105 | self.initial_state ||= {}
106 | # self.state is initially nil: no need to store state twice when there are no actions
107 | self.head ||= -1
108 | self.actions ||= []
109 | self.seq_id ||= 0
110 |
111 | if state.blank? && initial_state.blank?
112 | perform_reduce({})
113 | else
114 | reset_reduce_errors
115 | end
116 | end
117 |
118 | define_method('all_reducers') do
119 | send(reducers)
120 | end
121 |
122 | define_method('perform_reduce') do |action|
123 | reset_reduce_errors
124 |
125 | self.state = all_reducers.reduce(
126 | view_state.deep_dup.deep_symbolize_keys
127 | ) do |current_state, reducer|
128 | reducer.call(current_state, action)
129 | end
130 | end
131 |
132 | private :load_store, :all_reducers, :perform_reduce,
133 | :initial_state, :state, :actions, :head,
134 | :initial_state=, :state=, :actions=, :head=, :seq_id=
135 | end
136 | end
137 | end
138 | end
139 |
--------------------------------------------------------------------------------
/lib/rails_redhot/version.rb:
--------------------------------------------------------------------------------
1 | module RailsRedhot
2 | # Gem version
3 | VERSION = "0.3.0"
4 | end
5 |
--------------------------------------------------------------------------------
/rails_redhot.gemspec:
--------------------------------------------------------------------------------
1 | require_relative "lib/rails_redhot/version"
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "rails_redhot"
5 | spec.version = RailsRedhot::VERSION
6 | spec.authors = ["Ivo Herweijer"]
7 | spec.email = ["info@edwhs.nl"]
8 | spec.homepage = "https://github.com/easydatawarehousing/rails_redhot"
9 | spec.summary = "REDux pattern for HOTwire == Redhot"
10 | spec.description = "REDux pattern for HOTwire == Redhot"
11 | spec.license = "MIT"
12 |
13 | spec.metadata["allowed_push_host"] = "https://rubygems.org/"
14 | spec.metadata["homepage_uri"] = spec.homepage
15 | spec.metadata["source_code_uri"] = "https://github.com/easydatawarehousing/rails_redhot"
16 | spec.metadata["changelog_uri"] = "https://github.com/easydatawarehousing/rails_redhot/blob/master/CHANGELOG.md"
17 |
18 | spec.files = Dir["{lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "CHANGELOG.md"]
19 |
20 | spec.add_dependency "rails", ">= 7.0.0"
21 | end
22 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/controllers/.keep
--------------------------------------------------------------------------------
/test/dummy/Procfile.dev:
--------------------------------------------------------------------------------
1 | web: bin/rails server -p 3000
2 | css: bin/rails tailwindcss:watch
3 |
--------------------------------------------------------------------------------
/test/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link_tree ../../javascript .js
4 | //= link_tree ../../../vendor/javascript .js
5 | //= link_tree ../builds
6 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/application.tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/controllers/foobars_controller.rb:
--------------------------------------------------------------------------------
1 | class FoobarsController < ApplicationController
2 | before_action :set_foobar, only: %i[ show edit update update_action destroy ]
3 |
4 | # GET /foobars
5 | def index
6 | @foobars = Foobar.all
7 | end
8 |
9 | # GET /foobars/1
10 | def show
11 | end
12 |
13 | # GET /foobars/new
14 | def new
15 | @foobar = Foobar.new
16 | end
17 |
18 | # GET /foobars/1/edit
19 | def edit
20 | end
21 |
22 | # POST /foobars
23 | def create
24 | @foobar = Foobar.new(foobar_params)
25 |
26 | if @foobar.save
27 | redirect_to @foobar, notice: "Foobar was successfully created"
28 | else
29 | render :new, status: :unprocessable_entity
30 | end
31 | end
32 |
33 | # PATCH/PUT /foobars/1
34 | def update
35 | if @foobar.update(foobar_params)
36 | redirect_to @foobar, notice: "Foobar was successfully updated"
37 | else
38 | render :edit, status: :unprocessable_entity
39 | end
40 | end
41 |
42 | # DELETE /foobars/1
43 | def destroy
44 | @foobar.destroy
45 | redirect_to foobars_url, notice: "Foobar was successfully destroyed"
46 | end
47 |
48 | # PUT /foobars/1/update_action
49 | def update_action
50 | notice = case foobar_update_action_params[:action].to_sym
51 | when :add_foo
52 | do_dispatch(:add, 'Foo')
53 | when :add_bar
54 | do_dispatch(:add, 'Bar')
55 | when :add_custom
56 | do_dispatch(:add, foobar_update_action_params[:custom].strip.capitalize)
57 | when :remove
58 | do_dispatch(:remove, foobar_update_action_params[:action_id]&.to_i)
59 | when :undo
60 | do_undo
61 | when :redo
62 | do_redo
63 | when :flatten
64 | do_flatten
65 | end
66 |
67 | @foobar.reload if !@foobar.reduce_valid?
68 | @foobar.save! if @foobar.changed?
69 |
70 | pp_acts_as_redux
71 |
72 | flash.now.notice = notice if notice.present?
73 | render :edit
74 | end
75 |
76 | private
77 |
78 | def do_dispatch(type, value)
79 | return if value.blank?
80 |
81 | if @foobar.dispatch!(type: type, item: value)
82 | 'Updated !'
83 | else
84 | @foobar.reduce_errors.full_messages.join('. ')
85 | end
86 | end
87 |
88 | def do_undo
89 | @foobar.undo! ? 'Its undone !' : 'Nothing to undo'
90 | end
91 |
92 | def do_redo
93 | @foobar.redo! ? 'Its redone !' : 'Nothing to redo'
94 | end
95 |
96 | def do_flatten
97 | @foobar.flatten! ? 'Saved !' : 'Nothing to save'
98 | end
99 |
100 | def pp_acts_as_redux
101 | puts '='*80
102 | puts "Initial state: #{@foobar.my_redux['initial_state']&.dig('total') || 0} items"
103 | pp @foobar.my_redux['initial_state']&.dig('items')
104 |
105 | puts "\nCurrent state: #{@foobar.my_redux['state']&.dig('total') || 0} items"
106 | pp @foobar.my_redux['state']&.dig('items')
107 |
108 | puts "\nList of actions:"
109 | @foobar.my_redux['actions'].each_with_index do |action, i|
110 | puts "#{i == @foobar.my_redux['head'] ? 'HEAD =>' : ' '*7} #{action['type'].ljust(6)} : '#{action['item']}'"
111 | end
112 |
113 | puts "\nLast used sequence-id: #{@foobar.my_redux['seq_id']}"
114 | puts '='*80
115 | end
116 |
117 | # Use callbacks to share common setup or constraints between actions.
118 | def set_foobar
119 | @foobar = Foobar.find(params[:id])
120 | end
121 |
122 | # Only allow a list of trusted parameters through.
123 | def foobar_params
124 | params.fetch(:foobar, {})
125 | end
126 |
127 | # Only allow a list of trusted parameters through.
128 | def foobar_update_action_params
129 | params.require(:foobar).permit(:action, :action_id, :custom)
130 | end
131 | end
132 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/foobars_helper.rb:
--------------------------------------------------------------------------------
1 | module FoobarsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/javascript/application.js:
--------------------------------------------------------------------------------
1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 | import "@hotwired/turbo-rails"
3 |
--------------------------------------------------------------------------------
/test/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/test/dummy/app/models/another_foo_bar.rb:
--------------------------------------------------------------------------------
1 | class AnotherFooBar < ApplicationRecord
2 |
3 | acts_as_redux :another_redux_store,
4 | reducers: :my_redux_reducers,
5 | after_change: :my_after_change_actions
6 |
7 | private
8 |
9 | def my_redux_reducers
10 | [
11 | # Initialize the store if needed
12 | -> (state, _action) {
13 | state[:items] ||= []
14 | state
15 | },
16 | # Add item
17 | -> (state, action) {
18 | state[:items] << { id: next_seq_id, value: CGI.escape(action[:item]), color: '' }
19 | state
20 | }
21 | ]
22 | end
23 |
24 | def my_after_change_actions
25 | if view_state[:items].length.odd?
26 | view_state[:items].each { |item| item[:color] = 'red' }
27 | else
28 | view_state[:items].each { |item| item[:color] = 'green' }
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/test/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | include RailsRedhot::ActsAsRedux
3 |
4 | primary_abstract_class
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/models/foobar.rb:
--------------------------------------------------------------------------------
1 | class Foobar < ApplicationRecord
2 |
3 | acts_as_redux :my_redux
4 |
5 | private
6 |
7 | def my_redux_reducers
8 | # Memoize reducers
9 | @my_redux_reducers ||= [
10 | # Initialize the store if needed
11 | -> (state, _action) {
12 | state[:total] ||= 0
13 | state[:items] ||= []
14 |
15 | state
16 | },
17 | # Update total
18 | -> (state, action) {
19 | case action[:type]&.to_sym
20 | when :add
21 | state[:total] += 1
22 | when :remove
23 | state[:total] -= 1
24 | end
25 |
26 | state
27 | },
28 | # Update items
29 | -> (state, action) {
30 | case action[:type]&.to_sym
31 | when :add
32 | if action[:item].length.between?(1, 6)
33 | state[:items] << { id: next_seq_id, value: CGI.escape(action[:item]) }
34 | else
35 | reduce_errors.add(:base, message: 'Item should have between 1 and 6 characters')
36 | end
37 | when :remove
38 | if action[:item].blank? # Simple check for testing reduce_errors
39 | reduce_errors.add(:base, :blank)
40 | else
41 | state[:items].delete_if do |item|
42 | item.symbolize_keys!
43 | item[:id] == action[:item]
44 | end
45 | end
46 | end
47 |
48 | state
49 | }
50 | ]
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/_editor.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "notice", locals: { notice: notice } %>
2 |
3 |
4 | There are currently <%= foobar.view_state['total'] %> items
5 |
6 |
7 |
8 |
Current items:
9 |
10 | <%- if foobar.view_state.blank? || foobar.view_state['total'].zero? %>
11 |
There are no foobar items yet, please add one by using the buttons below
12 | <% else %>
13 |
14 | <%- foobar.view_state['items'].each do |item| %>
15 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
16 |
17 |
18 | <%= CGI.unescape(item['value']) %>
19 |
20 | <%= form.hidden_field :action, value: :remove %>
21 | <%= form.hidden_field :action_id, value: item['id'] %>
22 | <%= form.submit "X", class: "mr-6 p-3 h-10 text-gray-700 rounded text-sm font-bold bg-gray-200 hover:bg-gray-400" %>
23 | <% end %>
24 |
25 | <% end %>
26 |
27 | <% end %>
28 |
29 |
30 |
31 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
32 | <%= form.hidden_field :action, value: :add_foo %>
33 | <%= form.submit "Add a Foo", class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-500 rounded text-sm font-bold" %>
34 | <% end %>
35 |
36 |
37 |
38 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
39 | <%= form.hidden_field :action, value: :add_bar %>
40 | <%= form.submit "Add a Bar", class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-500 rounded text-sm font-bold" %>
41 | <% end %>
42 |
43 |
44 |
45 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
46 | <%= form.text_field :custom, placeholder: "Add a custom element", class: "w-56" %>
47 | <%= form.hidden_field :action, value: :add_custom %>
48 | <%= form.submit "Add custom", class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-500 rounded text-sm font-bold" %>
49 | <% end %>
50 |
51 |
52 | <%- if foobar.undo? %>
53 |
54 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
55 | <%= form.hidden_field :action, value: :undo %>
56 | <%= form.submit "Undo: #{foobar.undo_action['type'].capitalize}", class: "p-3 h-10 bg-red-700 text-gray-100 hover:bg-red-500 rounded text-sm font-bold" %>
57 | <% end %>
58 |
59 | <% end %>
60 |
61 | <%- if foobar.redo? %>
62 |
63 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
64 | <%= form.hidden_field :action, value: :redo %>
65 | <%= form.submit "Redo: #{foobar.redo_action['type'].capitalize}", class: "p-3 h-10 bg-red-700 text-gray-100 hover:bg-red-500 rounded text-sm font-bold" %>
66 | <% end %>
67 |
68 | <% end %>
69 |
70 | <%- if foobar.flatten? %>
71 |
72 | <%= form_with(model: foobar, url: update_action_foobar_path(foobar), method: :put) do |form| %>
73 | <%= form.hidden_field :action, value: :flatten %>
74 | <%= form.submit "Save changes", class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-500 rounded text-sm font-bold" %>
75 | <% end %>
76 |
77 | <% end %>
78 |
79 |
80 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/_foobar.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%- if foobar.view_state['total'] == 0 %>
4 |
Empty
5 | <% else %>
6 | <%- foobar.view_state['items'].each do |item| %>
7 |
8 | <%= CGI.unescape(item['value']) %>
9 |
10 | <% end %>
11 | <% end %>
12 |
13 |
14 |
15 | <%= link_to "Show foobar #{foobar.id}", foobar, class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold mr-4" %>
16 | <%= link_to "Edit this foobar", edit_foobar_path(foobar), class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold mr-4" %>
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_with(model: foobar) do |form| %>
2 | <% if foobar.errors.any? %>
3 |
4 |
<%= pluralize(foobar.errors.count, "error") %> prohibited this foobar from being saved:
5 |
6 |
7 | <% foobar.errors.each do |error| %>
8 | - <%= error.full_message %>
9 | <% end %>
10 |
11 |
12 | <% end %>
13 |
14 |
15 | <%= form.submit "Add a new Foobar", class: "p-3 h-10 bg-red-700 text-gray-100 hover:bg-red-500 border border-red-700 rounded text-sm font-bold" %>
16 |
17 | <% end %>
18 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/_notice.html.erb:
--------------------------------------------------------------------------------
1 | <%- if flash.notice %>
2 |
3 | <%= flash.notice %>
4 |
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/edit.html.erb:
--------------------------------------------------------------------------------
1 | Editing foobar <%= @foobar.id %>
2 |
3 |
4 | <%= render partial: "editor", locals: { foobar: @foobar } %>
5 |
6 |
7 |
8 | <%= link_to "Show this foobar", @foobar, class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold mr-4" %>
9 | <%= link_to "Back to foobars", foobars_path, class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold" %>
10 |
11 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "notice", locals: { notice: notice } %>
2 |
3 | Foobars
4 |
5 |
6 | <%= render @foobars %>
7 |
8 |
9 |
10 | <%= link_to "New foobar", new_foobar_path, class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold mr-4" %>
11 |
12 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/new.html.erb:
--------------------------------------------------------------------------------
1 | New foobar
2 |
3 | <%= render "form", foobar: @foobar %>
4 |
5 |
6 | <%= link_to "Back to foobars", foobars_path, class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold" %>
7 |
8 |
--------------------------------------------------------------------------------
/test/dummy/app/views/foobars/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "notice", locals: { notice: notice } %>
2 |
3 | <%= render @foobar %>
4 |
5 |
6 | <%= link_to "Edit this foobar", edit_foobar_path(@foobar), class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold mr-4" %>
7 | <%= link_to "Back to foobars", foobars_path, class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold mr-4" %>
8 | <%= button_to "Destroy this foobar", foobar_path(@foobar), method: :delete, class: "p-3 h-10 bg-green-900 text-gray-100 hover:bg-green-700 border border-green-900 rounded text-sm font-bold" %>
9 |
10 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 | <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
8 | <%= javascript_importmap_tags %>
9 |
10 |
11 |
12 |
13 | <%= yield %>
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/dummy/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | clear
3 |
4 | if ! command -v foreman &> /dev/null
5 | then
6 | echo "Installing foreman..."
7 | gem install foreman
8 | fi
9 |
10 | foreman start -f Procfile.dev
11 |
--------------------------------------------------------------------------------
/test/dummy/bin/importmap:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require_relative "../config/application"
4 | require "importmap/commands"
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path("..", __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts "== Installing dependencies =="
17 | system! "gem install bundler --conservative"
18 | system("bundle check") || system!("bundle install")
19 |
20 | # puts "\n== Copying sample files =="
21 | # unless File.exist?("config/database.yml")
22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml"
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! "bin/rails db:prepare"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/test/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails"
4 | # Pick the frameworks you want:
5 | require "active_model/railtie"
6 | require "active_job/railtie"
7 | require "active_record/railtie"
8 | require "active_storage/engine"
9 | require "action_controller/railtie"
10 | # require "action_mailer/railtie"
11 | require "action_mailbox/engine"
12 | require "action_text/engine"
13 | require "action_view/railtie"
14 | require "action_cable/engine"
15 | require "sprockets/railtie"
16 | require "rails/test_unit/railtie"
17 |
18 | # Require the gems listed in Gemfile, including any gems
19 | # you've limited to :test, :development, or :production.
20 | Bundler.require(*Rails.groups)
21 | require "rails_redhot"
22 |
23 | module Dummy
24 | class Application < Rails::Application
25 | config.load_defaults Rails::VERSION::STRING.to_f
26 |
27 | # For compatibility with applications that use this config
28 | config.action_controller.include_all_helpers = false
29 |
30 | # Configuration for the application, engines, and railties goes here.
31 | #
32 | # These settings can be overridden in specific environments using the files
33 | # in config/environments, which are processed later.
34 | #
35 | # config.time_zone = "Central Time (US & Canada)"
36 | # config.eager_load_paths << Rails.root.join("extras")
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/test/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)
3 |
4 | # :nocov:
5 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
6 | # :nocov:
7 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)
8 |
--------------------------------------------------------------------------------
/test/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem "sqlite3"
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/test/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable/disable caching. By default caching is disabled.
18 | # Run rails dev:cache to toggle caching.
19 | if Rails.root.join("tmp/caching-dev.txt").exist?
20 | config.action_controller.perform_caching = true
21 | config.action_controller.enable_fragment_cache_logging = true
22 |
23 | config.cache_store = :memory_store
24 | config.public_file_server.headers = {
25 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
26 | }
27 | else
28 | config.action_controller.perform_caching = false
29 |
30 | config.cache_store = :null_store
31 | end
32 |
33 | # Store uploaded files on the local file system (see config/storage.yml for options).
34 | config.active_storage.service = :local
35 |
36 | # Print deprecation notices to the Rails logger.
37 | config.active_support.deprecation = :log
38 |
39 | # Raise exceptions for disallowed deprecations.
40 | config.active_support.disallowed_deprecation = :raise
41 |
42 | # Tell Active Support which deprecation messages to disallow.
43 | config.active_support.disallowed_deprecation_warnings = []
44 |
45 | # Raise an error on page load if there are pending migrations.
46 | config.active_record.migration_error = :page_load
47 |
48 | # Highlight code that triggered database queries in logs.
49 | config.active_record.verbose_query_logs = true
50 |
51 | # Suppress logger output for asset requests.
52 | config.assets.quiet = true
53 |
54 | # Raises error for missing translations.
55 | # config.i18n.raise_on_missing_translations = true
56 |
57 | # Annotate rendered view with file names.
58 | # config.action_view.annotate_rendered_view_with_filenames = true
59 |
60 | # Uncomment if you wish to allow Action Cable access from any origin.
61 | # config.action_cable.disable_request_forgery_protection = true
62 | end
63 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
26 |
27 | # Compress CSS using a preprocessor.
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
34 | # config.asset_host = "http://assets.example.com"
35 |
36 | # Specifies the header that your server uses for sending files.
37 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
38 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
39 |
40 | # Store uploaded files on the local file system (see config/storage.yml for options).
41 | config.active_storage.service = :local
42 |
43 | # Mount Action Cable outside main process or domain.
44 | # config.action_cable.mount_path = nil
45 | # config.action_cable.url = "wss://example.com/cable"
46 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
47 |
48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Include generic and useful information about system operation, but avoid logging too much
52 | # information to avoid inadvertent exposure of personally identifiable information (PII).
53 | config.log_level = :info
54 |
55 | # Prepend all log lines with the following tags.
56 | config.log_tags = [ :request_id ]
57 |
58 | # Use a different cache store in production.
59 | # config.cache_store = :mem_cache_store
60 |
61 | # Use a real queuing backend for Active Job (and separate queues per environment).
62 | # config.active_job.queue_adapter = :resque
63 | # config.active_job.queue_name_prefix = "dummy_production"
64 |
65 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
66 | # the I18n.default_locale when a translation cannot be found).
67 | config.i18n.fallbacks = true
68 |
69 | # Don't log any deprecations.
70 | config.active_support.report_deprecations = false
71 |
72 | # Use default logging formatter so that PID and timestamp are not suppressed.
73 | config.log_formatter = ::Logger::Formatter.new
74 |
75 | # Use a different logger for distributed setups.
76 | # require "syslog/logger"
77 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
78 |
79 | if ENV["RAILS_LOG_TO_STDOUT"].present?
80 | logger = ActiveSupport::Logger.new(STDOUT)
81 | logger.formatter = config.log_formatter
82 | config.logger = ActiveSupport::TaggedLogging.new(logger)
83 | end
84 |
85 | # Do not dump schema after migrations.
86 | config.active_record.dump_schema_after_migration = false
87 |
88 | # Inserts middleware to perform automatic connection switching.
89 | # The `database_selector` hash is used to pass options to the DatabaseSelector
90 | # middleware. The `delay` is used to determine how long to wait after a write
91 | # to send a subsequent read to the primary.
92 | #
93 | # The `database_resolver` class is used by the middleware to determine which
94 | # database is appropriate to use based on the time delay.
95 | #
96 | # The `database_resolver_context` class is used by the middleware to set
97 | # timestamps for the last write to the primary. The resolver uses the context
98 | # class timestamps to determine how long to wait before reading from the
99 | # replica.
100 | #
101 | # By default Rails will store a last write timestamp in the session. The
102 | # DatabaseSelector middleware is designed as such you can define your own
103 | # strategy for connection switching and pass that into the middleware through
104 | # these configuration options.
105 | # config.active_record.database_selector = { delay: 2.seconds }
106 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
107 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
108 | end
109 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | # Turn false under Spring and add config.action_view.cache_template_loading = true
12 | config.cache_classes = true
13 |
14 | # Do not eager load code on boot. This avoids loading your whole application
15 | # just for the purpose of running a single test. If you are using a tool that
16 | # preloads Rails for running tests, you may have to set it to true.
17 | config.eager_load = false
18 |
19 | # Configure public file server for tests with Cache-Control for performance.
20 | config.public_file_server.enabled = true
21 | config.public_file_server.headers = {
22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}"
23 | }
24 |
25 | # Show full error reports and disable caching.
26 | config.consider_all_requests_local = true
27 | config.action_controller.perform_caching = false
28 | config.cache_store = :null_store
29 |
30 | # Raise exceptions instead of rendering exception templates.
31 | config.action_dispatch.show_exceptions = false
32 |
33 | # Disable request forgery protection in test environment.
34 | config.action_controller.allow_forgery_protection = false
35 |
36 | # Store uploaded files on the local file system in a temporary directory.
37 | config.active_storage.service = :test
38 |
39 | # Print deprecation notices to the stderr.
40 | config.active_support.deprecation = :stderr
41 |
42 | # Raise exceptions for disallowed deprecations.
43 | config.active_support.disallowed_deprecation = :raise
44 |
45 | # Tell Active Support which deprecation messages to disallow.
46 | config.active_support.disallowed_deprecation_warnings = []
47 |
48 | # Raises error for missing translations.
49 | # config.i18n.raise_on_missing_translations = true
50 |
51 | # Annotate rendered view with file names.
52 | # config.action_view.annotate_rendered_view_with_filenames = true
53 | end
54 |
--------------------------------------------------------------------------------
/test/dummy/config/importmap.rb:
--------------------------------------------------------------------------------
1 | # Pin npm packages by running ./bin/importmap
2 |
3 | pin "application", preload: true
4 | pin "@hotwired/turbo-rails", to: "turbo.js"
5 | # pin_all_from "app/javascript/controllers", under: "controllers"
6 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = "1.0"
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in the app/assets
11 | # folder are already added.
12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
13 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # :nocov:
4 |
5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
6 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
7 |
8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
9 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
10 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"]
11 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 | # # Specify URI for violation reports
15 | # # policy.report_uri "/csp-violation-report-endpoint"
16 | # end
17 |
18 | # If you are using UJS then enable automatic nonce generation
19 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
20 |
21 | # Set the nonce only to specific directives
22 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
23 |
24 | # Report CSP violations to a specified URI
25 | # For further information see the following documentation:
26 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
27 | # Rails.application.config.content_security_policy_report_only = true
28 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [
5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
6 | ]
7 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, "\\1en"
8 | # inflect.singular /^(ox)en/i, "\\1"
9 | # inflect.irregular "person", "people"
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym "RESTful"
16 | # end
17 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # :nocov:
7 |
8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
9 | ActiveSupport.on_load(:action_controller) do
10 | wrap_parameters format: [:json]
11 | end
12 |
13 | # To enable root element in JSON for ActiveRecord objects.
14 | # ActiveSupport.on_load(:active_record) do
15 | # self.include_root_in_json = true
16 | # end
17 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t "hello"
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t("hello") %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # "true": "foo"
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/test/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
12 | # terminating a worker in development environments.
13 | #
14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
15 |
16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
17 | #
18 | port ENV.fetch("PORT") { 3000 }
19 |
20 | # Specifies the `environment` that Puma will run in.
21 | #
22 | environment ENV.fetch("RAILS_ENV") { "development" }
23 |
24 | # Specifies the `pidfile` that Puma will use.
25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
26 |
27 | # Specifies the number of `workers` to boot in clustered mode.
28 | # Workers are forked web server processes. If using threads and workers together
29 | # the concurrency of the application would be max `threads` * `workers`.
30 | # Workers do not work on JRuby or Windows (both of which do not support
31 | # processes).
32 | #
33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
34 |
35 | # Use the `preload_app!` method when specifying a `workers` number.
36 | # This directive tells Puma to first boot the application and load code
37 | # before forking the application. This takes advantage of Copy On Write
38 | # process behavior so workers use less memory.
39 | #
40 | # preload_app!
41 |
42 | # Allow puma to be restarted by `bin/rails restart` command.
43 | plugin :tmp_restart
44 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | resources :foobars do
3 | member do
4 | put :update_action
5 | end
6 | end
7 |
8 | root "foobars#index"
9 | end
10 |
--------------------------------------------------------------------------------
/test/dummy/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket-<%= Rails.env %>
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket-<%= Rails.env %>
23 |
24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name-<%= Rails.env %>
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/test/dummy/config/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require('tailwindcss/defaultTheme')
2 |
3 | module.exports = {
4 | content: [
5 | './app/helpers/**/*.rb',
6 | './app/javascript/**/*.js',
7 | './app/views/**/*.{erb,html,slim}'
8 | ],
9 | theme: {
10 | extend: {
11 | fontFamily: {
12 | sans: ['Inter var', ...defaultTheme.fontFamily.sans],
13 | },
14 | },
15 | },
16 | plugins: [
17 | require('@tailwindcss/forms'),
18 | require('@tailwindcss/aspect-ratio'),
19 | require('@tailwindcss/typography'),
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20211110151849_create_foobars.rb:
--------------------------------------------------------------------------------
1 | class CreateFoobars < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :foobars do |t|
4 | t.text :my_redux
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20230314093029_create_another_foo_bars.rb:
--------------------------------------------------------------------------------
1 | class CreateAnotherFooBars < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :another_foo_bars do |t|
4 | t.text :another_redux_store
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # This file is the source Rails uses to define your schema when running `bin/rails
6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7 | # be faster and is potentially less error prone than running all of your
8 | # migrations from scratch. Old migrations may fail to apply correctly if those
9 | # migrations use external dependencies or application code.
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema[7.0].define(version: 2023_03_14_093029) do
14 | create_table "another_foo_bars", force: :cascade do |t|
15 | t.text "another_redux_store"
16 | t.datetime "created_at", null: false
17 | t.datetime "updated_at", null: false
18 | end
19 |
20 | create_table "foobars", force: :cascade do |t|
21 | t.text "my_redux"
22 | t.datetime "created_at", null: false
23 | t.datetime "updated_at", null: false
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/test/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/test/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/log/.keep
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/test/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/test/dummy/test/fixtures/another_foo_bars.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 | another_foo_bar_new:
3 | another_redux_store: "{\"initial_state\":{},\"head\":1,\"actions\":[{\"item\":\"Foo\"},{\"item\":\"Bar\"},{\"item\":\"Foo\"}],\"seq_id\":4,\"state\":{\"items\":[{\"id\":1,\"value\":\"Foo\",\"color\":\"green\"},{\"id\":2,\"value\":\"Bar\",\"color\":\"green\"}]}}"
4 |
--------------------------------------------------------------------------------
/test/dummy/test/fixtures/foobars.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 | foobar_new:
3 | my_redux: ''
4 |
5 | foobar_state:
6 | my_redux: "{\"initial_state\":{},\"head\":1,\"actions\":[{\"type\":\"add\",\"item\":\"Foo\"},{\"type\":\"add\",\"item\":\"Bar\"},{\"type\":\"add\",\"item\":\"Custom\"}],\"seq_id\":3,\"state\":{\"total\":2,\"items\":[{\"id\":1,\"value\":\"Foo\"},{\"id\":2,\"value\":\"Bar\"}]}}"
7 |
8 | foobar_initial:
9 | my_redux: "{\"initial_state\":{\"total\":1, \"items\":[{\"id\":1, \"value\":\"Foo\"}]},\"head\":1,\"actions\":[{\"type\":\"add\",\"item\":\"Bar\"},{\"type\":\"add\",\"item\":\"Custom\"}],\"seq_id\":4,\"state\":{\"total\":3,\"items\":[{\"id\":1,\"value\":\"Foo\"},{\"id\":2,\"value\":\"Bar\"},{\"id\":3,\"value\":\"Custom\"}]}}"
10 |
--------------------------------------------------------------------------------
/test/dummy/test/models/another_foo_bar_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class FooBarTestTest < ActiveSupport::TestCase
4 |
5 | setup do
6 | @another_foobar = another_foo_bars(:another_foo_bar_new)
7 | end
8 |
9 | test "can run after_dispatch method" do
10 | assert @another_foobar.dispatch!({ item: "Bar" })
11 | assert_equal @another_foobar.view_state[:items].last[:color], 'red'
12 | assert @another_foobar.dispatch!({ item: "Bar" })
13 | assert_equal @another_foobar.view_state[:items].last[:color], 'green'
14 | end
15 |
16 | test "can run after_dispatch after undo" do
17 | assert @another_foobar.undo!
18 | assert_equal @another_foobar.view_state[:items].last[:color], 'red'
19 | assert @another_foobar.undo!
20 | assert @another_foobar.view_state[:items].blank?
21 | end
22 |
23 | test "can run after_dispatch after redo" do
24 | assert @another_foobar.redo!
25 | assert_equal @another_foobar.view_state[:items].last[:color], 'red'
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/test/dummy/test/models/foobar_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | # Run some test on a new store
4 | class FoobarNewTest < ActiveSupport::TestCase
5 | setup do
6 | @foobar = foobars(:foobar_new)
7 | end
8 |
9 | test "should have a view_state" do
10 | assert @foobar.view_state.present?
11 | assert @foobar.view_state.key?("total")
12 | assert_equal @foobar.view_state["total"], 0
13 | assert @foobar.view_state.key?("items")
14 | assert_equal @foobar.view_state["items"], []
15 | end
16 |
17 |
18 | test "has nothing to undo" do
19 | assert !@foobar.undo?
20 | end
21 |
22 | test "has no undo action" do
23 | assert_nil @foobar.undo_action
24 | end
25 |
26 | test "can't undo" do
27 | assert !@foobar.undo!
28 | end
29 |
30 |
31 | test "has nothing to redo" do
32 | assert !@foobar.redo?
33 | end
34 |
35 | test "has no redo action" do
36 | assert_nil @foobar.redo_action
37 | end
38 |
39 | test "can't redo" do
40 | assert !@foobar.redo!
41 | end
42 |
43 |
44 | test "has nothing to flatten" do
45 | assert !@foobar.flatten?
46 | end
47 |
48 | test "can flatten" do
49 | assert @foobar.flatten!
50 | end
51 |
52 |
53 | test "can get the next sequence id" do
54 | assert_equal @foobar.seq_id, 0
55 | assert_equal @foobar.next_seq_id, 1
56 | end
57 |
58 |
59 | test "can dispatch an action" do
60 | assert @foobar.dispatch!({ type: "add", item: "Foo" })
61 | assert @foobar.reduce_valid?
62 | @foobar.save! # Make all keys strings
63 | assert_equal @foobar.my_redux["head"], 0
64 | assert_equal @foobar.my_redux["actions"].length, 1
65 | assert_equal @foobar.my_redux["actions"].first["type"], "add"
66 | assert_equal @foobar.my_redux["actions"].first["item"], "Foo"
67 | assert_equal @foobar.my_redux["seq_id"], 1
68 | end
69 |
70 |
71 | test "can signal a redux error" do
72 | assert !@foobar.dispatch!({ type: "remove", item: "" })
73 | assert !@foobar.reduce_valid?
74 | assert_equal @foobar.reduce_errors.details.length, 1
75 | assert_equal @foobar.reduce_errors.first.attribute, :base
76 | assert_equal @foobar.reduce_errors.first.type, :blank
77 | end
78 |
79 | test "can signal a redux error with a message" do
80 | assert !@foobar.dispatch!({ type: "add", item: "Too long" })
81 | assert !@foobar.reduce_valid?
82 | assert_equal @foobar.reduce_errors.full_messages.last, 'Item should have between 1 and 6 characters'
83 | end
84 | end
85 |
86 | # Run some tests on an existing store
87 | class FoobarStateTest < ActiveSupport::TestCase
88 | setup do
89 | @foobar = foobars(:foobar_state)
90 | end
91 |
92 | test "should have a view_state" do
93 | assert @foobar.view_state.present?
94 | assert @foobar.view_state.key?("total")
95 | assert_equal @foobar.view_state["total"], 2
96 | assert @foobar.view_state.key?("items")
97 | assert_equal @foobar.view_state["items"].length, 2
98 | end
99 |
100 |
101 | test "has something to undo" do
102 | assert @foobar.undo?
103 | end
104 |
105 | test "has an undo action" do
106 | assert_equal @foobar.undo_action["type"], "add"
107 | end
108 |
109 | test "can undo" do
110 | assert @foobar.undo!
111 | @foobar.save! # Make all keys strings
112 | assert_equal @foobar.my_redux["head"], 0
113 | assert_equal @foobar.my_redux["actions"].length, 3
114 | end
115 |
116 |
117 | test "has something to redo" do
118 | assert @foobar.redo?
119 | end
120 |
121 | test "has a redo action" do
122 | assert_equal @foobar.redo_action["type"], "add"
123 | end
124 |
125 | test "can redo" do
126 | assert @foobar.redo!
127 | @foobar.save! # Make all keys strings
128 | assert_equal @foobar.my_redux["head"], 2
129 | assert_equal @foobar.my_redux["actions"].length, 3
130 | assert_equal @foobar.my_redux["actions"].last["type"], "add"
131 | assert_equal @foobar.my_redux["actions"].last["item"], "Custom"
132 | assert_equal @foobar.my_redux["seq_id"], 4
133 | end
134 |
135 |
136 | test "has something to flatten" do
137 | assert @foobar.flatten?
138 | end
139 |
140 | test "can flatten" do
141 | assert @foobar.flatten!
142 | @foobar.save! # Make all keys strings
143 | assert_equal @foobar.my_redux["head"], -1
144 | assert_equal @foobar.my_redux["actions"].length, 0
145 | assert_equal @foobar.my_redux["initial_state"]["items"].length, 2
146 | end
147 |
148 | test "can flatten multiple times" do
149 | assert @foobar.flatten!
150 | assert @foobar.flatten!
151 | @foobar.save! # Make all keys strings
152 | assert_equal @foobar.my_redux["initial_state"]["items"].length, 2
153 | end
154 |
155 | test "can get the next sequence id" do
156 | assert_equal @foobar.seq_id, 3
157 | assert_equal @foobar.next_seq_id, 4
158 | end
159 |
160 |
161 | test "can dispatch an add action" do
162 | assert @foobar.dispatch!({ type: "add", item: "Foo" })
163 | @foobar.save! # Make all keys strings
164 | assert_equal @foobar.my_redux["head"], 2
165 | assert_equal @foobar.my_redux["actions"].length, 3
166 | assert_equal @foobar.my_redux["actions"].last["type"], "add"
167 | assert_equal @foobar.my_redux["actions"].last["item"], "Foo"
168 | assert_equal @foobar.my_redux["seq_id"], 4
169 |
170 | assert_equal @foobar.view_state["total"], 3
171 | assert_equal @foobar.view_state["items"].length, 3
172 | end
173 |
174 | test "can dispatch a remove action" do
175 | assert @foobar.dispatch!({ type: "remove", item: 1 })
176 | @foobar.save! # Make all keys strings
177 | assert_equal @foobar.my_redux["head"], 2
178 | assert_equal @foobar.my_redux["actions"].length, 3
179 | assert_equal @foobar.my_redux["actions"].last["type"], "remove"
180 | assert_equal @foobar.my_redux["actions"].last["item"], 1
181 | assert_equal @foobar.my_redux["seq_id"], 3
182 |
183 | assert_equal @foobar.view_state["total"], 1
184 | assert_equal @foobar.view_state["items"].length, 1
185 | end
186 |
187 | test "can dispatch an action after redo" do
188 | assert @foobar.redo!
189 | assert @foobar.dispatch!({ type: "add", item: "Foo" })
190 | @foobar.save! # Make all keys strings
191 | assert_equal @foobar.my_redux["head"], 3
192 | assert_equal @foobar.my_redux["actions"].length, 4
193 | assert_equal @foobar.my_redux["actions"].last["type"], "add"
194 | assert_equal @foobar.my_redux["actions"].last["item"], "Foo"
195 | assert_equal @foobar.my_redux["seq_id"], 5
196 | end
197 | end
198 |
199 | # Run tests on a store with an initial state
200 | class FoobarInitialTest < ActiveSupport::TestCase
201 | setup do
202 | @foobar = foobars(:foobar_initial)
203 | end
204 |
205 | test "has something to undo" do
206 | assert @foobar.undo?
207 | end
208 |
209 | test "can undo twice and keep initial state" do
210 | assert @foobar.undo!
211 | assert_equal @foobar.view_state["total"], 2
212 | assert_equal @foobar.view_state["items"].length, 2
213 |
214 | assert @foobar.undo!
215 | assert_equal @foobar.view_state["total"], 1
216 | assert_equal @foobar.view_state["items"].length, 1
217 |
218 | assert !@foobar.undo!
219 | assert_equal @foobar.view_state["total"], 1
220 | assert_equal @foobar.view_state["items"].length, 1
221 | end
222 | end
223 |
--------------------------------------------------------------------------------
/test/dummy/vendor/javascript/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/dummy/vendor/javascript/.keep
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/integration/.keep
--------------------------------------------------------------------------------
/test/integration/navigation_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class NavigationTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easydatawarehousing/rails_redhot/a8f75a9d721ce5124c12ce3b1870a781aa08a584/test/models/.keep
--------------------------------------------------------------------------------
/test/rails_redhot_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RailsRedhotTest < ActiveSupport::TestCase
4 | test "it has a version number" do
5 | assert RailsRedhot::VERSION
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # Configure Rails Environment
2 | ENV["RAILS_ENV"] = "test"
3 |
4 | require 'simplecov'
5 | SimpleCov.start { enable_coverage :branch }
6 |
7 | require_relative "../test/dummy/config/environment"
8 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
9 | require "rails/test_help"
10 |
11 |
12 | # Load fixtures from the engine
13 | if ActiveSupport::TestCase.respond_to?(:fixture_paths=)
14 | fixture_path = File.expand_path("dummy/test/fixtures", __dir__)
15 | ActiveSupport::TestCase.fixture_paths << fixture_path
16 | ActionDispatch::IntegrationTest.fixture_paths << fixture_path
17 | ActiveSupport::TestCase.fixture_paths << fixture_path + "/files"
18 | ActiveSupport::TestCase.fixtures :all
19 | end
20 |
--------------------------------------------------------------------------------