inline_forms 8.1.40 → 8.1.42

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.forgejo/workflows/full-gate.yml +4 -2
  3. data/.gitignore +6 -2
  4. data/.rubocop.yml +1 -0
  5. data/CHANGELOG.md +44 -0
  6. data/CLAUDE.md +68 -0
  7. data/Gemfile +4 -0
  8. data/Rakefile +52 -11
  9. data/app/controllers/inline_forms_controller.rb +15 -0
  10. data/app/views/inline_forms/_new.html.erb +4 -1
  11. data/app/views/inline_forms/_pending_migration_row.html.erb +15 -0
  12. data/app/views/inline_forms/_show.html.erb +4 -1
  13. data/app/views/inline_forms/field_pending.html.erb +10 -0
  14. data/lib/generators/inline_forms_addto/USAGE +31 -0
  15. data/lib/generators/inline_forms_addto_generator.rb +198 -26
  16. data/lib/inline_forms/attribute_list.rb +191 -0
  17. data/lib/inline_forms/gem_files.rb +26 -0
  18. data/lib/inline_forms/schema_apply.rb +85 -0
  19. data/lib/inline_forms/schema_intent.rb +76 -0
  20. data/lib/inline_forms/schema_label.rb +65 -0
  21. data/lib/inline_forms/schema_preview.rb +135 -0
  22. data/lib/inline_forms/version.rb +1 -1
  23. data/lib/inline_forms.rb +86 -0
  24. data/lib/locales/inline_forms.en.yml +2 -0
  25. data/lib/locales/inline_forms.nl.yml +2 -0
  26. data/test/attribute_list_test.rb +165 -0
  27. data/test/dummy/app/controllers/gizmos_controller.rb +4 -0
  28. data/test/dummy/app/models/gizmo.rb +25 -0
  29. data/test/dummy/config/application.rb +3 -0
  30. data/test/dummy/config/routes.rb +9 -0
  31. data/test/inline_forms_addto_generator_test.rb +220 -0
  32. data/test/integration/pending_migration_gate_test.rb +75 -0
  33. data/test/integration/schema_batch_pipeline_test.rb +189 -0
  34. data/test/integration/schema_edit_test.rb +141 -0
  35. data/test/integration/schema_preview_test.rb +65 -0
  36. data/test/integration_test_helper.rb +41 -0
  37. data/test/schema_apply_test.rb +73 -0
  38. data/test/schema_intent_test.rb +53 -0
  39. data/test/schema_label_test.rb +61 -0
  40. metadata +29 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e2f71a943f446268125b1012392c5156bcc5cbfc1df84385b4fc10225545135
4
- data.tar.gz: ee90225c78498b2a1b0759805363e7871b772c79cdc2f41620a4470e2ed01e2b
3
+ metadata.gz: 11df5d86221aad276d5bf2d4878702ce6651b595bd84f4911534922da817e07a
4
+ data.tar.gz: 14ac9ad9346af55d6573926f46f8fd907bf7d86e9eb695887d45e9abd5f67e58
5
5
  SHA512:
6
- metadata.gz: 4aded88b4f1ffeb2c4509505c16ef2e362e9bc89b86dc526fde9f83680750f850dad489a45d2067db754e558e05cd8d3871eb3dc48ec71e09e0d6e02f24c6717
7
- data.tar.gz: 318a3f451539ae8a3a0775053b1a6f3f422759114cb4587f7ba89fd2d225a1186904e600a4e6f86c9f1424c9c2784975d3aaf595062722fb83ecc8b74126d3ea
6
+ metadata.gz: 5f5b578c9c304f90509dc7fc1fe5a226b6f1b954eb11a451053f5107c474278caecc7fcf456f339794b34014078c38205a30ad786acd426d269ef6cff9096614
7
+ data.tar.gz: af1a4a4a228b4b3be79e5d71ab0b2ef6f2f02fb662032bad0b9ba40b285de548abbcb31c8adb501e990dd0d02ac3532819e3acee09334cc83f4899d8f42f8fc7
@@ -33,17 +33,19 @@ jobs:
33
33
  token: ${{ secrets.VALIDATION_HINTS }}
34
34
  path: validation_hints
35
35
 
36
- - name: Build the three gems
36
+ - name: Build the four gems
37
37
  run: |
38
38
  cd validation_hints && gem build validation_hints.gemspec && cd ..
39
39
  cd inline_forms && gem build inline_forms.gemspec && gem build inline_forms_installer.gemspec
