voids 2.1.0 → 3.0.0

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: 2ddc0ec3f6eb944d0b91f7d661887b9ef36c7b6e02aa63606cf8eafff8a6c71a
4
- data.tar.gz: 8fe0046ebed0bf511b7ce893a8e2cc48269233f7c617469c6e80e262d953d249
3
+ metadata.gz: 66922bf400447b157fb6b27cfc70a8e9cf7da4c60629824f5f40b5661fd5336a
4
+ data.tar.gz: 506f410f8806f92fdd5cd5e1d1b3289b0932ebd84d1dc7f0a85756598acbbfaf
5
5
  SHA512:
6
- metadata.gz: 522dd880a0decbaadc5f8584e84ba822cf00f777653c829f618a7626a851a0088125c45c4ec25f382e56b7bf588a114f1a7edf5978794586bebfcb8e5a076a9c
7
- data.tar.gz: 8135b1214dc06dae3f6e2e691a250a30a4708de8b661cc1b82ad96f05020ce5b10cfc1c6586e6044dd12fa0fdb94c823ef6851df4ee20acec95e7f9916fa11be
6
+ metadata.gz: 5482cbb6ce7d7efd0729acb4274cd6fd30440ffc48b0088b15bdef99e802ab8a880c0585dacd4a28168b35499e2878f0107696c0b29db9203eed609e8bb55b82
7
+ data.tar.gz: 9d2d5befe25f3f6227f907144136f7b584b0d44a770bdb47d9cadda4f4cfca7bccffa14605dbd93e0f99fb9b1935b678672001f5cf2730ed45f85963ebe040b8
data/CHANGELOG.md CHANGED
@@ -1,12 +1,26 @@
1
- ## [Unreleased]
1
+ # [Unreleased]
2
+
3
+ - ...
4
+
5
+ # [3.0.0] - 2026-09-21
6
+
7
+ - **Breaking:** no longer add an `:invalid` error to the association itself when a nested form is invalid. Nested errors are copied to the parent under `association.attribute` / `association[index].attribute` keys only, matching `ActiveRecord`'s autosave association behavior, which `accepts_nested_attributes_for` implies. Callers that checked `errors[:association]` should check for the copied nested keys instead.
8
+ - Validate each nested form exactly once per parent validation, rather than two to three times.
9
+ - Make `AssociationProxy#any?` block-aware (matching Ruby collection semantics).
10
+
11
+ # [2.1.0] - 2026-09-15
2
12
 
3
- - Add `changes_applied:` option to `Voids::Base#assign_attributes` so callers can bulk-assign attributes without resetting dirty tracking.
4
13
  - Propagate validation context to nested forms during parent validation.
5
14
  - Validate nested forms even when parent validations fail, so nested errors still bubble up.
6
15
  - Add `Voids::Base#empty?` with default emptiness semantics based on blank attributes.
7
- - Make `AssociationProxy#any?` block-aware (matching Ruby collection semantics).
8
16
  - Add `AssociationProxy#reverse` for array-like read access without explicit `to_a` conversion.
17
+ - Add `changes_applied:` option to `Voids::Base#assign_attributes` so callers can bulk-assign attributes without resetting dirty tracking.
18
+ - Add CI/coverage/version badges to README
19
+
20
+ # [2.0.0] - 2026-09-14
21
+
22
+ - Re-released as voids gem
9
23
 
10
- ## [1.0.0] - 2026-01-13
24
+ # [1.0.0] - 2026-01-13
11
25
 
12
26
  - Initial release
data/README.md CHANGED
@@ -200,6 +200,15 @@ form.valid?(:publish) # false
200
200
  form.errors.full_messages # includes nested form errors
