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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/README.rdoc +10 -0
  4. data/app/assets/javascripts/inline_forms/inline_forms.js +3 -10
  5. data/app/assets/stylesheets/inline_forms/inline_forms.scss +5 -4
  6. data/app/controllers/concerns/versions_concern.rb +0 -2
  7. data/app/controllers/inline_forms_controller.rb +76 -21
  8. data/app/helpers/inline_forms_helper.rb +42 -28
  9. data/app/views/inline_forms/_close.html.erb +9 -7
  10. data/app/views/inline_forms/_edit.html.erb +1 -1
  11. data/app/views/inline_forms/_list.html.erb +6 -21
  12. data/app/views/inline_forms/_new.html.erb +3 -5
  13. data/app/views/inline_forms/_show.html.erb +0 -26
  14. data/app/views/inline_forms/_versions.html.erb +5 -4
  15. data/app/views/inline_forms/_versions_list.html.erb +8 -8
  16. data/app/views/inline_forms/row_close.html.erb +10 -2
  17. data/app/views/inline_forms/versions_panel.html.erb +5 -2
  18. data/app/views/layouts/application.html.erb +2 -4
  19. data/app/views/layouts/inline_forms.html.erb +2 -5
  20. data/archived/README.md +47 -0
  21. data/archived/form_elements/README.md +27 -0
  22. data/archived/form_elements/chicas/README.md +31 -0
  23. data/archived/form_elements/geo_code_curacao/README.md +62 -0
  24. data/{app → archived/form_elements/geo_code_curacao/app}/helpers/form_elements/geo_code_curacao.rb +0 -1
  25. data/archived/form_elements/geo_code_curacao/app/views/geo_code_curacao/list_streets.html.erb +1 -0
  26. data/archived/form_elements/geo_code_curacao/app/views/geo_code_curacao/list_streets.js.erb +1 -0
  27. data/archived/form_elements/kansen_slider/README.md +31 -0
  28. data/archived/form_elements/tree/README.md +47 -0
  29. data/archived/form_elements/tree/app/views/inline_forms/_show_tree.html.erb +30 -0
  30. data/{app → archived/form_elements/tree/app}/views/inline_forms/_tree.html.erb +18 -5
  31. data/bin/inline_forms_installer_core.rb +70 -5
  32. data/docs/ujs-to-turbo.md +36 -37
  33. data/lib/generators/assets/stylesheets/inline_forms.scss +5 -4
  34. data/lib/inline_forms/archived_form_elements.rb +70 -0
  35. data/lib/inline_forms/version.rb +1 -1
  36. data/lib/inline_forms.rb +3 -1
  37. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb +6 -7
  38. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_required_test.rb +21 -0
  39. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_photos_pagination_test.rb +28 -0
  40. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_row_turbo_test.rb +14 -5
  41. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +30 -60
  42. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_pagination_test.rb +40 -0
  43. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb +93 -0
  44. data/lib/installer_templates/example_app_tests/test/integration/example_app_photo_revert_test.rb +94 -0
  45. data/lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb +6 -9
  46. data/lib/installer_templates/example_app_tests/test/models/example_app_apartment_name_validation_test.rb +16 -0
  47. data/test/archived_form_elements_test.rb +41 -0
  48. data/test/form_element_from_callee_test.rb +2 -2
  49. metadata +25 -20
  50. data/app/views/geo_code_curacao/list_streets.html.erb +0 -1
  51. data/app/views/geo_code_curacao/list_streets.js.erb +0 -1
  52. data/app/views/inline_forms/close.js.erb +0 -4
  53. data/app/views/inline_forms/list.js.erb +0 -1
  54. data/app/views/inline_forms/new.js.erb +0 -1
  55. data/app/views/inline_forms/record_destroyed.js.erb +0 -1
  56. data/app/views/inline_forms/show.js.erb +0 -1
  57. data/app/views/inline_forms/show_undo.js.erb +0 -1
  58. data/app/views/inline_forms/versions.js.erb +0 -4
  59. data/app/views/inline_forms/versions_list.js.erb +0 -1
  60. /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_dropdown_with_family_members.rb +0 -0
  61. /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_family_photo_list.rb +0 -0
  62. /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_photo_list.rb +0 -0
  63. /data/{app → archived/form_elements/geo_code_curacao/app}/controllers/geo_code_curacao_controller.rb +0 -0
  64. /data/{app → archived/form_elements/geo_code_curacao/app}/models/geo_code_curacao.rb +0 -0
  65. /data/{app → archived/form_elements/kansen_slider/app}/helpers/form_elements/kansen_slider.rb +0 -0
  66. /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 into the Sprockets bundle by inline_forms.js with
