hibiki_rails 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b661448cbe2b84a20e26f5cd0bffaf2cf7be8c755f0f1b9c692f77e9f5798430
4
- data.tar.gz: b09c3b35032a6990ab08c17e424a147e9e667905a4f9e9378062d52583e213d6
3
+ metadata.gz: a8ac38db2808ab9f20f6fccf7cf50c202454fc222cba122f6466e25cf5fe2eb3
4
+ data.tar.gz: 89b5a3ab98b4bf10ba10466de9f352d9e9e3c3a814abb35175df798aa4fafc89
5
5
  SHA512:
6
- metadata.gz: dc4013a0867b606341dd9af0135a82ab9e5a8c6b5f5cabd788eea48e0f3b604a708d31aa21c044f8639a5f2951242986b710d893b968246d03703359d562235b
7
- data.tar.gz: d407a4c966366e5b11d9db6eaf7e789dcd4e086ca028dad0b2bc9b69bc4ec8f6b110339e338894b51851aa8ace18f08ef2339ce9198cf7a9226dc48d11b8c592
6
+ metadata.gz: 1d970c278b67be036e36a4703e3a38f6581b27bb890a4a7b599000755085a74f311646ae1519752e6457633590326bb9e6ca0fcf1b7fb1925e784d5f3a2c70b3
7
+ data.tar.gz: 468b0706478f48ee97019baf35b4da08912647b7a649575b8f970d05a21186cb85cf9c6c5dc89d9d0e6c75c6b0ad5c936c0b5cb6d8b223b5924edbb094ea2304
data/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ The gem and the npm package are released in lockstep and share these version
4
4
  numbers — `app/assets/javascripts/hibiki.js` is a single copy served both ways,
5
5
  so importmap and bundler apps always resolve identical client code.
6
6
 
7
+ ## 0.9.1 — 2026-08-18
8
+
9
+ ### Added
10
+
11
+ **`hibiki:rails:nested` creates a missing child model from the field list.**
12
+ `bin/rails g hibiki:rails:nested Song Credit role:string position:integer`
13
+ no longer refuses when `Credit` doesn't exist: the generator invokes
14
+ `active_record:model` with `song:references` prepended (kept where you put it
15
+ if you typed it yourself), then wires the edge off the argument list exactly
16
+ as if the field list had narrowed an existing schema. Run
17
+ `bin/rails db:migrate` before using the fieldset. A created child is never
18
+ ordered silently — put `position` in the field list, or pass
19
+ `--position=COLUMN` and the column joins the create-table migration. With an
20
+ existing child model nothing changes, and a missing model with no field list
21
+ still refuses, now suggesting the inline syntax.
22
+
23
+ The runtime and the packaged client did not change; the npm 0.9.1 exists only
24
+ to keep the lockstep rule.
25
+
7
26
  ## 0.9.0 — 2026-08-17
8
27
 
9
28
  ### Added
@@ -8,9 +8,11 @@ Description:
8
8
  fallback in the full-page form, and the controller's params.expect
9
9
  double-array group.
10
10
 
11
- Parent and Child models must exist and be migrated, and Child must
12
- declare belongs_to :<parent>. An optional field list narrows or orders
13
- the child's form fields; the facts stay the schema's.
11
+ When the Child model exists (migrated, with belongs_to :<parent>), an
12
+ optional field list narrows or orders its form fields; the facts stay
13
+ the schema's. When it doesn't, the field list creates it — model and
14
+ migration via active_record:model, <parent>:references prepended for
15
+ you. Run bin/rails db:migrate before using the fieldset.
14
16
 
15
17
  Depth is composition: run once per edge. A run whose Parent is itself a
16
18
  nested child (its _<parent>_fields partial exists) nests into that
@@ -20,7 +22,8 @@ Description:
20
22
  the fieldset ordered — the has_many gains an order scope, the parent
21
23
  form stamps positions from array order, and rows get ↑/↓ buttons. The
22
24
  named column is added by migration when missing; --skip-position opts
23
- out entirely.
25
+ out entirely. A created child is never ordered silently: put position
26
+ in the field list, or pass --position.
24
27
 
25
28
  Examples:
26
29
  bin/rails g hibiki:rails:nested Song Credit
@@ -28,3 +31,5 @@ Examples:
28
31
  bin/rails g hibiki:rails:nested Credit Contribution
29
32
 
30
33
  bin/rails g hibiki:rails:nested Song Credit artist:references role:string --position=rank
34
+
35
+ bin/rails g hibiki:rails:nested Song Credit role:string position:integer
@@ -22,6 +22,8 @@ module Hibiki
22
22
  # generic Hibiki::Rails::NestedActions), reactive_nested on the parent