40
+ cd inline_forms_schema_edit && gem build inline_forms_schema_edit.gemspec
40
41
 
41
42
  - name: Install built gems
42
43
  run: |
43
44
  gem install --no-document \
44
45
  validation_hints/validation_hints-*.gem \
45
46
  inline_forms/inline_forms-*.gem \
46
- inline_forms/inline_forms_installer-*.gem
47
+ inline_forms/inline_forms_installer-*.gem \
48
+ inline_forms/inline_forms_schema_edit/inline_forms_schema_edit-*.gem
47
49
 
48
50
  # The installer git-inits the generated app and makes an initial commit
49
51
  # (installer_core.rb: `git add .` + `git commit`); a bare container has
data/.gitignore CHANGED
@@ -21,10 +21,14 @@ pkg
21
21
  # Builds
22
22
  *.gem
23
23
 
24
+ # Scratch / local notes and secrets (e.g. stuff/forgejo-token). Kept in-repo
25
+ # .gitignore on purpose: the global excludes file that also ignores this is
26
+ # per-machine and is absent on the release box, which let stuff/ leak into
27
+ # `gem build`. Do not rely on the global ignore alone.
28
+ stuff/
29
+
24
30
  Gemfile.lock
25
31
 
26
- # Local notes / analysis (not tracked)
27
- stuff/
28
32
 
29
33
 
30
34
  # test/dummy runtime artifacts (in-memory DB; only logs/tmp may appear)
data/.rubocop.yml CHANGED
@@ -14,6 +14,7 @@ AllCops:
14
14
  TargetRubyVersion: 3.3
15
15
  Exclude:
16
16
  - "pkg/**/*"
17
+ - "inline_forms_schema_edit/pkg/**/*"
17
18
  - "vendor/**/*"
18
19
  - "archived/**/*" # frozen historical code; linting defeats the purpose
19
20
  - "lib/installer_templates/**/*" # templates for *generated apps*, not engine style
data/CHANGELOG.md CHANGED
@@ -4,6 +4,50 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [8.1.42] - 2026-07-22
8
+
9
+ ### Fixed
10
+
11
+ - **`gem build` no longer packages the `stuff/` scratch dir (and its secrets).**
12
+ `InlineFormsGemFiles.gem_files` sweeps untracked files, so it depended on
13
+ `stuff/` being git-ignored. That ignore lived only in the per-machine global
14
+ excludes file, which is absent on the release box — so `stuff/forgejo-token`
15
+ (mode 0600) leaked into the file list and `gem build` died with `Errno::EACCES`.
16
+ `stuff/` is now excluded in `gem_files` itself, independent of any ignore
17
+ config, and re-added to the repo `.gitignore` as a second line of defense.
18
+
19
+ ### Changed
20
+
21
+ - **Schema-change GUI extracted into its own gem** (`inline_forms_schema_edit`,
22
+ same repo, own gemspec; phase 0 of
23
+ `stuff/2026-07-11-schema-gui-gem-and-automated-pipeline-plan.md`).
24
+ `InlineForms::SchemaController`, its views and the `inline_forms_schema`
25
+ layout move out of the engine; the staging services (`SchemaIntent`,
26
+ `SchemaPreview`, `SchemaApply`, `SchemaLabel`) stay. The installer gains a
27
+ `--schema-edit` flag (default off, implied by `--example`) that adds the gem
28
+ to the generated Gemfile and wires the schema routes + "+ field" nav link;
29
+ apps created without it ship no schema-GUI surface at all.
30
+
31
+ ## [8.1.41] - 2026-07-11
32
+
33
+ ### Changed
34
+
35
+ - **`inline_forms_addto` hardened** (see `stuff/2026-07-11-add-scalar-attribute-to-existing-model.md`). Adding a field to an existing model is now safer:
36
+ - **Smarter row placement.** New `inline_forms_attribute_list` rows land above the trailing metadata block (the first `:info` / `created_at` / `updated_at` row, and a `:header` directly above it) instead of at the very end, so an editable field no longer drops into the read-only info section. New `--before=ATTR` / `--after=ATTR` options place the row explicitly (warn + fall back to the default when the anchor is missing).
37
+ - **Robust model rewriting.** The greedy `]…end` regex is replaced by a bracket-depth scan from `@inline_forms_attribute_list ||= [` to its matching `]`, which survives `].freeze`, trailing comments, methods defined after the list, and nested `[ ]` in choice rows.
38
+ - **Value-bearing elements.** Choice elements (`dropdown_with_values`, `dropdown_with_integers`, `dropdown_with_values_with_stars`, `radio_button`, `check_box`, `scale_with_integers`, `scale_with_values`) now emit a placeholder `{ 1 => 'one', 2 => 'two' }` values hash and a warning instead of a bare 2-element row that raised at show time.
39
+ - **Collision-free migration timestamps.** Migration filenames use `max(now, highest_existing + 1)` so back-to-back runs (or a run right after another generator in the same second) never collide.
40
+ - **"Run db:migrate" reminder** printed after generation, since the attribute_list row is written immediately but its column only exists after migrating.
41
+
42
+ ### Added
43
+
44
+ - Example app now exercises `inline_forms_addto` end to end: the installer adds `Apartment#internal_note` via `rails g inline_forms_addto` + `db:migrate`, and `example_app_apartment_addto_field_test.rb` asserts the column migrated, the row renders on the stock show panel, and an inline update round-trips.
45
+ - Generator unit tests for the above (smart/explicit placement, `].freeze` + trailing-method robustness, value-bearing placeholder, timestamp uniqueness, and applying the generated migration to an in-memory sqlite DB).
46
+
47
+ ### Lockstep
48
+
49
+ - validation_hints 8.1.41, inline_forms_installer 8.1.41.
50
+
7
51
  ## [8.1.40] - 2026-07-10
