inline_forms 7.5.2 → 7.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +110 -0
- data/README.rdoc +10 -0
- data/app/assets/javascripts/inline_forms/inline_forms.js +3 -10
- data/app/assets/stylesheets/inline_forms/inline_forms.scss +5 -4
- data/app/controllers/concerns/versions_concern.rb +0 -2
- data/app/controllers/inline_forms_controller.rb +76 -21
- data/app/helpers/inline_forms_helper.rb +42 -28
- data/app/views/inline_forms/_close.html.erb +9 -7
- data/app/views/inline_forms/_edit.html.erb +1 -1
- data/app/views/inline_forms/_list.html.erb +6 -21
- data/app/views/inline_forms/_new.html.erb +3 -5
- data/app/views/inline_forms/_show.html.erb +0 -26
- data/app/views/inline_forms/_versions.html.erb +5 -4
- data/app/views/inline_forms/_versions_list.html.erb +8 -8
- data/app/views/inline_forms/row_close.html.erb +10 -2
- data/app/views/inline_forms/versions_panel.html.erb +5 -2
- data/app/views/layouts/application.html.erb +2 -4
- data/app/views/layouts/inline_forms.html.erb +2 -5
- data/archived/README.md +47 -0
- data/archived/form_elements/README.md +27 -0
- data/archived/form_elements/chicas/README.md +31 -0
- data/archived/form_elements/geo_code_curacao/README.md +62 -0
- data/{app → archived/form_elements/geo_code_curacao/app}/helpers/form_elements/geo_code_curacao.rb +0 -1
- data/archived/form_elements/geo_code_curacao/app/views/geo_code_curacao/list_streets.html.erb +1 -0
- data/archived/form_elements/geo_code_curacao/app/views/geo_code_curacao/list_streets.js.erb +1 -0
- data/archived/form_elements/kansen_slider/README.md +31 -0
- data/archived/form_elements/tree/README.md +47 -0
- data/archived/form_elements/tree/app/views/inline_forms/_show_tree.html.erb +30 -0
- data/{app → archived/form_elements/tree/app}/views/inline_forms/_tree.html.erb +18 -5
- data/bin/inline_forms_installer_core.rb +70 -5
- data/docs/ujs-to-turbo.md +36 -37
- data/lib/generators/assets/stylesheets/inline_forms.scss +5 -4
- data/lib/inline_forms/archived_form_elements.rb +70 -0
- data/lib/inline_forms/version.rb +1 -1
- data/lib/inline_forms.rb +3 -1
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb +6 -7
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_required_test.rb +21 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_photos_pagination_test.rb +28 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_row_turbo_test.rb +14 -5
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +30 -60
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_pagination_test.rb +40 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb +93 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_photo_revert_test.rb +94 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb +6 -9
- data/lib/installer_templates/example_app_tests/test/models/example_app_apartment_name_validation_test.rb +16 -0
- data/test/archived_form_elements_test.rb +41 -0
- data/test/form_element_from_callee_test.rb +2 -2
- metadata +25 -20
- data/app/views/geo_code_curacao/list_streets.html.erb +0 -1
- data/app/views/geo_code_curacao/list_streets.js.erb +0 -1
- data/app/views/inline_forms/close.js.erb +0 -4
- data/app/views/inline_forms/list.js.erb +0 -1
- data/app/views/inline_forms/new.js.erb +0 -1
- data/app/views/inline_forms/record_destroyed.js.erb +0 -1
- data/app/views/inline_forms/show.js.erb +0 -1
- data/app/views/inline_forms/show_undo.js.erb +0 -1
- data/app/views/inline_forms/versions.js.erb +0 -4
- data/app/views/inline_forms/versions_list.js.erb +0 -1
- /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_dropdown_with_family_members.rb +0 -0
- /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_family_photo_list.rb +0 -0
- /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_photo_list.rb +0 -0
- /data/{app → archived/form_elements/geo_code_curacao/app}/controllers/geo_code_curacao_controller.rb +0 -0
- /data/{app → archived/form_elements/geo_code_curacao/app}/models/geo_code_curacao.rb +0 -0
- /data/{app → archived/form_elements/kansen_slider/app}/helpers/form_elements/kansen_slider.rb +0 -0
- /data/{app → archived/form_elements/tree/app}/helpers/form_elements/move.rb +0 -0
|
@@ -57,7 +57,6 @@ gem 'rails-i18n', '~> 7.0'
|
|
|
57
57
|
gem 'rails-jquery-autocomplete'
|
|
58
58
|
gem 'rails', '~> 7.0.0'
|
|
59
59
|
gem 'rake'
|
|
60
|
-
gem 'remotipart', '~> 1.0'
|
|
61
60
|
gem 'rvm'
|
|
62
61
|
gem 'dartsass-rails'
|
|
63
62
|
# Rails 7 no longer adds sprockets-rails to the default Gemfile; declare it
|
|
@@ -66,10 +65,9 @@ gem 'dartsass-rails'
|
|
|
66
65
|
gem 'sprockets-rails'
|
|
67
66
|
# Rails 7 default JavaScript tooling: importmap-rails replaces Webpacker.
|
|
68
67
|
gem 'importmap-rails'
|
|
69
|
-
# Hotwire/Turbo. Loaded
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# format so future controllers can opt in to Turbo Stream responses.
|
|
68
|
+
# Hotwire/Turbo. Loaded from layouts as `<script type="module">`; inline flows
|
|
69
|
+
# use `<turbo-frame>` + HTML responses (see docs/ujs-to-turbo.md). Registers the
|
|
70
|
+
# `turbo_stream` MIME type for optional stream responses.
|
|
73
71
|
gem 'turbo-rails'
|
|
74
72
|
gem 'tabs_on_rails', :git => 'https://github.com/acesuares/tabs_on_rails.git', :branch => 'update_remote_before_action'
|
|
75
73
|
gem 'unicorn'
|
|
@@ -682,6 +680,73 @@ if ENV['install_example'] == 'true'
|
|
|
682
680
|
run 'bundle exec rails generate uploader Image'
|
|
683
681
|
run 'bundle exec rails g inline_forms Apartment name:string title:string description:rich_text photos:has_many photos:associated _enabled:yes _presentation:\'#{name}\''
|
|
684
682
|
|
|
683
|
+
say "- Apartment name is required..."
|
|
684
|
+
inject_into_file "app/models/apartment.rb",
|
|
685
|
+
"\n validates :name, presence: true\n",
|
|
686
|
+
after: " has_paper_trail\n"
|
|
687
|
+
|
|
688
|
+
# CarrierWave + PaperTrail history.
|
|
689
|
+
# PaperTrail snapshots the column scalar (the stored filename) on update,
|
|
690
|
+
# but CarrierWave's default `remove_previously_stored_files_after_update`
|
|
691
|
+
# deletes the old file on disk and re-uses the same filename, so a
|
|
692
|
+
# PaperTrail revert restores a filename whose bytes are gone.
|
|
693
|
+
# We keep every uploaded file on disk and namespace filenames with a
|
|
694
|
+
# per-upload token so successive uploads do not collide. See
|
|
695
|
+
# https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
|
|
696
|
+
# (Answers 2, 4 and 5).
|
|
697
|
+
say "- Configuring CarrierWave to keep previously stored files (PaperTrail history)..."
|
|
698
|
+
create_file "config/initializers/carrierwave.rb", <<-CWINIT.strip_heredoc
|
|
699
|
+
# Keep previously stored files on disk so PaperTrail-driven restore
|
|
700
|
+
# actually returns the previous image bytes. See
|
|
701
|
+
# https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
|
|
702
|
+
# The per-uploader overrides in app/uploaders/image_uploader.rb
|
|
703
|
+
# complement this by giving every upload a unique on-disk filename
|
|
704
|
+
# and by no-op'ing `remove!` so destroyed records keep their files.
|
|
705
|
+
CarrierWave.configure do |config|
|
|
706
|
+
config.remove_previously_stored_files_after_update = false
|
|
707
|
+
end
|
|
708
|
+
CWINIT
|
|
709
|
+
|
|
710
|
+
inject_into_file "app/uploaders/image_uploader.rb",
|
|
711
|
+
after: "class ImageUploader < CarrierWave::Uploader::Base\n" do
|
|
712
|
+
<<-RUBY.strip_heredoc.gsub(/^/, " ")
|
|
713
|
+
# PaperTrail history support. CarrierWave's default behaviour wipes the
|
|
714
|
+
# previous file on update and reuses the same filename; PaperTrail only
|
|
715
|
+
# stores the column scalar, so a plain `version.reify; save!` restores a
|
|
716
|
+
# filename whose bytes are gone. The knobs below preserve every byte:
|
|
717
|
+
#
|
|
718
|
+
# * `remove_previously_stored_files_after_update = false` is set
|
|
719
|
+
# globally in config/initializers/carrierwave.rb (covers
|
|
720
|
+
# `multi_image_field` uploaders too).
|
|
721
|
+
# * `remove!` is a no-op so hard-destroyed records keep their files
|
|
722
|
+
# and revert-after-destroy still finds the bytes on disk.
|
|
723
|
+
# * `filename` is prefixed with a per-upload UUID so successive
|
|
724
|
+
# uploads never collide on disk.
|
|
725
|
+
#
|
|
726
|
+
# Trade-off: files accumulate on disk; sweeping is out of scope.
|
|
727
|
+
# Source: https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
|
|
728
|
+
def remove!
|
|
729
|
+
# no-op: keep the file so PaperTrail revert can restore it.
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def filename
|
|
733
|
+
# CarrierWave 3.x calls `filename` again after storing to record the
|
|
734
|
+
# persisted name; at that point `original_filename` may be nil and we
|
|
735
|
+
# must still return the memoized name (see
|
|
736
|
+
# https://github.com/carrierwaveuploader/carrierwave/issues/2708).
|
|
737
|
+
@name ||= "\#{secure_token}-\#{original_filename}" if original_filename
|
|
738
|
+
@name
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
private
|
|
742
|
+
|
|
743
|
+
def secure_token
|
|
744
|
+
var = :"@\#{mounted_as}_secure_token"
|
|
745
|
+
model.instance_variable_get(var) || model.instance_variable_set(var, SecureRandom.uuid)
|
|
746
|
+
end
|
|
747
|
+
RUBY
|
|
748
|
+
end
|
|
749
|
+
|
|
685
750
|
say "- Lower Photo.per_page so the seeded gallery paginates..."
|
|
686
751
|
# The model template (lib/generators/templates/model.erb) emits
|
|
687
752
|
# attr_reader :per_page
|
data/docs/ujs-to-turbo.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Track progress toward full Turbo integration and removal of jQuery UJS from inline_forms generated apps.
|
|
4
4
|
|
|
5
|
-
**Current gem version:** see `lib/inline_forms/version.rb` (Step
|
|
5
|
+
**Current gem version:** see `lib/inline_forms/version.rb` (**Step 5** — Turbo Drive on, jquery-ujs / remotipart removed in **7.8.0**)
|
|
6
6
|
|
|
7
|
-
**Architecture today:**
|
|
7
|
+
**Architecture today:** inline interactions use **`<turbo-frame>`** + **`format.html`** (and optional **`format.turbo_stream`**). Turbo loads as an ES module from the layouts; **Turbo Drive** uses the library default (**enabled**). No `*.js.erb` in active engine views; no `jquery_ujs` or `jquery.remotipart` in the Sprockets bundle.
|
|
8
8
|
|
|
9
|
-
**Target end state:** Turbo Frames
|
|
9
|
+
**Target end state:** Stock flows use Turbo Frames + HTML; optional `turbo_stream` where it simplifies a response. jQuery UJS and `*.js.erb` are gone from the engine.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ Track progress toward full Turbo integration and removal of jQuery UJS from inli
|
|
|
14
14
|
|
|
15
15
|
- [x] `gem 'turbo-rails'` in installer Gemfile (`bin/inline_forms_installer_core.rb`)
|
|
16
16
|
- [x] Turbo loaded as `<script type="module">` in `layouts/inline_forms.html.erb` and `layouts/application.html.erb`
|
|
17
|
-
- [x] `Turbo.session.drive
|
|
17
|
+
- [x] `Turbo.session.drive` uses the **default (enabled)** — **7.8.0** (was disabled while UJS coexisted)
|
|
18
18
|
- [x] Turbo **not** in Sprockets bundle (`app/assets/javascripts/inline_forms/inline_forms.js`) — ESM parse-error lesson from 7.1.1
|
|
19
19
|
- [x] Smoke test: `lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb`
|
|
20
20
|
|
|
@@ -47,12 +47,12 @@ Convert the **`show → edit → update → show_element → close`** cycle with
|
|
|
47
47
|
|
|
48
48
|
- [x] Wrap each top-level list row in `<turbo-frame id="apartment_<id>">` (`_list.html.erb` when `parent_class` is nil)
|
|
49
49
|
- [x] Row title link: GET `show` → HTML `row_show` / `_show` inside frame (no `:remote`; `data-turbo` + `data-turbo-frame`)
|
|
50
|
-
- [x] `InlineFormsController#show` (full record, no `params[:attribute]`): `format.html` → `row_show` / `row_close` + `turbo_rails/frame` when `turbo_frame_request?`, else full `inline_forms` layout
|
|
50
|
+
- [x] `InlineFormsController#show` (full record, no `params[:attribute]`): `format.html` → `row_show` / `row_close` + `turbo_rails/frame` when `turbo_frame_request?`, else full `inline_forms` layout
|
|
51
51
|
- [x] `close_link` and `_close` presentation link: Turbo when `@inline_forms_turbo_row` (row HTML path)
|
|
52
|
-
- [x] `soft_delete`, `soft_restore`, `destroy`, `revert`: `format.html` → `row_close` / `row_destroyed` (+ Turbo toolbar links)
|
|
52
|
+
- [x] `soft_delete`, `soft_restore`, `destroy`, `revert`: `format.html` → `row_close` / `row_destroyed` (+ Turbo toolbar links)
|
|
53
53
|
- [x] Remove `edit.js.erb`, `update.js.erb`, `show_element.js.erb` (field lifecycle is Turbo HTML only)
|
|
54
|
-
- [
|
|
55
|
-
- [x] Remove `:remote => true` from nested `_list` / `_close` / toolbar where migrated (row toolbar, versions, nested `+` use Turbo;
|
|
54
|
+
- [x] Remove `show.js.erb`, `close.js.erb`, `record_destroyed.js.erb`, `show_undo.js.erb` (7.7.0; tree migrated)
|
|
55
|
+
- [x] Remove `:remote => true` from nested `_list` / `_close` / toolbar where migrated (row toolbar, versions, nested `+` use Turbo; top-level `new` was Step 4)
|
|
56
56
|
- [x] Remove `data-turbo="false"` from nested rows once nested inline-edit is Turbo-native (7.4.2)
|
|
57
57
|
|
|
58
58
|
### Nested associated lists (e.g. Apartment → Photo)
|
|
@@ -77,7 +77,7 @@ Convert the **`show → edit → update → show_element → close`** cycle with
|
|
|
77
77
|
|
|
78
78
|
### Helpers (`app/helpers/inline_forms_helper.rb`)
|
|
79
79
|
|
|
80
|
-
- [x] `close_link`, `link_to_soft_delete`, `link_to_destroy`, `link_to_new_record`, `link_to_versions_list`, `close_versions_list_link`: Turbo
|
|
80
|
+
- [x] `close_link`, `link_to_soft_delete`, `link_to_destroy`, `link_to_new_record`, `link_to_versions_list`, `close_versions_list_link`: Turbo frame attrs only (**7.8.0**; `turbo_row:` retained for API compat)
|
|
81
81
|
|
|
82
82
|
### Tests
|
|
83
83
|
|
|
@@ -96,49 +96,48 @@ Convert the **`show → edit → update → show_element → close`** cycle with
|
|
|
96
96
|
|
|
97
97
|
### Lists and create flow
|
|
98
98
|
|
|
99
|
-
- [
|
|
100
|
-
- [
|
|
101
|
-
- [
|
|
102
|
-
- [
|
|
99
|
+
- [x] Top-level index: wrap in `<turbo-frame id="apartments_list">`; in-frame pagination (7.7.0)
|
|
100
|
+
- [x] `new` / `create`: frame refresh replacing list container (7.7.0)
|
|
101
|
+
- [x] Remove `new.js.erb`, `list.js.erb` (7.7.0)
|
|
102
|
+
- [x] `_new.html.erb`: Turbo path when `@turbo_frame` (no `:remote` on cancel)
|
|
103
103
|
|
|
104
104
|
### Tree
|
|
105
105
|
|
|
106
|
-
- [
|
|
107
|
-
- [ ] Remove tree-related UJS if any dedicated `*.js.erb` remain
|
|
106
|
+
- [x] ~~`:tree` / `_tree.html.erb`~~ — **archived in 7.7.0** (`archived/form_elements/tree/`); needs host tree gem/APIs. Turbo-ready partial kept in archive.
|
|
108
107
|
|
|
109
108
|
### Versions panel
|
|
110
109
|
|
|
111
|
-
- [
|
|
112
|
-
- [
|
|
113
|
-
- [
|
|
110
|
+
- [x] `VersionsConcern#list_versions`: HTML frame (`versions_panel` / `versions_list_panel`, `turbo_rails/frame` layout) — **7.7.3** drops `format.js`
|
|
111
|
+
- [x] Remove `versions.js.erb`, `versions_list.js.erb` (**7.7.3**)
|
|
112
|
+
- [x] `_versions_list.html.erb`: revert uses `inline_forms_turbo_link_data` (row frame), not `:remote => true`
|
|
114
113
|
|
|
115
114
|
### Geo / misc
|
|
116
115
|
|
|
117
|
-
- [
|
|
116
|
+
- [x] ~~`geo_code_curacao`~~ — **archived in 7.6.0** (`archived/form_elements/geo_code_curacao/`). If restored, migrate `list_streets.js.erb` to frame or stream.
|
|
118
117
|
|
|
119
118
|
### Controller cleanup
|
|
120
119
|
|
|
121
|
-
- [
|
|
122
|
-
- [ ] Audit `respond_to` blocks: parallel `format.turbo_stream` where stream is cleaner than full frame
|
|
120
|
+
- [x] Every action in `InlineFormsController` + `VersionsConcern` has a non-JS response path (**7.8.0** audit)
|
|
121
|
+
- [ ] Audit `respond_to` blocks: parallel `format.turbo_stream` where stream is cleaner than full frame (optional)
|
|
123
122
|
|
|
124
123
|
### Tests
|
|
125
124
|
|
|
126
|
-
- [
|
|
127
|
-
- [
|
|
128
|
-
- [
|
|
129
|
-
- [
|
|
125
|
+
- [x] Top-level list pagination in frame (`example_app_apartment_top_level_pagination_test.rb`)
|
|
126
|
+
- [x] Create apartment → list frame updates (`example_app_apartment_top_level_new_test.rb`)
|
|
127
|
+
- [x] Versions panel open/close + revert from list (`example_app_apartment_versions_turbo_test.rb`, **7.7.3**)
|
|
128
|
+
- [x] Assert zero `data-remote="true"` in rendered HTML for inline_forms flows (helpers + `_new` / `_close` no longer fall back to UJS — **7.8.0**)
|
|
130
129
|
|
|
131
130
|
---
|
|
132
131
|
|
|
133
|
-
## Step 5 — Enable Drive, remove UJS
|
|
132
|
+
## Step 5 — Enable Drive, remove UJS (DONE in **7.8.0**)
|
|
134
133
|
|
|
135
|
-
- [
|
|
136
|
-
- [
|
|
137
|
-
- [
|
|
138
|
-
- [
|
|
139
|
-
- [
|
|
140
|
-
- [
|
|
141
|
-
- [
|
|
134
|
+
- [x] Remove `Turbo.session.drive = false` from both layouts (Drive default **on**)
|
|
135
|
+
- [x] Delete `//= require jquery_ujs` from `inline_forms.js`
|
|
136
|
+
- [x] Delete `//= require jquery.remotipart` from `inline_forms.js`
|
|
137
|
+
- [x] Delete all `app/views/inline_forms/*.js.erb` (completed by **7.7.3**)
|
|
138
|
+
- [x] Remove `format.js` branches from controllers (none remain on stock actions)
|
|
139
|
+
- [x] Update `example_app_turbo_layout_test.rb`: refute `Turbo.session.drive = false`
|
|
140
|
+
- [x] Full `bundle exec rails test` in `--example` app (before release)
|
|
142
141
|
|
|
143
142
|
### jQuery (optional follow-up — not required to drop UJS)
|
|
144
143
|
|
|
@@ -166,13 +165,13 @@ These can remain while UJS is gone; separate migration if desired:
|
|
|
166
165
|
| `close.js.erb` | Fade + `_close` partial |
|
|
167
166
|
| `record_destroyed.js.erb` | Fade out row |
|
|
168
167
|
| `show_undo.js.erb` | Undo link after destroy |
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
168
|
+
| ~~`versions.js.erb`~~ | Removed **7.7.3** — versions panel HTML frame |
|
|
169
|
+
| ~~`versions_list.js.erb`~~ | Removed **7.7.3** — versions list HTML frame |
|
|
170
|
+
| ~~`geo_code_curacao/list_streets.js.erb`~~ | Archived 7.6.0 — street autocomplete JSON |
|
|
172
171
|
|
|
173
172
|
### Controller actions still on `format.js`
|
|
174
173
|
|
|
175
|
-
|
|
174
|
+
None on `InlineFormsController` / `VersionsConcern` stock actions (**7.8.0**). Host app controllers may still use `format.js`.
|
|
176
175
|
|
|
177
176
|
### Key files
|
|
178
177
|
|
|
@@ -224,13 +224,14 @@ select:hover, select:focus {
|
|
|
224
224
|
font-size: 110%;
|
|
225
225
|
}
|
|
226
226
|
// Custom elements default to `display: inline`, which collapses the row layout
|
|
227
|
-
// of
|
|
228
|
-
// hides its rows under the fixed top bar. Force block layout so frames behave
|
|
229
|
-
// like the legacy `<div class="list_container">` and `<div class="row">` they
|
|
230
|
-
// replaced (see app/views/inline_forms/_list.html.erb).
|
|
227
|
+
// of a top-level `<turbo-frame id="apartments_list">` inside `#outer_container`.
|
|
231
228
|
turbo-frame {
|
|
232
229
|
display: block;
|
|
233
230
|
}
|
|
231
|
+
#outer_container > turbo-frame.list_container {
|
|
232
|
+
display: block;
|
|
233
|
+
width: 100%;
|
|
234
|
+
}
|
|
234
235
|
.list_container {
|
|
235
236
|
.row {
|
|
236
237
|
font-size: 1.2rem;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
module InlineForms
|
|
3
|
+
class ArchivedFormElementError < StandardError; end
|
|
4
|
+
|
|
5
|
+
# Retired form-element symbols. Full source for entries with +archive_path+ lives
|
|
6
|
+
# under archived/form_elements/<name>/ — see archived/README.md.
|
|
7
|
+
ARCHIVED_FORM_ELEMENTS = {
|
|
8
|
+
geo_code_curacao: {
|
|
9
|
+
archived_in_version: "7.6.0",
|
|
10
|
+
archive_path: "archived/form_elements/geo_code_curacao",
|
|
11
|
+
summary: "Curaçao street geocode (MySQL Zones/Buurten/Straatcode, jQuery autocomplete, UJS list_streets).",
|
|
12
|
+
},
|
|
13
|
+
chicas_photo_list: {
|
|
14
|
+
archived_in_version: "7.6.0",
|
|
15
|
+
archive_path: "archived/form_elements/chicas",
|
|
16
|
+
summary: "Chicas app read-only member photo gallery (show-only).",
|
|
17
|
+
},
|
|
18
|
+
chicas_family_photo_list: {
|
|
19
|
+
archived_in_version: "7.6.0",
|
|
20
|
+
archive_path: "archived/form_elements/chicas",
|
|
21
|
+
summary: "Chicas app read-only family member photo gallery (show-only).",
|
|
22
|
+
},
|
|
23
|
+
chicas_dropdown_with_family_members: {
|
|
24
|
+
archived_in_version: "7.6.0",
|
|
25
|
+
archive_path: "archived/form_elements/chicas",
|
|
26
|
+
summary: "Chicas client picker via family.clients; moves CarrierWave upload dir on update.",
|
|
27
|
+
},
|
|
28
|
+
kansen_slider: {
|
|
29
|
+
archived_in_version: "7.6.0",
|
|
30
|
+
archive_path: "archived/form_elements/kansen_slider",
|
|
31
|
+
summary: "jQuery UI slider for integer-coded chance scale; uses model attribute_values.",
|
|
32
|
+
},
|
|
33
|
+
tree: {
|
|
34
|
+
archived_in_version: "7.7.0",
|
|
35
|
+
archive_path: "archived/form_elements/tree",
|
|
36
|
+
summary: "Self-referential children list via parent.children; requires host tree APIs (see README).",
|
|
37
|
+
},
|
|
38
|
+
move: {
|
|
39
|
+
archived_in_version: "7.7.0",
|
|
40
|
+
archive_path: "archived/form_elements/tree",
|
|
41
|
+
summary: "Reparent via hash_tree_to_collection + add_child (host must implement; pairs with :tree).",
|
|
42
|
+
},
|
|
43
|
+
absence_list: {
|
|
44
|
+
removed_in_version: "6.3.0",
|
|
45
|
+
archive_path: nil,
|
|
46
|
+
summary: "Project-specific absence list UI; removed without a copy in this repo (see CHANGELOG 6.3.0).",
|
|
47
|
+
},
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
def self.validate_no_archived_form_elements_for!(klass)
|
|
51
|
+
return unless klass.instance_methods.include?(:inline_forms_attribute_list)
|
|
52
|
+
|
|
53
|
+
klass.new.inline_forms_attribute_list.each do |attribute, _label, form_element|
|
|
54
|
+
key = form_element.to_sym
|
|
55
|
+
next unless ARCHIVED_FORM_ELEMENTS.key?(key)
|
|
56
|
+
|
|
57
|
+
meta = ARCHIVED_FORM_ELEMENTS[key]
|
|
58
|
+
version = meta[:archived_in_version] || meta[:removed_in_version]
|
|
59
|
+
hint = if meta[:archive_path]
|
|
60
|
+
"Restore from #{meta[:archive_path]}/README.md or vendor into your app."
|
|
61
|
+
else
|
|
62
|
+
"See archived/README.md and CHANGELOG #{version}."
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
raise ArchivedFormElementError,
|
|
66
|
+
"#{klass.name} inline_forms_attribute_list declares #{attribute}:#{form_element}, " \
|
|
67
|
+
"which was retired in inline_forms #{version} (#{meta[:summary]}). #{hint}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/inline_forms/version.rb
CHANGED
data/lib/inline_forms.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
|
2
2
|
require ('inline_forms/version.rb')
|
|
3
3
|
require_relative ('inline_forms/form_element_from_callee')
|
|
4
|
+
require_relative ('inline_forms/archived_form_elements')
|
|
4
5
|
# InlineForms is a Rails Engine that let you setup an admin interface quick and
|
|
5
6
|
# easy. Please install it as a gem or include it in your Gemfile.
|
|
6
7
|
module InlineForms
|
|
@@ -204,8 +205,9 @@ module InlineForms
|
|
|
204
205
|
|
|
205
206
|
ActiveRecord::Base.descendants.each do |klass|
|
|
206
207
|
begin
|
|
208
|
+
InlineForms.validate_no_archived_form_elements_for!(klass)
|
|
207
209
|
InlineForms.validate_plain_text_configuration_for!(klass)
|
|
208
|
-
rescue InlineForms::PlainTextColumnMissingError
|
|
210
|
+
rescue InlineForms::PlainTextColumnMissingError, InlineForms::ArchivedFormElementError
|
|
209
211
|
raise
|
|
210
212
|
rescue StandardError
|
|
211
213
|
# Some descendants might be abstract or temporarily unresolved while
|
|
@@ -13,16 +13,15 @@ class ExampleAppApartmentFieldTurboTest < ExampleAppIntegrationTestCase
|
|
|
13
13
|
@turbo_headers = { "Turbo-Frame" => @frame_id }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
test "show panel
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"X-Requested-With" => "XMLHttpRequest"
|
|
21
|
-
}
|
|
16
|
+
test "show panel opens as HTML inside row turbo-frame" do
|
|
17
|
+
row_frame = "apartment_#{@apartment.id}"
|
|
18
|
+
get apartment_path(@apartment, update: row_frame),
|
|
19
|
+
headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
|
|
22
20
|
|
|
23
21
|
assert_response :success
|
|
22
|
+
assert_includes @response.body, %(<turbo-frame id="#{row_frame}">)
|
|
24
23
|
assert_includes @response.body, "turbo-frame",
|
|
25
|
-
"
|
|
24
|
+
"row show should embed _show with per-field turbo-frame wrappers"
|
|
26
25
|
assert_includes @response.body, @frame_id
|
|
27
26
|
end
|
|
28
27
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../example_app/example_integration_test_case"
|
|
4
|
+
|
|
5
|
+
class ExampleAppApartmentNameRequiredTest < ExampleAppIntegrationTestCase
|
|
6
|
+
setup do
|
|
7
|
+
@frame = "apartments_list"
|
|
8
|
+
@frame_headers = { "Turbo-Frame" => @frame, "Accept" => "text/html" }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test "top-level create without name does not persist" do
|
|
12
|
+
assert_no_difference("Apartment.count") do
|
|
13
|
+
post apartments_path(update: @frame),
|
|
14
|
+
params: { title: "Missing name" },
|
|
15
|
+
headers: @frame_headers
|
|
16
|
+
end
|
|
17
|
+
assert_response :success
|
|
18
|
+
assert_includes @response.body, 'name="name"'
|
|
19
|
+
assert_includes @response.body, 'class="edit_form"'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -220,6 +220,34 @@ class ExampleAppApartmentPhotosPaginationTest < ExampleAppIntegrationTestCase
|
|
|
220
220
|
assert_select %(turbo-frame##{sample_row_id} a[data-turbo='true'][data-turbo-frame='#{sample_row_id}']), minimum: 1
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
+
test "nested Photo versions restore targets nested row turbo-frame not bare photo id" do
|
|
224
|
+
photo = @apartment.photos.first!
|
|
225
|
+
original_name = photo.name
|
|
226
|
+
photo.update!(name: "#{original_name}-changed")
|
|
227
|
+
row_id = "apartment_#{@apartment.id}_photo_#{photo.id}"
|
|
228
|
+
versions_frame = "photo_#{photo.id}_versions"
|
|
229
|
+
version = photo.versions.where(event: "update").order(:id).last
|
|
230
|
+
assert version, "expected an update version to revert"
|
|
231
|
+
|
|
232
|
+
get list_versions_photo_path(photo, update: versions_frame),
|
|
233
|
+
headers: { "Turbo-Frame" => versions_frame, "Accept" => "text/html" }
|
|
234
|
+
assert_response :success
|
|
235
|
+
assert_includes @response.body, "data-turbo-frame=\"#{row_id}\"",
|
|
236
|
+
"restore must target the nested row frame (#{row_id}), not photo_#{photo.id}"
|
|
237
|
+
|
|
238
|
+
# 7.9.0: revert always responds with turbo-stream (the legacy
|
|
239
|
+
# `format.html` fallback was dropped).
|
|
240
|
+
post revert_photo_path(version.id, update: row_id),
|
|
241
|
+
headers: {
|
|
242
|
+
"Turbo-Frame" => versions_frame,
|
|
243
|
+
"Accept" => "text/vnd.turbo-stream.html"
|
|
244
|
+
}
|
|
245
|
+
assert_response :success
|
|
246
|
+
assert_includes @response.body, %(action="replace")
|
|
247
|
+
assert_includes @response.body, %(target="#{row_id}")
|
|
248
|
+
assert_equal original_name, photo.reload.name
|
|
249
|
+
end
|
|
250
|
+
|
|
223
251
|
test "nested Photo row opens and closes via Turbo HTML (not_accessible_through_html model)" do
|
|
224
252
|
photo = @apartment.photos.first!
|
|
225
253
|
row_id = "apartment_#{@apartment.id}_photo_#{photo.id}"
|
|
@@ -79,16 +79,25 @@ class ExampleAppApartmentRowTurboTest < ExampleAppIntegrationTestCase
|
|
|
79
79
|
doomed = Apartment.create!(name: "Turbo Revert Me", title: "Y")
|
|
80
80
|
apt_id = doomed.id
|
|
81
81
|
frame = "apartment_#{apt_id}"
|
|
82
|
-
|
|
82
|
+
delete_headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
|
|
83
83
|
|
|
84
|
-
delete apartment_path(doomed, update: frame), headers:
|
|
84
|
+
delete apartment_path(doomed, update: frame), headers: delete_headers
|
|
85
85
|
assert_response :success
|
|
86
86
|
|
|
87
|
+
# 7.9.0 dropped the `format.html` fallback in `revert`; restore links
|
|
88
|
+
# always request a turbo-stream now (the response replaces both the
|
|
89
|
+
# row and the versions panel in one stream).
|
|
90
|
+
versions_frame = "#{frame}_versions"
|
|
87
91
|
destroy_version = PaperTrail::Version.where(item_type: "Apartment", item_id: apt_id).order(:id).last
|
|
88
|
-
post revert_apartment_path(destroy_version.id, update: frame),
|
|
92
|
+
post revert_apartment_path(destroy_version.id, update: frame),
|
|
93
|
+
headers: {
|
|
94
|
+
"Turbo-Frame" => versions_frame,
|
|
95
|
+
"Accept" => "text/vnd.turbo-stream.html"
|
|
96
|
+
}
|
|
89
97
|
assert_response :success
|
|
90
98
|
assert Apartment.where(name: "Turbo Revert Me").exists?
|
|
91
|
-
assert_includes @response.body, %(
|
|
92
|
-
|
|
99
|
+
assert_includes @response.body, %(action="replace")
|
|
100
|
+
assert_includes @response.body, %(target="#{frame}")
|
|
101
|
+
assert_includes @response.body, %(target="#{versions_frame}")
|
|
93
102
|
end
|
|
94
103
|
end
|
|
@@ -2,99 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../example_app/example_integration_test_case"
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
# (
|
|
7
|
-
# on the +"+ new"+ link, but the index page wraps the list in a plain
|
|
8
|
-
# +<div id="apartments_list">+ (not a +<turbo-frame>+) -- so:
|
|
9
|
-
#
|
|
10
|
-
# * +cancel+ / +create+ targeted a frame the page did not have, and Turbo
|
|
11
|
-
# logged "Content missing" and dropped the response;
|
|
12
|
-
# * the +"+ new"+ click itself either fell back to a full-page navigation or
|
|
13
|
-
# landed in that broken state.
|
|
14
|
-
#
|
|
15
|
-
# Wrapping the top-level list in a real +<turbo-frame>+ regresses layout
|
|
16
|
-
# (the frame collapses inside +position: absolute+ +#outer_container+), so
|
|
17
|
-
# the fix keeps top-level behind UJS: +link_to_new_record+ omits Turbo data
|
|
18
|
-
# attributes when no +parent_class+ is provided, and +new.js.erb+ /
|
|
19
|
-
# +list.js.erb+ swap +#apartments_list+ contents in place.
|
|
5
|
+
# Step 4: top-level `/apartments` list, +new, cancel, and create use Turbo Frames
|
|
6
|
+
# (`<turbo-frame id="apartments_list">`) instead of UJS + new.js.erb / list.js.erb.
|
|
20
7
|
class ExampleAppApartmentTopLevelNewTest < ExampleAppIntegrationTestCase
|
|
21
8
|
setup do
|
|
22
9
|
@frame = "apartments_list"
|
|
10
|
+
@frame_headers = { "Turbo-Frame" => @frame, "Accept" => "text/html" }
|
|
11
|
+
@apartment = Apartment.find_or_create_by!(name: "Top Level List Apt") do |a|
|
|
12
|
+
a.title = "Top level list seed"
|
|
13
|
+
end
|
|
23
14
|
end
|
|
24
15
|
|
|
25
|
-
test "top-level apartments index
|
|
16
|
+
test "top-level apartments index wraps list in turbo-frame" do
|
|
26
17
|
get apartments_path
|
|
27
18
|
assert_response :success
|
|
28
19
|
assert_match(
|
|
29
|
-
%r{<
|
|
30
|
-
@response.body
|
|
31
|
-
"top-level list must stay a <div id=\"apartments_list\"> for UJS swaps; " \
|
|
32
|
-
"wrapping in a <turbo-frame> at this position breaks layout"
|
|
20
|
+
%r{<turbo-frame[^>]+id="#{Regexp.escape(@frame)}"[^>]*class="list_container"},
|
|
21
|
+
@response.body
|
|
33
22
|
)
|
|
34
23
|
refute_match(
|
|
35
|
-
%r{<
|
|
24
|
+
%r{<div[^>]+id="#{Regexp.escape(@frame)}"[^>]*class="list_container"},
|
|
36
25
|
@response.body,
|
|
37
|
-
"
|
|
38
|
-
"layout (frame collapses under fixed top bars) and orphans cancel/create"
|
|
26
|
+
"list root must be turbo-frame, not legacy div"
|
|
39
27
|
)
|
|
40
28
|
end
|
|
41
29
|
|
|
42
|
-
test "top-level + new link uses UJS
|
|
30
|
+
test "top-level + new link uses Turbo not UJS remote" do
|
|
43
31
|
get apartments_path
|
|
44
32
|
assert_response :success
|
|
45
33
|
assert_match(
|
|
46
|
-
%r{<a [^>]*class="button new_button"[^>]*data-
|
|
47
|
-
@response.body
|
|
48
|
-
"top-level + must POST via UJS (no <turbo-frame> on the page to target); " \
|
|
49
|
-
"data-turbo* on this link causes 'Content missing' on cancel/create"
|
|
34
|
+
%r{<a [^>]*class="button new_button"[^>]*data-turbo="true"[^>]*data-turbo-frame="#{Regexp.escape(@frame)}"[^>]*href="/apartments/new\?update=apartments_list"},
|
|
35
|
+
@response.body
|
|
50
36
|
)
|
|
51
37
|
refute_match(
|
|
52
|
-
%r{<a [^>]*class="button new_button"[^>]*data-
|
|
53
|
-
@response.body
|
|
54
|
-
"top-level + must NOT carry data-turbo-frame=apartments_list (no matching frame)"
|
|
38
|
+
%r{<a [^>]*class="button new_button"[^>]*data-remote="true"},
|
|
39
|
+
@response.body
|
|
55
40
|
)
|
|
56
41
|
end
|
|
57
42
|
|
|
58
|
-
test "top-level new returns
|
|
59
|
-
get new_apartment_path(update: @frame),
|
|
43
|
+
test "top-level new returns new form inside matching turbo-frame" do
|
|
44
|
+
get new_apartment_path(update: @frame), headers: @frame_headers
|
|
60
45
|
assert_response :success
|
|
61
|
-
assert_includes @response.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@response.body,
|
|
66
|
-
"UJS new.js.erb must swap #apartments_list with the rendered form"
|
|
67
|
-
)
|
|
68
|
-
assert_includes @response.body, 'name=\"name\"'
|
|
69
|
-
assert_includes @response.body, 'class=\"edit_form\"'
|
|
46
|
+
assert_includes @response.body, %(<turbo-frame id="#{@frame}">)
|
|
47
|
+
assert_includes @response.body, 'name="name"'
|
|
48
|
+
assert_includes @response.body, 'class="edit_form"'
|
|
49
|
+
refute_includes @response.content_type.to_s, "javascript"
|
|
70
50
|
end
|
|
71
51
|
|
|
72
|
-
test "top-level cancel returns
|
|
73
|
-
get apartments_path(update: @frame, ul_needed: true),
|
|
52
|
+
test "top-level cancel returns list inside matching turbo-frame" do
|
|
53
|
+
get apartments_path(update: @frame, ul_needed: true), headers: @frame_headers
|
|
74
54
|
assert_response :success
|
|
75
|
-
assert_includes @response.
|
|
76
|
-
|
|
77
|
-
assert_match(
|
|
78
|
-
%r{\$\('#apartments_list'\)\.html\(},
|
|
79
|
-
@response.body,
|
|
80
|
-
"UJS list.js.erb must swap #apartments_list back to the list"
|
|
81
|
-
)
|
|
55
|
+
assert_includes @response.body, %(<turbo-frame id="#{@frame}")
|
|
56
|
+
assert_includes @response.body, "<turbo-frame id=\"apartment_"
|
|
82
57
|
end
|
|
83
58
|
|
|
84
|
-
test "top-level create via
|
|
59
|
+
test "top-level create via Turbo persists and returns the list frame" do
|
|
85
60
|
name = "TopLevelNewApt-#{SecureRandom.hex(4)}"
|
|
86
61
|
assert_difference("Apartment.count", 1) do
|
|
87
62
|
post apartments_path(update: @frame),
|
|
88
63
|
params: { name: name, title: "Top level new test" },
|
|
89
|
-
|
|
64
|
+
headers: @frame_headers
|
|
90
65
|
end
|
|
91
66
|
assert_response :success
|
|
92
|
-
assert_includes @response.
|
|
93
|
-
|
|
94
|
-
%r{\$\('#apartments_list'\)\.html\(},
|
|
95
|
-
@response.body
|
|
96
|
-
)
|
|
97
|
-
assert_includes @response.body, name,
|
|
98
|
-
"expected the newly-created Apartment to appear in the swapped list"
|
|
67
|
+
assert_includes @response.body, %(<turbo-frame id="#{@frame}">)
|
|
68
|
+
assert_includes @response.body, name
|
|
99
69
|
end
|
|
100
70
|
end
|