201
201
  ```
202
202
 
203
+ Nested errors are copied onto the parent, keyed by the association and the nested
204
+ attribute. As in `ActiveRecord`, no error is added to the association itself:
205
+
206
+ ```ruby
207
+ form.errors.messages.keys # => [:"images[0].url"] (has_many)
208
+ form.errors.messages.keys # => [:"author.name"] (has_one)
209
+ form.errors[:images] # => []
210
+ ```
211
+
203
212
  ### Extracting attributes for persistence
204
213
 
205
214
  Use `model_attributes` for the form's own attributes:
data/lib/voids/base.rb CHANGED
@@ -247,17 +247,15 @@ module Voids
247
247
  when :has_one
248
248
  next if nested_form.valid?(context)
249
249
 
250
- errors.add(name.to_sym, :invalid)
251
250
  copy_nested_errors(name, nested_form)
252
251
  all_valid = false
253
252
  when :has_many
254
- next if nested_form.valid?(context)
255
-
256
- errors.add(name.to_sym, :invalid)
257
253
  nested_form.each_with_index do |form, index|
258
- copy_nested_errors("#{name}[#{index}]", form) unless form.valid?(context)
254
+ next if form.valid?(context)
255
+
256
+ copy_nested_errors("#{name}[#{index}]", form)
257
+ all_valid = false
259
258
  end
260
- all_valid = false
261
259
  end
262
260
  end
263
261
 
data/lib/voids/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Voids
4
- VERSION = '2.1.0'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -386,8 +386,8 @@ RSpec.describe Voids::Base do
386
386
  form.images.new
387
387
 
388
388
  expect(form.valid?).to be(false)
389
- expect(form.errors.details[:images]).to include(error: :invalid)
390
- expect(form.errors.messages.keys).to include(:'images[0].url')
389
+ expect(form.errors.messages.keys).to contain_exactly(:'images[0].url')
390
+ expect(form.errors[:images]).to be_empty
391
391
  end
392
392
 
393
393
  it 'includes nested form errors in parent errors' do
@@ -407,8 +407,8 @@ RSpec.describe Voids::Base do
407
407
  form.build_photo
408
408
  form.valid?
409
409
 
410
- expect(form.errors.messages.keys).to include(:photo, :'photo.url')
411
- expect(form.errors.details[:photo]).to include(error: :invalid)
410
+ expect(form.errors.messages.keys).to contain_exactly(:'photo.url')
411
+ expect(form.errors[:photo]).to be_empty
412
412
  end
413
413
 
414
414
  it 'includes nested form errors in parent even when parent has errors' do
@@ -429,8 +429,8 @@ RSpec.describe Voids::Base do
429
429
  form.build_photo
430
430
  form.valid?
431
431
 
432
- expect(form.errors.messages.keys).to contain_exactly(:title, :photo, :'photo.url')
433
- expect(form.errors.details[:photo]).to include(error: :invalid)
432
+ expect(form.errors.messages.keys).to contain_exactly(:title, :'photo.url')
433
+ expect(form.errors[:photo]).to be_empty
434
434
  end
435
435
 
436
436
  it 'passes the validation context to nested forms' do
@@ -452,8 +452,34 @@ RSpec.describe Voids::Base do
452
452
  form.build_photo(url: 'http://myspace.com/sicily-photos')
453
453
  form.valid?(:final_setup)
454
454
 
455
- expect(form.errors.messages.keys).to contain_exactly(:photo, :'photo.url')
456
- expect(form.errors.details[:photo]).to include(error: :invalid)
455
+ expect(form.errors.messages.keys).to contain_exactly(:'photo.url')
456
+ expect(form.errors[:photo]).to be_empty
457
+ end
458
+
459
+ it 'validates each nested form exactly once' do
460
+ validation_counts = Hash.new(0)
461
+ nested_class = Class.new(described_class) do
462
+ attribute :url, :string
463
+ validate do
464
+ validation_counts[url] += 1
465
+ errors.add(:url, :blank) if url.blank?
466
+ end
467
+ end
468
+ stub_const('ImageForm', nested_class)
469
+
470
+ form_class = Class.new(described_class) do
471
+ has_many :images
472
+ attribute :title, :string
473
+ end
474
+ stub_const('ImageGalleryForm', form_class)
475
+
476
+ form = form_class.new(title: 'test')
477
+ %w[first second].each { |url| form.images.new(url:) }
478
+ form.images.new(url: '')
479
+ form.images.new(url: 'fourth')
480
+ form.valid?
481
+
482
+ expect(validation_counts).to eq({ 'first' => 1, 'second' => 1, '' => 1, 'fourth' => 1 })
457
483
  end
458
484
  end
459
485
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voids
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Brody