8
52
 
9
53
  ### Added
data/CLAUDE.md ADDED
@@ -0,0 +1,68 @@
1
+ # CLAUDE.md — inline_forms
2
+
3
+ Instructions for any work in the inline_forms repo (`/home/code/inline_forms`).
4
+ These apply to all work here and take precedence over global defaults where they
5
+ conflict. Consolidated from the former `stuff/prompt/workflow.md` and
6
+ `stuff/prompt/test-the-example-app.md`.
7
+
8
+ ## Workflow after any significant change
9
+
10
+ 1. Run the full example-app test (see **Testing the example app** below). Keep
11
+ improving until all tests succeed.
12
+ 2. Bump the **patch** version. Stay in the **8.1** line until further notice.
13
+ Never bump the minor version — even for big or breaking changes.
14
+ - Change the version in **all four gems** so they stay in lockstep, always,
15
+ even if nothing changed in some of them:
16
+ `validation_hints`, `inline_forms`, `inline_forms_installer`, and
17
+ `inline_forms_schema_edit` (in this repo under `inline_forms_schema_edit/`).
18
+ 3. Update the Changelog of all four gems.
19
+ 4. Commit everything.
20
+ 5. After committing, push to the `forgejo` remote
21
+ (`ssh://forgejo@dev02:2222/ace/...`) in **both repos** (inline_forms and
22
+ validation_hints) so every commit runs through CI (Forgejo Actions on dev02).
23
+ - Check the run at http://dev02:3300/ace/inline_forms/actions and fix
24
+ failures.
25
+ 6. Never push to `origin` (GitHub). Only Ace pushes there, and GitHub must not
26
+ run anything (no `.github/workflows`).
27
+
28
+ ## Testing the example app
29
+
30
+ Goal: build the current gem, install it into `/home/code/testInline`, recreate
31
+ `MyApp` from scratch using `--example`, and verify it with Rails tests.
32
+
33
+ Do all steps end-to-end without asking for confirmation unless a command fails.
34
+
35
+ ### Steps
36
+
37
+ 1. In `/home/code/inline_forms`:
38
+ - Ensure the latest code is used.
39
+ - Run: `rvm use .`
40
+ - Build gems:
41
+ `gem build inline_forms.gemspec && gem build inline_forms_installer.gemspec && (cd inline_forms_schema_edit && gem build inline_forms_schema_edit.gemspec)`
42
+ - Confirm the built file names/versions
43
+ (`inline_forms-<version>.gem`, `inline_forms_installer-<version>.gem`,
44
+ `inline_forms_schema_edit/inline_forms_schema_edit-<version>.gem`).
45
+
46
+ 2. In `/home/code/testInline`:
47
+ - Remove the old app if present: `/home/code/testInline/MyApp`
48
+ - Run: `rvm use .`
49
+ - Install the freshly built gems from
50
+ `/home/code/inline_forms/inline_forms-<version>.gem`,
51
+ `inline_forms_installer-<version>.gem`, and
52
+ `inline_forms_schema_edit/inline_forms_schema_edit-<version>.gem`.
53
+
54
+ 3. Still in `/home/code/testInline`:
55
+ - Generate a fresh example app:
56
+ `inline_forms create MyApp -d sqlite --example`
57
+
58
+ 4. In `/home/code/testInline/MyApp`:
59
+ - Run: `rvm use .`
60
+ - Run verification: `rails test`
61
+
62
+ ### Output requirements
63
+
64
+ - Briefly report each phase result (build, install, app generation, test run).
65
+ - Include the exact commands run.
66
+ - Include the test summary (runs/assertions/failures/errors/skips).
67
+ - If anything fails, stop at the failing step and include the exact error and
68
+ the next corrective command you recommend.
data/Gemfile CHANGED
@@ -13,6 +13,10 @@ group :test do
13
13
  gem "will_paginate", "~> 4.0"