23
23
  # form, the model wiring, the classic fields_for fallback in the
24
24
  # full-page form, and the controller's params.expect double-array group.
25
+ # A missing child model is created from the field list (model +
26
+ # migration via active_record:model, the parent reference prepended).
25
27
  #
26
28
  # Depth is composition — run once per edge. A run whose parent is
27
29
  # itself a nested child injects into the parent's own fields partial
@@ -62,6 +64,14 @@ module Hibiki
62
64
  warn_without_phlex_rails
63
65
  end
64
66
 
67
+ def create_child_model
68
+ return unless create_child?
69
+
70
+ # to_argv, never GeneratedAttribute#to_s: `title:string!` round-trips
71
+ # through to_s as "title:string{null}", which .parse then rejects.
72
+ invoke "active_record:model", child_model_argv
73
+ end
74
+
65
75
  def create_child_form
66
76
  template "child_form.rb.tt", child_form_path
67
77
  end
@@ -111,10 +121,7 @@ module Hibiki
111
121
  end
112
122
 
113
123
  def post_install
114
- if @position_migration
115
- say_status :migrate, "#{child_table_name}.#{position_column} was added — " \
116
- "run bin/rails db:migrate before using the fieldset.", :yellow
117
- end
124
+ migrate_notice
118
125
  schema.belongs_tos.each do |column|
119
126
  say_status :assoc, "using #{column.association_class_name}##{column.label_column} as " \
120
127
  "the #{column.human_name.downcase} option label — edit " \
@@ -124,6 +131,18 @@ module Hibiki
124
131
 
125
132
  private
126
133
 
134
+ # Never both: a created child's ordering column rides its
135
+ # create-table migration.
136
+ def migrate_notice
137
+ if create_child?
138
+ say_status :migrate, "#{child_class_name} was generated — " \
139
+ "run bin/rails db:migrate before using the fieldset.", :yellow
140
+ elsif @position_migration
141
+ say_status :migrate, "#{child_table_name}.#{position_column} was added — " \
142
+ "run bin/rails db:migrate before using the fieldset.", :yellow
143
+ end
144
+ end
145
+
127
146
  # The scaffold's own view layer, read from what it left on disk; an
128
147
  # explicit --phlex wins. Deep mode reads it off the parent's fields
129
148
  # partial instead of the row form.
@@ -36,8 +36,19 @@ module Hibiki
36
36
 
37
37
  def child_form_class_name = "#{child_class_name}Form"
38
38
  def child_form_path = File.join("app/forms", "#{child_class_name.underscore}_form.rb")
39
+ def child_model_path = File.join("app/models", "#{child_class_name.underscore}.rb")
39
40
  def child_table_name = child_class_name.tableize.tr("/", "_")
40
41
 
42
+ # Create mode: no constant, no model file, and a field list to build
43
+ # from. Memoized before the invoke writes the file — later calls must
44
+ # keep the preflight answer.
45
+ def create_child?
46
+ return @create_child if defined?(@create_child)
47
+
48
+ @create_child = attributes.any? && child_class_name.safe_constantize.nil? &&
49
+ !exists?(child_model_path)
50
+ end
51
+
41
52
  def child_class
42
53
  @child_class ||= begin
43
54
  klass = child_class_name.safe_constantize
@@ -53,9 +64,13 @@ module Hibiki
53
64
  end
54
65
 
55
66
  def missing_child_message
56
- "No model #{child_class_name}. hibiki:rails:nested needs the child model " \
57
- "to exist:\n bin/rails g model #{child_class_name} #{singular_name}:references " \
58
- "role:string\nMigrate, then re-run."
67
+ if exists?(child_model_path)
68
+ "#{child_class_name} has no table yet. Run bin/rails db:migrate first."
69
+ else
70
+ "No model #{child_class_name}. Pass its fields and the generator creates it " \
71
+ "(#{singular_name}:references is added for you):\n " \
72
+ "bin/rails g hibiki:rails:nested #{name} #{child} role:string"
73
+ end
59
74
  end
60
75
 
61
76
  # The child's belongs_to back to the parent. Required: the has_many's
@@ -92,9 +107,14 @@ module Hibiki
92
107
 
93
108
  # Everything that can refuse, before anything is written.
94
109
  def check_wireable!
95
- child_class
96
- parent_reflection
97
- check_parent_wireable!
110
+ if create_child?
111
+ check_parent_wireable!
112
+ check_creatable!
113
+ else
114
+ child_class
115
+ parent_reflection
116
+ check_parent_wireable!
117
+ end
98
118
  check_editable_columns!
99
119
  end
