inline_forms 8.1.0 → 8.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.rdoc +2 -2
- data/inline_forms.gemspec +4 -4
- data/lib/generators/inline_forms_addto_generator.rb +291 -0
- data/lib/generators/inline_forms_attribute_overrides.rb +85 -0
- data/lib/generators/inline_forms_generator.rb +4 -73
- data/lib/generators/templates/add_columns_migration.erb +7 -0
- data/lib/generators/templates/migration.erb +1 -1
- data/lib/inline_forms/version.rb +1 -1
- data/test/inline_forms_addto_generator_test.rb +251 -0
- data/test/inline_forms_generator_test.rb +1 -1
- metadata +15 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1cb2062c3fc919203715606f7a499b51f35cac4882d681098499e48ee7b6f06f
|
|
4
|
+
data.tar.gz: 8f08d99f8c9b161c398919747e8fe457e0e6c91370d721eb8e300e9fd89d934a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f72943d7626636b4657c09b79aa47feff78fd533fe179bcc5200322ba49b48dceee70b4cff15362b715af47d34e40c0ff2184e879c1e0cee715698bd236cd223
|
|
7
|
+
data.tar.gz: e7aea2a16935eb0c0fb7bae12a2c2bd83ed1ce8324d29d750523c624e72b15233574d18f520bb64b4c8495a054a7d841958451c83a81608e3f6f62cc6b55346b
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [8.1.2] - 2026-05-26
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Rails 8.1:** engine and installer pin **`rails >= 8.1, < 8.2`** (resolved **8.1.3**) and **`rails-i18n >= 8.1, < 9.0`** (resolved **8.1.0**). Generated apps' `config/application.rb` is normalised to **`config.load_defaults 8.1`** and the installer prefers **`rails _8.1.x_`** for `rails new`. All generator and installer migrations now emit **`ActiveRecord::Migration[8.1]`** (`lib/generators/templates/migration.erb`, `lib/generators/templates/add_columns_migration.erb`, `DeviseCreateUsers`, `InlineFormsCreateJoinTableUserRole`, `AddOwnerToApartments`, `SeedExampleApartmentsAndOwners`).
|
|
12
|
+
- **`validation_hints` constraint:** widened to **`>= 8.1.2, < 9.0`** so Bundler picks up the matching companion release that targets `activerecord >= 8.1`.
|
|
13
|
+
- **README.rdoc:** requirements table and `rails-i18n` narrative updated to **Rails 8.1.x** / `~> 8.1` / `config.load_defaults 8.1`.
|
|
14
|
+
- **`InlineForms::VERSION`** and **`InlineFormsInstaller::VERSION`** → **8.1.2** (lockstep with **validation_hints 8.1.2**).
|
|
15
|
+
|
|
16
|
+
### Notes
|
|
17
|
+
|
|
18
|
+
- **Example app gate (recorded):** `inline_forms create MyApp -d sqlite --example` against the freshly built **8.1.2** gem trio on `rails 8.1.3`: install completes in ~77s, `bundle check: ok`, **88 runs, 502 assertions, 0 failures, 0 errors, 0 skips**. A subsequent `bundle exec rails test` in the generated app reproduces the same result in ~1.9s.
|
|
19
|
+
- **PaperTrail 16.0.0 / ActiveRecord 8.1 warning:** PT emits a compatibility warning during boot (it pins `< 8.1` internally) but does not raise; all PaperTrail-backed integration and model tests pass on AR 8.1.3. Revisit pin when paper_trail ships an 8.1-compatible release; no behaviour change needed in inline_forms for now.
|
|
20
|
+
- **Frozen-string warnings:** Ruby 4.0's `--debug-frozen-string-literal` surfaces literal-string warnings from `tabs_on_rails 3.0.0` and one inline_forms helper (`check_list_helper.rb:13`); these are non-fatal under current Ruby and tracked separately.
|
|
21
|
+
|
|
22
|
+
## [8.1.1] - 2026-05-26
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`rails g inline_forms_addto <Model> attr:type [...]`:** new generator that adds fields to an *existing* inline_forms model. Emits an additive migration (`add_column` for scalars, `add_reference :table, :name, foreign_key: true` for `:belongs_to`/`:references`/`:dropdown`, single `:string` column for `:image_field`/`:audio_field`; no column for `:rich_text`/`has_many`/`has_one`/`habtm`) and edits the model file: injects `belongs_to` / `has_many` / `has_one` / `has_and_belongs_to_many` / `has_rich_text` / `mount_uploader` lines (idempotent), and inserts new rows into the model's `inline_forms_attribute_list` (handles both the generator-shaped and the installer's hand-written `User`/`Member` block). Does NOT touch routes, controller, or `MODEL_TABS` — those are already wired for the existing model. Use case: extending the narrow `User` (or `--user-model Member`) shipped by the installer with app-specific fields like `occupation`, `birthdate`, `organization:dropdown`, `bio:rich_text` without hand-writing migrations or attribute-list edits. Plan: [`stuff/inline_forms_addto.md`](stuff/inline_forms_addto.md).
|
|
27
|
+
- **`--allow-unknown` parity** with `inline_forms`: unknown field types raise `Thor::Error` by default; pass `--allow-unknown` to keep the legacy commented output (`# add_column …`, `# [ :name , "name", :unknown ]`).
|
|
28
|
+
- **`--replace`** flag: opt-in rewrite of existing `_presentation` / `_list_order` (and the legacy `_order`) / `_list_search` scopes/methods. Without `--replace` these special names are skipped with `say_status :skipped` so re-runs don't silently mutate the model's ordering/search policy.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Refactor — `Rails::Generators::GeneratedAttribute` overrides moved to `lib/generators/inline_forms_attribute_overrides.rb`** and shared by both `InlineFormsGenerator` (`rails g inline_forms`) and the new `InlineFormsAddtoGenerator` (`rails g inline_forms_addto`). Pure refactor: same `column_type` / `attribute_type` / `relation?` / `migration?` / `attribute?` / `SPECIAL_GENERATOR_NAMES` semantics, now qualified with `InlineForms::` constants so the file works outside the `module InlineForms` lexical scope.
|
|
33
|
+
|
|
34
|
+
### Notes
|
|
35
|
+
|
|
36
|
+
- The new generator class is top-level (`InlineFormsAddtoGenerator`, not `InlineForms::InlineFormsAddtoGenerator`) so `rails g inline_forms_addto …` resolves directly. Rails' generator lookup only collapses `name:name` namespaces (the `inline_forms:inline_forms` → `inline_forms` rule that `InlineFormsGenerator` relies on). A `module InlineForms; InlineFormsAddtoGenerator = ::InlineFormsAddtoGenerator; end` alias is provided for any programmatic invokers that reach for the namespaced constant.
|
|
37
|
+
|
|
7
38
|
## [8.1.0] - 2026-05-25
|
|
8
39
|
|
|
9
40
|
### Added
|
data/README.rdoc
CHANGED
|
@@ -5,7 +5,7 @@ Inline Forms is almost a complete admin application. You can try it out easily.
|
|
|
5
5
|
= Requirements
|
|
6
6
|
|
|
7
7
|
* Ruby **>= 4.0** (generated apps pin **ruby-4.0.4**)
|
|
8
|
-
* Rails **8.
|
|
8
|
+
* Rails **8.1.x** (+rails ~> 8.1+, +config.load_defaults 8.1+)
|
|
9
9
|
* **validation_hints** **~> 8** (companion gem; same version line as +inline_forms+ / +inline_forms_installer+)
|
|
10
10
|
|
|
11
11
|
= Usage
|
|
@@ -252,7 +252,7 @@ In every case the Turbo wiring is the same: +link_options: { data: { turbo_frame
|
|
|
252
252
|
|
|
253
253
|
== Generated application +rails-i18n+
|
|
254
254
|
|
|
255
|
-
New apps get +rails-i18n+ from RubyGems (+ '~> 8.
|
|
255
|
+
New apps get +rails-i18n+ from RubyGems (+ '~> 8.1'+), not from the +svenfuchs/rails-i18n+ Git repository. The installer pins +rails ~> 8.1+ with +config.load_defaults 8.1+; the published +rails-i18n+ 8.x line matches that stack.
|
|
256
256
|
|
|
257
257
|
== File uploads (CarrierWave)
|
|
258
258
|
|
data/inline_forms.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
|
|
|
11
11
|
s.email = ["ace@suares.com"]
|
|
12
12
|
s.homepage = %q{http://github.com/acesuares/inline_forms}
|
|
13
13
|
s.summary = %q{Inline editing of forms for Rails 8.}
|
|
14
|
-
s.description = %q{Inline Forms eases setup of admin-style forms with inline editing. Field lists are declared on the model. Requires Rails 8.
|
|
14
|
+
s.description = %q{Inline Forms eases setup of admin-style forms with inline editing. Field lists are declared on the model. Requires Rails 8.1.x, Ruby >= 4.0, and validation_hints ~> 8.}
|
|
15
15
|
s.licenses = ["MIT"]
|
|
16
16
|
s.required_ruby_version = ">= 4.0.0"
|
|
17
17
|
|
|
@@ -19,9 +19,9 @@ Gem::Specification.new do |s|
|
|
|
19
19
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
|
20
20
|
s.require_paths = ["lib"]
|
|
21
21
|
|
|
22
|
-
s.add_dependency("validation_hints", ">= 8.1.
|
|
23
|
-
s.add_dependency("rails", ">= 8.
|
|
24
|
-
s.add_dependency("rails-i18n", ">= 8.
|
|
22
|
+
s.add_dependency("validation_hints", ">= 8.1.2", "< 9.0")
|
|
23
|
+
s.add_dependency("rails", ">= 8.1", "< 8.2")
|
|
24
|
+
s.add_dependency("rails-i18n", ">= 8.1", "< 9.0")
|
|
25
25
|
|
|
26
26
|
s.add_development_dependency("minitest", "~> 5.0")
|
|
27
27
|
end
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
require "inline_forms"
|
|
3
|
+
require_relative "inline_forms_attribute_overrides"
|
|
4
|
+
|
|
5
|
+
# == Usage
|
|
6
|
+
#
|
|
7
|
+
# rails g inline_forms_addto Model attribute:type [attribute:type ...] [options]
|
|
8
|
+
#
|
|
9
|
+
# Adds fields to an existing inline_forms model. Emits an additive
|
|
10
|
+
# migration (`add_column` / `add_reference` / single :string column for
|
|
11
|
+
# `:image_field`/`:audio_field`) and edits the model file:
|
|
12
|
+
#
|
|
13
|
+
# * adds `belongs_to` / `has_many` / `has_one` / `has_and_belongs_to_many` /
|
|
14
|
+
# `has_rich_text` / `mount_uploader` lines (idempotent)
|
|
15
|
+
# * inserts new rows into the model's `inline_forms_attribute_list`
|
|
16
|
+
#
|
|
17
|
+
# Does NOT touch routes, the controller, or `MODEL_TABS` (those are
|
|
18
|
+
# already wired for the existing model). Use `rails g inline_forms <Model>`
|
|
19
|
+
# to create new models.
|
|
20
|
+
#
|
|
21
|
+
# == Special generator names
|
|
22
|
+
#
|
|
23
|
+
# `_no_model`, `_no_migration`, `_id`, `_enabled` are install-time only and
|
|
24
|
+
# are rejected. `_presentation`, `_list_order`, `_list_search` are
|
|
25
|
+
# accepted but only acted on when `--replace` is passed; otherwise the
|
|
26
|
+
# generator skips them with a `say_status :skipped` notice.
|
|
27
|
+
#
|
|
28
|
+
# Top-level (no `InlineForms` module wrapper) so Rails resolves
|
|
29
|
+
# `rails g inline_forms_addto Model …` to this class without the
|
|
30
|
+
# `inline_forms:inline_forms_addto` double-segment trick that Rails only
|
|
31
|
+
# applies for matching `name:name` pairs (`inline_forms:inline_forms`).
|
|
32
|
+
class InlineFormsAddtoGenerator < Rails::Generators::NamedBase
|
|
33
|
+
INSTALL_TIME_ONLY_NAMES = %w[_no_model _no_migration _id _enabled].freeze
|
|
34
|
+
REPLACE_ONLY_NAMES = %w[_presentation _list_order _list_search _order].freeze
|
|
35
|
+
|
|
36
|
+
argument :attributes, type: :array, banner: "[name:form_element]..."
|
|
37
|
+
class_option :allow_unknown, type: :boolean, default: false,
|
|
38
|
+
desc: "Allow unknown field types (legacy behavior: comment generated lines instead of failing)."
|
|
39
|
+
class_option :replace, type: :boolean, default: false,
|
|
40
|
+
desc: "Replace existing _presentation/_list_order/_list_search instead of skipping."
|
|
41
|
+
|
|
42
|
+
source_root File.expand_path("templates", __dir__)
|
|
43
|
+
|
|
44
|
+
def validate!
|
|
45
|
+
unless File.exist?(File.join(destination_root, model_file_path))
|
|
46
|
+
raise Thor::Error,
|
|
47
|
+
"Model #{model_file_path} not found. " \
|
|
48
|
+
"Use `rails g inline_forms #{name} ...` for new models."
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def set_some_flags
|
|
53
|
+
@unknown_attributes = []
|
|
54
|
+
@forbidden_names = []
|
|
55
|
+
@skipped_replace_names = []
|
|
56
|
+
attributes.each do |attribute|
|
|
57
|
+
if INSTALL_TIME_ONLY_NAMES.include?(attribute.name)
|
|
58
|
+
@forbidden_names << attribute.name
|
|
59
|
+
next
|
|
60
|
+
end
|
|
61
|
+
if REPLACE_ONLY_NAMES.include?(attribute.name) && !options[:replace]
|
|
62
|
+
@skipped_replace_names << "#{attribute.name}:#{attribute.type}"
|
|
63
|
+
next
|
|
64
|
+
end
|
|
65
|
+
if !attribute.name.start_with?("_") && (
|
|
66
|
+
(attribute.attribute? && attribute.attribute_type == :unknown) ||
|
|
67
|
+
(attribute.migration? && attribute.column_type == :unknown)
|
|
68
|
+
)
|
|
69
|
+
@unknown_attributes << "#{attribute.name}:#{attribute.type}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless @forbidden_names.empty?
|
|
74
|
+
raise Thor::Error,
|
|
75
|
+
"Names #{@forbidden_names.uniq.join(', ')} are install-time only " \
|
|
76
|
+
"and not supported by inline_forms_addto."
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
allow_unknown = options[:allow_unknown].to_s == "true"
|
|
80
|
+
if !allow_unknown && !@unknown_attributes.empty?
|
|
81
|
+
raise Thor::Error,
|
|
82
|
+
"Unknown field type(s): #{@unknown_attributes.uniq.join(', ')}. " \
|
|
83
|
+
"Add a valid form element type or pass --allow-unknown to keep legacy commented output."
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@skipped_replace_names.each do |label|
|
|
87
|
+
say_status :skipped, "#{label} (pass --replace to rewrite the existing scope/method)", :yellow
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def generate_migration
|
|
92
|
+
@migration_lines = String.new
|
|
93
|
+
attributes.each do |attribute|
|
|
94
|
+
next if INSTALL_TIME_ONLY_NAMES.include?(attribute.name)
|
|
95
|
+
next if REPLACE_ONLY_NAMES.include?(attribute.name)
|
|
96
|
+
next unless attribute.migration?
|
|
97
|
+
|
|
98
|
+
if attribute.column_type == :belongs_to
|
|
99
|
+
@migration_lines << " add_reference :#{table_name}, :#{attribute.name}, foreign_key: true\n"
|
|
100
|
+
else
|
|
101
|
+
commenter = attribute.attribute_type == :unknown ? "#" : " "
|
|
102
|
+
@migration_lines << "#{commenter} add_column :#{table_name}, :#{attribute.name}, :#{attribute.column_type}\n"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
return if @migration_lines.empty?
|
|
107
|
+
|
|
108
|
+
template "add_columns_migration.erb",
|
|
109
|
+
"db/migrate/#{time_stamp}_#{migration_file_basename}.rb"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def update_model
|
|
113
|
+
attributes.each do |attribute|
|
|
114
|
+
next if INSTALL_TIME_ONLY_NAMES.include?(attribute.name)
|
|
115
|
+
next if REPLACE_ONLY_NAMES.include?(attribute.name) && !options[:replace]
|
|
116
|
+
|
|
117
|
+
if REPLACE_ONLY_NAMES.include?(attribute.name) && options[:replace]
|
|
118
|
+
replace_special!(attribute)
|
|
119
|
+
next
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
case attribute.column_type
|
|
123
|
+
when :belongs_to
|
|
124
|
+
inject_class_line!(" belongs_to :#{attribute.name}\n")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
case attribute.type
|
|
128
|
+
when :image_field, :audio_field
|
|
129
|
+
uploader_class = "#{attribute.name}_uploader".camelcase
|
|
130
|
+
inject_class_line!(" mount_uploader :#{attribute.name}, #{uploader_class}\n")
|
|
131
|
+
when :has_many
|
|
132
|
+
inject_class_line!(" has_many :#{attribute.name}\n")
|
|
133
|
+
when :has_many_destroy
|
|
134
|
+
inject_class_line!(" has_many :#{attribute.name}, :dependent => :destroy\n")
|
|
135
|
+
when :has_one
|
|
136
|
+
inject_class_line!(" has_one :#{attribute.name}\n")
|
|
137
|
+
when :habtm, :has_and_belongs_to_many, :check_list
|
|
138
|
+
inject_class_line!(" has_and_belongs_to_many :#{attribute.name}\n")
|
|
139
|
+
when :rich_text
|
|
140
|
+
inject_class_line!(" has_rich_text :#{attribute.name}\n")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
next unless attribute.attribute?
|
|
144
|
+
|
|
145
|
+
add_attribute_list_row!(attribute)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def model_file_name
|
|
152
|
+
name.underscore
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def model_file_path
|
|
156
|
+
"app/models/#{model_file_name}.rb"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def model_class_name
|
|
160
|
+
name.camelize
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def table_name
|
|
164
|
+
name.pluralize.underscore
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def migration_class_name
|
|
168
|
+
"InlineFormsAddTo#{table_name.camelize}#{migration_class_suffix}"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Suffix keeps re-runs from colliding (Rails would reject duplicate
|
|
172
|
+
# migration class names). Built from the column names being added.
|
|
173
|
+
def migration_class_suffix
|
|
174
|
+
cols = attributes.reject { |a| INSTALL_TIME_ONLY_NAMES.include?(a.name) || REPLACE_ONLY_NAMES.include?(a.name) }
|
|
175
|
+
.map { |a| a.name.camelize }
|
|
176
|
+
cols.empty? ? "Fields" : cols.first(3).join
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def migration_file_basename
|
|
180
|
+
cols = attributes.reject { |a| INSTALL_TIME_ONLY_NAMES.include?(a.name) || REPLACE_ONLY_NAMES.include?(a.name) }
|
|
181
|
+
.map(&:name)
|
|
182
|
+
label = cols.first(3).join("_")
|
|
183
|
+
label = "fields" if label.empty?
|
|
184
|
+
"inline_forms_add_to_#{table_name}_#{label}"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def time_stamp
|
|
188
|
+
# Mirrors InlineFormsGenerator#time_stamp.
|
|
189
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Idempotent inject: skip if `line` (sans trailing newline) already
|
|
193
|
+
# appears inside the model file. Uses `inject_into_class` so the
|
|
194
|
+
# injection lands at the top of the class body (after `class Foo <
|
|
195
|
+
# ApplicationRecord`).
|
|
196
|
+
def inject_class_line!(line)
|
|
197
|
+
content = File.read(File.join(destination_root, model_file_path))
|
|
198
|
+
probe = line.rstrip
|
|
199
|
+
if content.include?(probe)
|
|
200
|
+
say_status :identical, "#{model_file_path}: #{probe.strip}", :blue
|
|
201
|
+
return
|
|
202
|
+
end
|
|
203
|
+
inject_into_class model_file_path, model_class_name, line
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Inserts a new row into the existing `inline_forms_attribute_list`
|
|
207
|
+
# method. Falls back to appending a fresh method when the model has
|
|
208
|
+
# only `attr_writer :inline_forms_attribute_list` (inherited from
|
|
209
|
+
# `ApplicationRecord`) and no body.
|
|
210
|
+
def add_attribute_list_row!(attribute)
|
|
211
|
+
content = File.read(File.join(destination_root, model_file_path))
|
|
212
|
+
row = " [ :#{attribute.name} , \"#{attribute.name}\", :#{attribute.attribute_type} ],\n"
|
|
213
|
+
|
|
214
|
+
if content.match?(/@inline_forms_attribute_list \|\|=\s*\[/)
|
|
215
|
+
if content.match?(/\[\s*:#{Regexp.escape(attribute.name)}\s*,/)
|
|
216
|
+
say_status :identical, "#{model_file_path}: row :#{attribute.name}", :blue
|
|
217
|
+
return
|
|
218
|
+
end
|
|
219
|
+
# Insert before the closing `]` of the array, keeping appended order.
|
|
220
|
+
# Match against the file's full text and write it back so we work
|
|
221
|
+
# around Thor's `gsub_file` block-arg quirk (the block sees only the
|
|
222
|
+
# full match, no $1/$2). MatchData captures are explicit here.
|
|
223
|
+
m = content.match(/(@inline_forms_attribute_list \|\|=\s*\[(?:.|\n)*?)(\n\s*\]\s*\n\s*end)/)
|
|
224
|
+
if m
|
|
225
|
+
replacement = m[1] + "\n" + row.chomp + m[2]
|
|
226
|
+
new_content = content.sub(m[0], replacement)
|
|
227
|
+
File.write(File.join(destination_root, model_file_path), new_content)
|
|
228
|
+
say_status :insert, "#{model_file_path}: row :#{attribute.name}", :green
|
|
229
|
+
else
|
|
230
|
+
say_status :warn, "#{model_file_path}: could not locate end of @inline_forms_attribute_list, skipping :#{attribute.name}", :yellow
|
|
231
|
+
end
|
|
232
|
+
else
|
|
233
|
+
say_status :warn, "#{model_file_path}: no inline_forms_attribute_list found, appending a fresh method", :yellow
|
|
234
|
+
body = <<~RUBY.gsub(/^/, " ")
|
|
235
|
+
|
|
236
|
+
def inline_forms_attribute_list
|
|
237
|
+
@inline_forms_attribute_list ||= [
|
|
238
|
+
[ :#{attribute.name} , "#{attribute.name}", :#{attribute.attribute_type} ],
|
|
239
|
+
]
|
|
240
|
+
end
|
|
241
|
+
RUBY
|
|
242
|
+
inject_into_class model_file_path, model_class_name, body
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def replace_special!(attribute)
|
|
247
|
+
case attribute.name
|
|
248
|
+
when "_presentation"
|
|
249
|
+
new_method = " def _presentation\n \"#{attribute.type}\"\n end\n"
|
|
250
|
+
if File.read(File.join(destination_root, model_file_path)).match?(/def _presentation\b/)
|
|
251
|
+
gsub_file model_file_path,
|
|
252
|
+
/ def _presentation\b.*?\n end\n/m,
|
|
253
|
+
new_method
|
|
254
|
+
else
|
|
255
|
+
inject_into_class model_file_path, model_class_name, "\n#{new_method}"
|
|
256
|
+
end
|
|
257
|
+
when "_list_order", "_order"
|
|
258
|
+
col = attribute.type
|
|
259
|
+
new_scope = " scope :inline_forms_list, -> { order(:#{col}, :id) }\n"
|
|
260
|
+
new_spaceship = " def <=>(other)\n self.#{col} <=> other.#{col}\n end\n"
|
|
261
|
+
path = File.join(destination_root, model_file_path)
|
|
262
|
+
content = File.read(path)
|
|
263
|
+
if content.match?(/scope :inline_forms_list\b/)
|
|
264
|
+
gsub_file model_file_path, / scope :inline_forms_list, ->.*\n/, new_scope
|
|
265
|
+
else
|
|
266
|
+
inject_into_class model_file_path, model_class_name, new_scope
|
|
267
|
+
end
|
|
268
|
+
if content.match?(/def <=>\(other\)/)
|
|
269
|
+
gsub_file model_file_path, / def <=>\(other\).*?\n end\n/m, new_spaceship
|
|
270
|
+
else
|
|
271
|
+
inject_into_class model_file_path, model_class_name, "\n#{new_spaceship}"
|
|
272
|
+
end
|
|
273
|
+
when "_list_search"
|
|
274
|
+
col = attribute.type
|
|
275
|
+
new_scope = " scope :inline_forms_search, ->(q) { where(\"#{col} LIKE ?\", \"%\#{q}%\") }\n"
|
|
276
|
+
if File.read(File.join(destination_root, model_file_path)).match?(/scope :inline_forms_search\b/)
|
|
277
|
+
gsub_file model_file_path, / scope :inline_forms_search, ->.*\n/, new_scope
|
|
278
|
+
else
|
|
279
|
+
inject_into_class model_file_path, model_class_name, new_scope
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
module InlineForms
|
|
286
|
+
# Backwards-compatible alias (tests and any programmatic invokers that
|
|
287
|
+
# addressed the namespaced version while the generator was scoped to
|
|
288
|
+
# `module InlineForms`). Rails generator discovery uses the top-level
|
|
289
|
+
# constant above.
|
|
290
|
+
InlineFormsAddtoGenerator = ::InlineFormsAddtoGenerator unless const_defined?(:InlineFormsAddtoGenerator, false)
|
|
291
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
require "rails/generators"
|
|
3
|
+
require "rails/generators/generated_attribute"
|
|
4
|
+
require "inline_forms"
|
|
5
|
+
|
|
6
|
+
# Shared `Rails::Generators::GeneratedAttribute` extensions used by both
|
|
7
|
+
# `InlineForms::InlineFormsGenerator` (`rails g inline_forms`) and
|
|
8
|
+
# `InlineForms::InlineFormsAddtoGenerator` (`rails g inline_forms_addto`).
|
|
9
|
+
#
|
|
10
|
+
# Loaded once; idempotent (the `class_eval` re-defines methods to the
|
|
11
|
+
# same bodies on a re-require, no behavior drift).
|
|
12
|
+
Rails::Generators::GeneratedAttribute.class_eval do
|
|
13
|
+
# Override Rails::Generators::GeneratedAttribute.valid_type? so that our
|
|
14
|
+
# custom field types (dropdown, check_list, image_field, rich_text, ...)
|
|
15
|
+
# pass through parsing. We do our own unknown-type detection later (with
|
|
16
|
+
# Thor::Error + --allow-unknown), so it is safe to accept everything here.
|
|
17
|
+
#
|
|
18
|
+
# Rails 6.1 used to rescue ActiveRecord::Base.connection failures, which
|
|
19
|
+
# masked the issue; Rails 7+ raises NameError when ActiveRecord is not
|
|
20
|
+
# loaded yet, breaking generator unit tests.
|
|
21
|
+
def self.valid_type?(_type)
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Deducts the column_type for migrations from the type.
|
|
26
|
+
#
|
|
27
|
+
# We first merge the Special Column Types with the Default Column Types,
|
|
28
|
+
# which has the effect that the Default Column Types with the same key
|
|
29
|
+
# override the Special Column Types.
|
|
30
|
+
#
|
|
31
|
+
# If the type is not in the merged hash, then column_type defaults to :unknown
|
|
32
|
+
#
|
|
33
|
+
# You are advised to check your migrations for the :unknown, because either you made a
|
|
34
|
+
# typo in the generator command line or you need to add a Form Element!
|
|
35
|
+
def column_type
|
|
36
|
+
InlineForms::SPECIAL_COLUMN_TYPES
|
|
37
|
+
.merge(InlineForms::DEFAULT_COLUMN_TYPES)
|
|
38
|
+
.merge(InlineForms::RELATIONS)
|
|
39
|
+
.merge(InlineForms::SPECIAL_RELATIONS)[type] || :unknown
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Override the attribute_type to include our special column types.
|
|
43
|
+
#
|
|
44
|
+
# If a type is not in the Special Column Type hash, then the default
|
|
45
|
+
# column type hash is used, and if that fails, the attribute_type
|
|
46
|
+
# will be :unknown. Make sure to check your models for the :unknown.
|
|
47
|
+
def attribute_type
|
|
48
|
+
if InlineForms::SPECIAL_COLUMN_TYPES.merge(InlineForms::RELATIONS).has_key?(type)
|
|
49
|
+
type
|
|
50
|
+
else
|
|
51
|
+
InlineForms::DEFAULT_FORM_ELEMENTS[type] || :unknown
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def special_relation?
|
|
56
|
+
InlineForms::SPECIAL_RELATIONS.has_key?(type)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def relation?
|
|
60
|
+
InlineForms::RELATIONS.has_key?(type) || special_relation?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Special "attribute" names that drive the generator (presentation,
|
|
64
|
+
# ordering, search, etc.) but never become real columns or fields.
|
|
65
|
+
SPECIAL_GENERATOR_NAMES = %w[
|
|
66
|
+
_presentation
|
|
67
|
+
_order
|
|
68
|
+
_list_order
|
|
69
|
+
_list_search
|
|
70
|
+
_enabled
|
|
71
|
+
_id
|
|
72
|
+
_no_migration
|
|
73
|
+
_no_model
|
|
74
|
+
].freeze unless const_defined?(:SPECIAL_GENERATOR_NAMES)
|
|
75
|
+
|
|
76
|
+
def migration?
|
|
77
|
+
not ( column_type == :no_migration ||
|
|
78
|
+
SPECIAL_GENERATOR_NAMES.include?(name) )
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def attribute?
|
|
82
|
+
not ( SPECIAL_GENERATOR_NAMES.include?(name) ||
|
|
83
|
+
relation? )
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
|
2
2
|
require "inline_forms"
|
|
3
|
+
require_relative "inline_forms_attribute_overrides"
|
|
3
4
|
module InlineForms
|
|
4
5
|
# == Usage
|
|
5
6
|
# This generator generates a migration, a model and a controller.
|
|
@@ -13,82 +14,12 @@ module InlineForms
|
|
|
13
14
|
# rails g example_generator Modelname attribute:type attribute:type ...
|
|
14
15
|
# an array with attributes and types is created for use in the generator.
|
|
15
16
|
#
|
|
16
|
-
# Rails::Generators::GeneratedAttribute creates, among others,
|
|
17
|
+
# Rails::Generators::GeneratedAttribute creates, among others, an attribute_type.
|
|
17
18
|
# This attribute_type maps column types to form attribute helpers like text_field.
|
|
18
|
-
# We override it
|
|
19
|
+
# We override it in `lib/generators/inline_forms_attribute_overrides.rb`
|
|
20
|
+
# (shared with `InlineFormsAddtoGenerator`).
|
|
19
21
|
#
|
|
20
22
|
class InlineFormsGenerator < Rails::Generators::NamedBase
|
|
21
|
-
Rails::Generators::GeneratedAttribute.class_eval do #:doc:
|
|
22
|
-
# Override Rails::Generators::GeneratedAttribute.valid_type? so that our
|
|
23
|
-
# custom field types (dropdown, check_list, image_field, rich_text, ...)
|
|
24
|
-
# pass through parsing. We do our own unknown-type detection later (with
|
|
25
|
-
# Thor::Error + --allow-unknown), so it is safe to accept everything here.
|
|
26
|
-
#
|
|
27
|
-
# Rails 6.1 used to rescue ActiveRecord::Base.connection failures, which
|
|
28
|
-
# masked the issue; Rails 7+ raises NameError when ActiveRecord is not
|
|
29
|
-
# loaded yet, breaking generator unit tests.
|
|
30
|
-
def self.valid_type?(_type)
|
|
31
|
-
true
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Deducts the column_type for migrations from the type.
|
|
35
|
-
#
|
|
36
|
-
# We first merge the Special Column Types with the Default Column Types,
|
|
37
|
-
# which has the effect that the Default Column Types with the same key override
|
|
38
|
-
# the Special Column Types.
|
|
39
|
-
#
|
|
40
|
-
# If the type is not in the merged hash, then column_type defaults to :unknown
|
|
41
|
-
#
|
|
42
|
-
# You are advised to check you migrations for the :unknown, because either you made a
|
|
43
|
-
# typo in the generator command line or you need to add a Form Element!
|
|
44
|
-
#
|
|
45
|
-
def column_type
|
|
46
|
-
SPECIAL_COLUMN_TYPES.merge(DEFAULT_COLUMN_TYPES).merge(RELATIONS).merge(SPECIAL_RELATIONS)[type] || :unknown
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Override the attribute_type to include our special column types.
|
|
50
|
-
#
|
|
51
|
-
# If a type is not in the Special Column Type hash, then the default
|
|
52
|
-
# column type hash is used, and if that fails, the attribute_type
|
|
53
|
-
# will be :unknown. Make sure to check your models for the :unknown.
|
|
54
|
-
#
|
|
55
|
-
def attribute_type
|
|
56
|
-
SPECIAL_COLUMN_TYPES.merge(RELATIONS).has_key?(type) ? type : DEFAULT_FORM_ELEMENTS[type] || :unknown
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def special_relation?
|
|
60
|
-
SPECIAL_RELATIONS.has_key?(type)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def relation?
|
|
64
|
-
RELATIONS.has_key?(type) || special_relation?
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Special "attribute" names that drive the generator (presentation,
|
|
68
|
-
# ordering, search, etc.) but never become real columns or fields.
|
|
69
|
-
SPECIAL_GENERATOR_NAMES = %w[
|
|
70
|
-
_presentation
|
|
71
|
-
_order
|
|
72
|
-
_list_order
|
|
73
|
-
_list_search
|
|
74
|
-
_enabled
|
|
75
|
-
_id
|
|
76
|
-
_no_migration
|
|
77
|
-
_no_model
|
|
78
|
-
].freeze
|
|
79
|
-
|
|
80
|
-
def migration?
|
|
81
|
-
not ( column_type == :no_migration ||
|
|
82
|
-
SPECIAL_GENERATOR_NAMES.include?(name) )
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def attribute?
|
|
86
|
-
not ( SPECIAL_GENERATOR_NAMES.include?(name) ||
|
|
87
|
-
relation? )
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
end
|
|
92
23
|
argument :attributes, :type => :array, :banner => "[name:form_element]..."
|
|
93
24
|
class_option :allow_unknown, :type => :boolean, :default => false, :desc => "Allow unknown field types (legacy behavior: comment generated lines instead of failing)."
|
|
94
25
|
|
data/lib/inline_forms/version.rb
CHANGED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "logger"
|
|
7
|
+
require "rails"
|
|
8
|
+
require "rails/generators"
|
|
9
|
+
require "inline_forms"
|
|
10
|
+
require_relative "../lib/generators/inline_forms_addto_generator"
|
|
11
|
+
|
|
12
|
+
class InlineFormsAddtoGeneratorTest < Minitest::Test
|
|
13
|
+
GENERATOR_SHAPED_MODEL = <<~RUBY
|
|
14
|
+
class Widget < ApplicationRecord
|
|
15
|
+
|
|
16
|
+
belongs_to :category
|
|
17
|
+
|
|
18
|
+
def inline_forms_attribute_list
|
|
19
|
+
@inline_forms_attribute_list ||= [
|
|
20
|
+
[ :name , "name", :text_field ],
|
|
21
|
+
[ :category , "category", :belongs_to ],
|
|
22
|
+
]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
RUBY
|
|
27
|
+
|
|
28
|
+
INSTALLER_SHAPED_USER = <<~RUBY
|
|
29
|
+
class User < ApplicationRecord
|
|
30
|
+
|
|
31
|
+
devise :database_authenticatable
|
|
32
|
+
belongs_to :locale
|
|
33
|
+
has_and_belongs_to_many :roles
|
|
34
|
+
|
|
35
|
+
def _presentation
|
|
36
|
+
"\#{name}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inline_forms_attribute_list
|
|
40
|
+
@inline_forms_attribute_list ||= [
|
|
41
|
+
[ :header_user_login, '', :header ],
|
|
42
|
+
[ :name, '', :text_field ],
|
|
43
|
+
[ :email, '', :text_field ],
|
|
44
|
+
[ :locale , '', :dropdown ],
|
|
45
|
+
[ :password, '', :devise_password_field ],
|
|
46
|
+
[ :header_user_roles, '', :header ],
|
|
47
|
+
[ :roles, '', :check_list ],
|
|
48
|
+
]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
RUBY
|
|
53
|
+
|
|
54
|
+
BARE_MODEL = <<~RUBY
|
|
55
|
+
class Bare < ApplicationRecord
|
|
56
|
+
end
|
|
57
|
+
RUBY
|
|
58
|
+
|
|
59
|
+
def setup
|
|
60
|
+
@destination_root = Dir.mktmpdir("inline_forms_addto_generator_test")
|
|
61
|
+
mkdir_p("app/models")
|
|
62
|
+
mkdir_p("db/migrate")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def teardown
|
|
66
|
+
FileUtils.remove_entry(@destination_root) if @destination_root && Dir.exist?(@destination_root)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_raises_when_model_file_missing
|
|
70
|
+
stdout, stderr = capture_io do
|
|
71
|
+
run_generator("DoesNotExist", "occupation:string")
|
|
72
|
+
end
|
|
73
|
+
output = "#{stdout}#{stderr}"
|
|
74
|
+
assert_includes(output, "rails g inline_forms DoesNotExist")
|
|
75
|
+
refute(File.exist?(File.join(@destination_root, "app/models/does_not_exist.rb")))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_adds_scalar_column_belongs_to_rich_text_and_image_field_to_existing_model
|
|
79
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
80
|
+
|
|
81
|
+
run_generator(
|
|
82
|
+
"Widget",
|
|
83
|
+
"occupation:string",
|
|
84
|
+
"organization:belongs_to",
|
|
85
|
+
"supplier:dropdown",
|
|
86
|
+
"bio:rich_text",
|
|
87
|
+
"avatar:image_field"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
model = read("app/models/widget.rb")
|
|
91
|
+
migration = read_single_addto_migration_for("widgets")
|
|
92
|
+
|
|
93
|
+
assert_includes(model, "belongs_to :organization")
|
|
94
|
+
assert_includes(model, "belongs_to :supplier")
|
|
95
|
+
assert_includes(model, "has_rich_text :bio")
|
|
96
|
+
assert_includes(model, "mount_uploader :avatar, AvatarUploader")
|
|
97
|
+
assert_includes(model, '[ :occupation , "occupation", :text_field ]')
|
|
98
|
+
# :belongs_to is a relation -> no row in inline_forms_attribute_list
|
|
99
|
+
# (matches InlineFormsGenerator semantics). :dropdown is not a relation
|
|
100
|
+
# at lookup time, so it does get a row.
|
|
101
|
+
refute_includes(model, '[ :organization , "organization", :belongs_to ]')
|
|
102
|
+
assert_includes(model, '[ :supplier , "supplier", :dropdown ]')
|
|
103
|
+
assert_includes(model, '[ :avatar , "avatar", :image_field ]')
|
|
104
|
+
|
|
105
|
+
assert_includes(migration, "add_column :widgets, :occupation, :string")
|
|
106
|
+
assert_includes(migration, "add_reference :widgets, :organization, foreign_key: true")
|
|
107
|
+
assert_includes(migration, "add_reference :widgets, :supplier, foreign_key: true")
|
|
108
|
+
assert_includes(migration, "add_column :widgets, :avatar, :string")
|
|
109
|
+
refute_includes(migration, ":bio")
|
|
110
|
+
refute_includes(migration, "create_table")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_idempotent_rerun_does_not_duplicate_lines
|
|
114
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
115
|
+
|
|
116
|
+
run_generator("Widget", "occupation:string", "organization:belongs_to")
|
|
117
|
+
sleep 1 # unique migration timestamp on second run
|
|
118
|
+
run_generator("Widget", "occupation:string", "organization:belongs_to")
|
|
119
|
+
|
|
120
|
+
model = read("app/models/widget.rb")
|
|
121
|
+
|
|
122
|
+
assert_equal(1, model.scan("belongs_to :organization").size)
|
|
123
|
+
assert_equal(1, model.scan('[ :occupation , "occupation", :text_field ]').size)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_appends_row_to_installer_shaped_user_attribute_list
|
|
127
|
+
write_model("user.rb", INSTALLER_SHAPED_USER)
|
|
128
|
+
|
|
129
|
+
run_generator("User", "occupation:string", "birthdate:date")
|
|
130
|
+
|
|
131
|
+
model = read("app/models/user.rb")
|
|
132
|
+
|
|
133
|
+
assert_includes(model, '[ :occupation , "occupation", :text_field ]')
|
|
134
|
+
assert_includes(model, '[ :birthdate , "birthdate", :date_select ]')
|
|
135
|
+
refute_match(/\]\s*\n\s*\[\s*:occupation/, model)
|
|
136
|
+
|
|
137
|
+
user_array_section = model[/@inline_forms_attribute_list \|\|=\s*\[(.|\n)*?\n\s*\]/]
|
|
138
|
+
assert(user_array_section, "could not locate @inline_forms_attribute_list array")
|
|
139
|
+
assert_match(/\[ :roles,.*\].*?\[ :occupation\b/m, user_array_section)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_bare_model_gets_fresh_attribute_list_method
|
|
143
|
+
write_model("bare.rb", BARE_MODEL)
|
|
144
|
+
|
|
145
|
+
out, _err = capture_io do
|
|
146
|
+
run_generator("Bare", "occupation:string")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
model = read("app/models/bare.rb")
|
|
150
|
+
|
|
151
|
+
assert_includes(out, "no inline_forms_attribute_list found")
|
|
152
|
+
assert_includes(model, "def inline_forms_attribute_list")
|
|
153
|
+
assert_includes(model, '[ :occupation , "occupation", :text_field ]')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_unknown_type_raises_thor_error_by_default
|
|
157
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
158
|
+
|
|
159
|
+
stdout, stderr = capture_io do
|
|
160
|
+
run_generator("Widget", "payload:not_a_real_type")
|
|
161
|
+
end
|
|
162
|
+
output = "#{stdout}#{stderr}"
|
|
163
|
+
assert_includes(output, "Unknown field type(s): payload:not_a_real_type")
|
|
164
|
+
assert_includes(output, "--allow-unknown")
|
|
165
|
+
|
|
166
|
+
refute_addto_migration_for("widgets")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_allow_unknown_keeps_legacy_commented_output
|
|
170
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
171
|
+
|
|
172
|
+
run_generator("Widget", "payload:not_a_real_type", "--allow-unknown")
|
|
173
|
+
|
|
174
|
+
model = read("app/models/widget.rb")
|
|
175
|
+
migration = read_single_addto_migration_for("widgets")
|
|
176
|
+
|
|
177
|
+
assert_includes(model, '[ :payload , "payload", :unknown ]')
|
|
178
|
+
assert_includes(migration, "# add_column :widgets, :payload, :unknown")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def test_install_time_only_names_are_rejected
|
|
182
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
183
|
+
|
|
184
|
+
%w[_no_model _no_migration _id _enabled].each do |forbidden|
|
|
185
|
+
stdout, stderr = capture_io do
|
|
186
|
+
run_generator("Widget", "#{forbidden}:yes")
|
|
187
|
+
end
|
|
188
|
+
output = "#{stdout}#{stderr}"
|
|
189
|
+
assert_includes(output, forbidden, "expected error to mention #{forbidden}")
|
|
190
|
+
assert_includes(output, "install-time only")
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_skips_replace_only_names_without_replace_flag
|
|
195
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
196
|
+
|
|
197
|
+
out, _err = capture_io do
|
|
198
|
+
run_generator("Widget", "_list_order:title")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
assert_includes(out, "_list_order:title")
|
|
202
|
+
assert_includes(out, "skipped")
|
|
203
|
+
model = read("app/models/widget.rb")
|
|
204
|
+
refute_includes(model, "scope :inline_forms_list, -> { order(:title, :id) }")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_replace_flag_rewrites_list_order_scope
|
|
208
|
+
initial = GENERATOR_SHAPED_MODEL.sub(
|
|
209
|
+
/class Widget < ApplicationRecord\n/,
|
|
210
|
+
"class Widget < ApplicationRecord\n\n scope :inline_forms_list, -> { order(:name, :id) }\n def <=>(other)\n self.name <=> other.name\n end\n"
|
|
211
|
+
)
|
|
212
|
+
write_model("widget.rb", initial)
|
|
213
|
+
|
|
214
|
+
run_generator("Widget", "_list_order:title", "--replace")
|
|
215
|
+
|
|
216
|
+
model = read("app/models/widget.rb")
|
|
217
|
+
assert_includes(model, "scope :inline_forms_list, -> { order(:title, :id) }")
|
|
218
|
+
refute_includes(model, "order(:name, :id)")
|
|
219
|
+
assert_includes(model, "self.title <=> other.title")
|
|
220
|
+
refute_includes(model, "self.name <=> other.name")
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def run_generator(*args)
|
|
226
|
+
InlineForms::InlineFormsAddtoGenerator.start(args, destination_root: @destination_root)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def write_model(file, content)
|
|
230
|
+
File.write(File.join(@destination_root, "app/models", file), content)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def read(relative_path)
|
|
234
|
+
File.read(File.join(@destination_root, relative_path))
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def mkdir_p(relative_path)
|
|
238
|
+
FileUtils.mkdir_p(File.join(@destination_root, relative_path))
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def read_single_addto_migration_for(table_name)
|
|
242
|
+
files = Dir.glob(File.join(@destination_root, "db/migrate/*_inline_forms_add_to_#{table_name}_*.rb"))
|
|
243
|
+
assert_equal(1, files.size, "Expected one addto migration for #{table_name}, got #{files.inspect}")
|
|
244
|
+
File.read(files.first)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def refute_addto_migration_for(table_name)
|
|
248
|
+
files = Dir.glob(File.join(@destination_root, "db/migrate/*_inline_forms_add_to_#{table_name}_*.rb"))
|
|
249
|
+
assert_equal([], files, "Expected no addto migration for #{table_name}")
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -56,7 +56,7 @@ class InlineFormsGeneratorTest < Minitest::Test
|
|
|
56
56
|
|
|
57
57
|
assert_includes(application_controller, "MODEL_TABS = %w(things ")
|
|
58
58
|
|
|
59
|
-
assert_includes(migration, "class InlineFormsCreateThings < ActiveRecord::Migration[8.
|
|
59
|
+
assert_includes(migration, "class InlineFormsCreateThings < ActiveRecord::Migration[8.1]")
|
|
60
60
|
assert_includes(migration, "create_table :things do |t|")
|
|
61
61
|
assert_includes(migration, "t.string :name")
|
|
62
62
|
assert_includes(migration, "t.belongs_to :category")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inline_forms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.1.
|
|
4
|
+
version: 8.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ace Suares
|
|
@@ -17,7 +17,7 @@ dependencies:
|
|
|
17
17
|
requirements:
|
|
18
18
|
- - ">="
|
|
19
19
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: 8.1.
|
|
20
|
+
version: 8.1.2
|
|
21
21
|
- - "<"
|
|
22
22
|
- !ruby/object:Gem::Version
|
|
23
23
|
version: '9.0'
|
|
@@ -27,7 +27,7 @@ dependencies:
|
|
|
27
27
|
requirements:
|
|
28
28
|
- - ">="
|
|
29
29
|
- !ruby/object:Gem::Version
|
|
30
|
-
version: 8.1.
|
|
30
|
+
version: 8.1.2
|
|
31
31
|
- - "<"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '9.0'
|
|
@@ -37,27 +37,27 @@ dependencies:
|
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '8.
|
|
40
|
+
version: '8.1'
|
|
41
41
|
- - "<"
|
|
42
42
|
- !ruby/object:Gem::Version
|
|
43
|
-
version: '8.
|
|
43
|
+
version: '8.2'
|
|
44
44
|
type: :runtime
|
|
45
45
|
prerelease: false
|
|
46
46
|
version_requirements: !ruby/object:Gem::Requirement
|
|
47
47
|
requirements:
|
|
48
48
|
- - ">="
|
|
49
49
|
- !ruby/object:Gem::Version
|
|
50
|
-
version: '8.
|
|
50
|
+
version: '8.1'
|
|
51
51
|
- - "<"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '8.
|
|
53
|
+
version: '8.2'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: rails-i18n
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '8.
|
|
60
|
+
version: '8.1'
|
|
61
61
|
- - "<"
|
|
62
62
|
- !ruby/object:Gem::Version
|
|
63
63
|
version: '9.0'
|
|
@@ -67,7 +67,7 @@ dependencies:
|
|
|
67
67
|
requirements:
|
|
68
68
|
- - ">="
|
|
69
69
|
- !ruby/object:Gem::Version
|
|
70
|
-
version: '8.
|
|
70
|
+
version: '8.1'
|
|
71
71
|
- - "<"
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
73
|
version: '9.0'
|
|
@@ -86,7 +86,7 @@ dependencies:
|
|
|
86
86
|
- !ruby/object:Gem::Version
|
|
87
87
|
version: '5.0'
|
|
88
88
|
description: Inline Forms eases setup of admin-style forms with inline editing. Field
|
|
89
|
-
lists are declared on the model. Requires Rails 8.
|
|
89
|
+
lists are declared on the model. Requires Rails 8.1.x, Ruby >= 4.0, and validation_hints
|
|
90
90
|
~> 8.
|
|
91
91
|
email:
|
|
92
92
|
- ace@suares.com
|
|
@@ -495,8 +495,11 @@ files:
|
|
|
495
495
|
- inline_forms.gemspec
|
|
496
496
|
- lib/generators/USAGE
|
|
497
497
|
- lib/generators/assets/stylesheets/inline_forms_devise.css
|
|
498
|
+
- lib/generators/inline_forms_addto_generator.rb
|
|
499
|
+
- lib/generators/inline_forms_attribute_overrides.rb
|
|
498
500
|
- lib/generators/inline_forms_generator.rb
|
|
499
501
|
- lib/generators/templates/_inline_forms_tabs.html.erb
|
|
502
|
+
- lib/generators/templates/add_columns_migration.erb
|
|
500
503
|
- lib/generators/templates/application_record.rb
|
|
501
504
|
- lib/generators/templates/controller.erb
|
|
502
505
|
- lib/generators/templates/migration.erb
|
|
@@ -558,6 +561,7 @@ files:
|
|
|
558
561
|
- test/archived_form_elements_test.rb
|
|
559
562
|
- test/form_element_from_callee_test.rb
|
|
560
563
|
- test/inline_edit_polymorphic_path_test.rb
|
|
564
|
+
- test/inline_forms_addto_generator_test.rb
|
|
561
565
|
- test/inline_forms_generator_test.rb
|
|
562
566
|
- test/plain_text_configuration_test.rb
|
|
563
567
|
- test/test_helper.rb
|
|
@@ -590,6 +594,7 @@ test_files:
|
|
|
590
594
|
- test/archived_form_elements_test.rb
|
|
591
595
|
- test/form_element_from_callee_test.rb
|
|
592
596
|
- test/inline_edit_polymorphic_path_test.rb
|
|
597
|
+
- test/inline_forms_addto_generator_test.rb
|
|
593
598
|
- test/inline_forms_generator_test.rb
|
|
594
599
|
- test/plain_text_configuration_test.rb
|
|
595
600
|
- test/test_helper.rb
|