14
14
  gem "paper_trail", "~> 17.0"
15
15
  gem "turbo-rails"
16
+ # The schema-GUI engine gem (same repo, own gemspec). Test group so the
17
+ # dummy app's `Bundler.require(*Rails.groups)` loads it and the engine's
18
+ # schema_edit integration tests exercise the extracted controller/views.
19
+ gem "inline_forms_schema_edit", path: "inline_forms_schema_edit"
16
20
  end
17
21
 
18
22
  group :development, :test do
data/Rakefile CHANGED
@@ -7,11 +7,18 @@ namespace :installer do
7
7
  Bundler::GemHelper.install_tasks name: "inline_forms_installer"
8
8
  end
9
9
 
10
+ namespace :schema_edit do
11
+ # The schema-GUI gem lives in its own subdirectory (own gemspec, own pkg/).
12
+ Bundler::GemHelper.install_tasks dir: File.expand_path("inline_forms_schema_edit", __dir__),
13
+ name: "inline_forms_schema_edit"
14
+ end
15
+
10
16
  def inline_forms_pkg_gems
11
- pkg = File.expand_path("pkg", __dir__)
12
17
  version = InlineForms::VERSION
13
- %w[inline_forms inline_forms_installer].map do |name|
14
- path = File.join(pkg, "#{name}-#{version}.gem")
18
+ { "inline_forms" => "pkg",
19
+ "inline_forms_installer" => "pkg",
20
+ "inline_forms_schema_edit" => "inline_forms_schema_edit/pkg" }.map do |name, pkg|
21
+ path = File.expand_path(File.join(pkg, "#{name}-#{version}.gem"), __dir__)
15
22
  raise "Missing #{path}. Run rake build:all first." unless File.file?(path)
16
23
  path
17
24
  end
@@ -25,8 +32,8 @@ def validation_hints_pkg_gem
25
32
  ].find { |path| File.file?(path) }
26
33
  end
27
34
 
28
- desc "Build both inline_forms and inline_forms_installer into pkg/"
29
- task "build:all" => [ "build", "installer:build" ]
35
+ desc "Build inline_forms, inline_forms_installer and inline_forms_schema_edit"
36
+ task "build:all" => [ "build", "installer:build", "schema_edit:build" ]
30
37
 
31
38
  desc "Install freshly built gems from pkg/ into the current gemset (required before inline_forms create)"
32
39
  task "install:local" => [ "build:all" ] do
@@ -38,16 +45,50 @@ task "install:local" => [ "build:all" ] do
38
45
  puts "Verify: inline_forms --version 2>/dev/null || gem which inline_forms_installer"
39
46
  end
40
47
 