100
120
 
@@ -119,10 +139,21 @@ module Hibiki
119
139
 
120
140
  raise ::Rails::Generators::Error,
121
141
  "#{child_class_name} has no editable columns beyond " \
122
- "#{parent_reflection.foreign_key}#{" and #{position_column}" if ordered?} — " \
142
+ "#{child_foreign_key}#{" and #{position_column}" if ordered?} — " \
123
143
  "nothing to build a fieldset from."
124
144
  end
125
145
 
146
+ # A bare <parent>_id column generates no belongs_to, and the has_many's
147
+ # inverse_of and accepts_nested_attributes_for both stand on it.
148
+ def check_creatable!
149
+ fk = "#{singular_name}_id"
150
+ return if attributes.none? { !it.reference? && it.name == fk }
151
+
152
+ raise ::Rails::Generators::Error,
153
+ "Spell the parent column #{singular_name}:references — a bare #{fk} " \
154
+ "generates no belongs_to, and the nested wiring needs one."
155
+ end
156
+
126
157
  # ---- the owning resource ---------------------------------------------
127
158
  #
128
159
  # Root mode: the parent itself. Deep mode: derived from where the
@@ -182,15 +213,36 @@ module Hibiki
182
213
 
183
214
  # ---- the child schema ------------------------------------------------
184
215
  #
185
- # Always the introspection path — the child model is required — with
186
- # the scaffold's 3.6 merge when a field list narrows or orders it. The
187
- # parent's foreign key and the ordering column never become form
188
- # fields: the association assigns one, the parent form stamps the
189
- # other.
216
+ # Introspection when the child model exists (the scaffold's 3.6 merge
217
+ # when a field list narrows or orders it); the argument-only path when
218
+ # this run is creating the model — the fresh constant is not loadable
219
+ # in-process, so nothing may read it. The parent's foreign key and the
220
+ # ordering column never become form fields: the association assigns
221
+ # one, the parent form stamps the other.
222
+
223
+ def introspectable_child = create_child? ? nil : child_class
224
+
225
+ # Argument-derived in create mode: there is no reflection to ask yet.
226
+ def child_foreign_key
227
+ @child_foreign_key ||= create_child? ? "#{singular_name}_id" : parent_reflection.foreign_key.to_s
228
+ end
229
+
230
+ # What create_child_model hands to active_record:model: the parent
231
+ # reference prepended unless the field list already spells it, the
232
+ # --position column appended when ordering asked for one the list
233
+ # doesn't carry.
234
+ def child_model_argv
235
+ parent_ref = ("#{singular_name}:references" unless parent_ref_argument?)
236
+ position_arg = ("#{position_column}:integer" if ordered? && options[:position].present? &&
237
+ attributes.none? { it.name == position_column })
238
+ [child_class_name, *parent_ref, *attributes.map { to_argv(it) }, *position_arg]
239
+ end
240
+
241
+ def parent_ref_argument? = attributes.any? { it.reference? && it.name == singular_name }
190
242
 
191
243
  def full_schema
192
244
  @full_schema ||= if attributes.any?
193
- ScaffoldSchema.from_attributes(attributes, model: child_class)
245
+ ScaffoldSchema.from_attributes(attributes, model: introspectable_child)
194
246
  else
195
247
  ScaffoldSchema.from_model(child_class)
196
248
  end
@@ -206,7 +258,7 @@ module Hibiki
206
258
 
207
259
  def form_excluded?(column)
208
260
  return true if column.belongs_to? && column.association_name == singular_name.to_sym
209
- return true if column.name.to_s == parent_reflection.foreign_key.to_s
261
+ return true if column.name.to_s == child_foreign_key
210
262
 
211
263
  ordered? && column.name.to_s == position_column
212
264
  end
@@ -218,6 +270,7 @@ module Hibiki
218
270
 
219
271
  @ordered = if options[:skip_position] then false
220
272
  elsif options[:position].present? then true
273
+ elsif create_child? then attributes.any? { it.name == "position" }
221
274
  else child_class.column_names.include?("position")
222
275
  end
223
276
  end
@@ -225,7 +278,7 @@ module Hibiki
225
278
  def position_column = @position_column ||= options[:position].presence || "position"
226
279
 
227
280
  def position_migration_needed?
228
- ordered? && !child_class.column_names.include?(position_column)
281
+ ordered? && !create_child? && !child_class.column_names.include?(position_column)
229
282
  end
230
283
 
231
284
  # ---- template refs ---------------------------------------------------
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Hibiki
4
4
  module Rails
5
- VERSION = "0.9.0"
5
+ VERSION = "0.9.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hibiki_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - planetaska