70
- # Turbo Drive disabled by default so existing UJS-driven links/forms keep
71
- # working; turbo-rails also registers the `turbo_stream` Mime type and view
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 3 complete in **7.5.0** except tree-related `*.js.erb` — Step 4)
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:** almost every inline interaction is `remote: true` → `format.js` `*.js.erb` doing `$('#<update_span>').html(...)`. Turbo is loaded as an ES module with **`Turbo.session.drive = false`**. One vertical slice (nested has_many list pagination) uses **`<turbo-frame>`**.
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/Streams for all swaps; **`format.html`** / **`format.turbo_stream`** responses; no `*.js.erb`; no `jquery_ujs` or `jquery.remotipart`; Drive enabled.
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 = false` (UJS still owns navigation)
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; `format.js` still renders `show.js.erb` / `close.js.erb` for legacy callers
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); `format.js` kept for non-frame callers only
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
- - [ ] Remove `show.js.erb`, `close.js.erb`, `record_destroyed.js.erb`, `show_undo.js.erb` — **`_tree.html.erb` still uses UJS row open (Step 4)**
55
- - [x] Remove `:remote => true` from nested `_list` / `_close` / toolbar where migrated (row toolbar, versions, nested `+` use Turbo; `_tree` / top-level `new` UJS deferred to Step 4)
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 when `turbo_row:` (default **true**); legacy `remote: true` only when `turbo_row: false`
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
- - [ ] Top-level index: wrap in `<turbo-frame id="apartments_list">`; in-frame pagination
100
- - [ ] `new` / `create`: frame refresh or stream replacing list container
101
- - [ ] Remove `new.js.erb`, `list.js.erb` (keep `format.js` only until Step 5 if needed for overlap)
102
- - [ ] `_new.html.erb`: drop `:remote => true`
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
- - [ ] `_tree.html.erb`: same frame pattern as `_list`; remote pagination frame pagination
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
- - [ ] `VersionsConcern#list_versions`: HTML frame / stream
112
- - [ ] Remove `versions.js.erb`, `versions_list.js.erb`
113
- - [ ] `_versions_list.html.erb`: drop `:remote => true` on revert links (or stream revert)
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
- - [ ] `geo_code_curacao/list_streets.js.erb`: frame or stream for street dropdown
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
- - [ ] Every action in `InlineFormsController` + `VersionsConcern` has a non-JS response path
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
- - [ ] Top-level list pagination in frame
127
- - [ ] Create apartment → list frame updates
128
- - [ ] Versions panel open/close
129
- - [ ] Assert zero `data-remote="true"` in rendered HTML for inline_forms flows
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
- - [ ] Remove `Turbo.session.drive = false` from both layouts
136
- - [ ] Delete `//= require jquery_ujs` from `inline_forms.js`
137
- - [ ] Delete `//= require jquery.remotipart` from `inline_forms.js`
138
- - [ ] Delete all `app/views/inline_forms/*.js.erb` (and geo `list_streets.js.erb`)
139
- - [ ] Remove `format.js` branches from controllers (or empty stubs, then delete)
140
- - [ ] Update `example_app_turbo_layout_test.rb`: Drive enabled (or line removed)
141
- - [ ] Full `bundle exec rails test` in `--example` app
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
- | `versions.js.erb` | Fade + versions partial |
170
- | `versions_list.js.erb` | Versions table |
171
- | `geo_code_curacao/list_streets.js.erb` | Street list dropdown |
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
- `index`, `show`, `edit`, `update`, `new`, `create`, `soft_delete`, `soft_restore`, `destroy`, `revert`, `list_versions`
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 an empty/just-mounted top-level `<turbo-frame id="apartments_list">` and
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
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module InlineForms
3
- VERSION = "7.5.2"
3
+ VERSION = "7.9.1"
4
4
  end
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 partial wraps scalar fields in turbo-frame" do
17
- get apartment_path(@apartment, update: "apartment_#{@apartment.id}"),
18
- headers: {
19
- "Accept" => "text/javascript, application/javascript",
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
- "UJS show.js.erb should embed _show with turbo-frame field wrappers"
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
- headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
82
+ delete_headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
83
83
 
84
- delete apartment_path(doomed, update: frame), headers: 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), headers: headers
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, %(<turbo-frame id="#{frame}">)
92
- refute_includes @response.body, "object_presentation"
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
- # 7.5.2: top-level Apartment +new+ / +cancel+ / +create+ keep the legacy UJS
6
- # (+remote: true+) contract. 7.5.1 emitted +data-turbo-frame="apartments_list"+
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 keeps the legacy <div> wrapper (no <turbo-frame> for the list root)" do
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{<div[^>]+class="list_container"[^>]+id="#{Regexp.escape(@frame)}"},
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{<turbo-frame\s+id="#{Regexp.escape(@frame)}"},
24
+ %r{<div[^>]+id="#{Regexp.escape(@frame)}"[^>]*class="list_container"},
36
25
  @response.body,
37
- "top-level list root should NOT be a <turbo-frame> -- doing so regresses " \
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 (data-remote), not Turbo" do
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-remote="true"[^>]*href="/apartments/new\?update=apartments_list"},
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-turbo-frame="#{Regexp.escape(@frame)}"},
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 JS that swaps #apartments_list with the form (UJS)" do
59
- get new_apartment_path(update: @frame), xhr: true
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.content_type, "javascript",
62
- "UJS XHR must hit format.js"
63
- assert_match(
64
- %r{\$\('#apartments_list'\)\.html\(},
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 JS that swaps #apartments_list back to the list (UJS)" do
73
- get apartments_path(update: @frame, ul_needed: true), xhr: 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.content_type, "javascript",
76
- "UJS XHR must hit format.js"
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 UJS persists and returns the list swap" do
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
- xhr: true
64
+ headers: @frame_headers
90
65
  end
91
66
  assert_response :success
92
- assert_includes @response.content_type, "javascript"
93
- assert_match(
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