41
- # Release inline_forms + inline_forms_installer only (build, git tag/push, RubyGems push).
42
- # Does not run inline_forms create, MyApp, or tests. validation_hints is a separate
43
- # repo: cd ../validation_hints && rake release (same version number).
44
- desc "Release inline_forms and inline_forms_installer to RubyGems (no app generation)"
48
+ # Release inline_forms + inline_forms_installer + inline_forms_schema_edit
49
+ # (build, git tag/push, RubyGems push). Does not run inline_forms create,
50
+ # MyApp, or tests. validation_hints is a separate repo:
51
+ # cd ../validation_hints && rake release (same version number).
52
+ # Release git-push ALWAYS targets `origin` (GitHub) only.
53
+ #
54
+ # Do NOT use Bundler's stock `release:source_control_push`: it pushes to the
55
+ # CURRENT BRANCH's tracking remote (`git config branch.<branch>.remote`).
56
+ # Feature branches in this repo track `forgejo` (dev02) because that's where
57
+ # CI runs, so a release cut from a feature branch would push the release commit
58
+ # and version tag to forgejo instead of GitHub. `origin` is the only place
59
+ # release commits/tags belong; `forgejo` is CI-only. This task is also
60
+ # idempotent on the tag, so a re-run after a failed push still lands the tag on
61
+ # origin (Bundler's version skips entirely once the tag exists locally).
62
+ # Also override the bare `release` task (from install_tasks above) so a stray
63
+ # `rake release` can't push to a feature branch's forgejo tracking remote
64
+ # either. The intended entry point is `release:all`, but this closes the
65
+ # footgun on both.
66
+ Rake::Task["release"].clear if Rake::Task.task_defined?("release")
67
+ desc "Build inline_forms, tag, push to origin (GitHub) and push the gem to RubyGems"
68
+ task "release" => [ "build", "release:guard_clean",
69
+ "release:push_to_origin", "release:rubygem_push" ]
70
+
71
+ desc "Tag the release and push the commit + tag to origin (GitHub) only"
72
+ task "release:push_to_origin" do
73
+ version = InlineForms::VERSION
74
+ tag = "v#{version}"
75
+ branch = `git rev-parse --abbrev-ref HEAD`.strip
76
+ unless system("git", "rev-parse", "-q", "--verify", "refs/tags/#{tag}",
77
+ out: File::NULL, err: File::NULL)
78
+ sh "git", "tag", tag
79
+ end
80
+ sh "git", "push", "origin", "refs/heads/#{branch}"
81
+ sh "git", "push", "origin", "refs/tags/#{tag}"
82
+ end
83
+
84
+ desc "Release inline_forms, inline_forms_installer and inline_forms_schema_edit to RubyGems (no app generation)"
45
85
  task "release:all" => [
46
86
  "build:all",
47
87
  "release:guard_clean",
48
- "release:source_control_push",
88
+ "release:push_to_origin",
49
89
  "release:rubygem_push",
50
- "installer:release:rubygem_push"
90
+ "installer:release:rubygem_push",
91
+ "schema_edit:release:rubygem_push"
51
92
  ]
52
93
 
53
94
  require "rake/testtask"
@@ -137,6 +137,11 @@ class InlineFormsController < ApplicationController
137
137
  @update_span = params[:update]
138
138
  attributes = @inline_forms_attribute_list || @object.inline_forms_attribute_list
139
139
  attributes.each do | attribute, form_element |
140
+ # Skip a row whose column is not migrated yet (model edited, migration
141
+ # not run). Writing it would raise; the new form shows it as a pending
142
+ # placeholder and does not submit a value for it.
143
+ next if InlineForms.attribute_pending_migration?(@object, attribute, form_element)
144
+
140
145
  InlineForms.assert_plain_text_column!(object: @object, attribute: attribute, form_element: form_element)
141
146
  send("#{form_element}_update", @object, attribute) unless form_element == :associated || (cancan_enabled? && cannot?(:read, @object, attribute))
142
147
  end
@@ -186,6 +191,16 @@ class InlineFormsController < ApplicationController
186
191
  @form_element = params[:form_element]
187
192
  @sub_id = params[:sub_id]
188
193
  @update_span = params[:update]
194
+ # Defense-in-depth: the UI renders a pending field as a read-only
195
+ # placeholder (never links to edit/update), but a hand-crafted request
196
+ # could still target a column that is not migrated yet. Refuse cleanly
197
+ # instead of 500ing on the missing column.
198
+ if InlineForms.attribute_pending_migration?(@object, @attribute, @form_element)
199
+ respond_to do |format|
200
+ format.html { render "inline_forms/field_pending", layout: "turbo_rails/frame", status: :unprocessable_entity }
201
+ end
202
+ return
203
+ end
189
204
  InlineForms.assert_plain_text_column!(object: @object, attribute: @attribute, form_element: @form_element)
190
205
  send("#{@form_element}_update", @object, @attribute)
191
206
  # Branch on the actual save result. Previously the return value of
@@ -42,7 +42,10 @@
42
42
  <% attributes.each do | attribute, form_element | -%>
43
43
  <% unless form_element.to_sym == :associated || (cancan_enabled? && cannot?(:read, @object, attribute)) -%>
44
44
  <% css_class_id = "attribute_#{attribute}_#{@object.id}" -%>
45
- <% if form_element && form_element.to_sym == :header %>
45
+ <% if InlineForms.attribute_pending_migration?(@object, attribute, form_element) %>
46
+ <%= render partial: "inline_forms/pending_migration_row",
47
+ locals: { object: @object, attribute: attribute } %>
48
+ <% elsif form_element && form_element.to_sym == :header %>
46
49
  <div class="row form_element_header" >
47
50
  <div class='large-12 column<%= " attribute_name attribute_#{attribute} form_element_#{form_element}" -%>' >
48
51
  <%= @object.human_attribute_name(attribute) -%>
@@ -0,0 +1,15 @@
1
+ <%# Rendered in place of a field whose column does not exist yet (the model
2
+ file has the attribute_list row but its migration has not run). Read-only,
3
+ harmless, self-explanatory — see InlineForms.attribute_pending_migration?. %>
4
+ <div class="row pending_migration_row <%= cycle('odd', 'even') %>">
5
+ <div class='medium-5 large-5 column'>
6
+ <%= render partial: "inline_forms/attribute_label",
7
+ locals: { object: object, attribute: attribute } -%>
8
+ </div>
9
+ <div class='medium-7 large-7 column'>
10
+ <span class="pending_migration_notice">
11
+ <%= t("inline_forms.pending_migration.notice",
12
+ default: "pending migration \u2014 run rails db:migrate") -%>
13
+ </span>
14
+ </div>
15
+ </div>
@@ -23,7 +23,10 @@
23
23
  <% attributes.each do | attribute, form_element | -%>
24
24
  <% if cancan_disabled? || can?(:read, @object, attribute) %>
25
25
  <% css_class_id = "#{@object.class.name.underscore}_#{@object.id}_#{attribute}" -%>
26
- <% if form_element == :header %>
26
+ <% if InlineForms.attribute_pending_migration?(@object, attribute, form_element) %>
27
+ <%= render partial: "inline_forms/pending_migration_row",
28
+ locals: { object: @object, attribute: attribute } %>
29
+ <% elsif form_element == :header %>
27
30
  <div class="row form_element_header">
28
31
  <div class='large-12 column' >
29
32
  <%= @object.class.human_attribute_name(attribute) -%>
@@ -0,0 +1,10 @@
1
+ <%# Turbo-frame response for a forced field request (show/edit/update) against
2
+ an attribute whose column does not exist yet. The UI never links here (the
3
+ row renders as a pending placeholder in _show), so this is defense-in-depth
4
+ for a hand-crafted request during the pre-migrate window. %>
5
+ <turbo-frame id="<%= @update_span %>">
6
+ <span class="pending_migration_notice">
7
+ <%= t("inline_forms.pending_migration.notice",
8
+ default: "pending migration \u2014 run rails db:migrate") -%>
9
+ </span>
10
+ </turbo-frame>
@@ -13,6 +13,37 @@ Description:
13
13
  rejected. `_presentation`, `_list_order`, `_list_search`, and `_order` are
14
14
  skipped unless you pass `--replace`.
15
15
 
16
+ Placement:
17
+ By default a new attribute_list row is inserted above the model's trailing
18
+ "metadata" block (its `:info` rows / created_at / updated_at, and a
19
+ `:header` directly above them) rather than at the very end, so an editable
20
+ field does not land inside the read-only info section. Override with:
21
+
22
+ --before=ATTR insert the new row before an existing attribute
23
+ --after=ATTR insert the new row after an existing attribute
24
+
25
+ If the named anchor is not found, the generator warns and falls back to the
26
+ default placement.
27
+
28
+ Value-bearing form elements:
29
+ Choice elements (dropdown_with_values, dropdown_with_integers,
30
+ dropdown_with_values_with_stars, radio_button, check_box,
31
+ scale_with_integers, scale_with_values) need a values hash as the 3rd row
32
+ element. The generator inserts a placeholder `{ 1 => 'one', 2 => 'two' }`
33
+ and warns; edit it to your real values.
34
+
35
+ Constraints and caveats:
36
+ - Additive only: the generated migration uses `add_column` / `add_reference`
37
+ (nullable, no default). Renaming, dropping, or making a column NOT NULL
38
+ (and the SQLite table-rebuild that would require) is out of scope; do
39
+ those by hand.
40
+ - Run `bundle exec rails db:migrate` after generating: the model row is
41
+ written immediately, but the column only exists once the migration runs.
42
+ - PaperTrail: versions recorded before this migration have no value for the
43
+ new column, so reverting an old record leaves the new column at its
44
+ default (nil), and the versions panel shows no history for it prior to
45
+ the migration.
46
+
16
47
  Example:
17
48
  rails generate inline_forms_addto User occupation:text_field birthdate:date organization:dropdown bio:rich_text
18
49