|
3 | 3 | module DossierValidateConcern |
4 | 4 | extend ActiveSupport::Concern |
5 | 5 |
|
| 6 | + # Autosave's _ensure_no_duplicate_errors calls errors.uniq! after validations, |
| 7 | + # which would collapse same-attribute, same-type errors imported from |
| 8 | + # different champs. Include the champ in the error identity to prevent that. |
| 9 | + class ChampNestedError < ActiveModel::NestedError |
| 10 | + protected def attributes_for_hash |
| 11 | + [*super, inner_error.base] |
| 12 | + end |
| 13 | + end |
| 14 | + |
6 | 15 | included do |
7 | 16 | validate :validate_champs_public_value, on: :champs_public_value |
8 | 17 | validate :validate_champs_private_value, on: :champs_private_value |
| 18 | + validate :validate_champs_public_completeness, on: :champs_public_completeness |
| 19 | + validate :validate_champs_private_completeness, on: :champs_private_completeness |
9 | 20 | end |
10 | 21 |
|
11 | 22 | def champs_public_valid? |
12 | | - validate(:champs_public_value) |
13 | | - check_mandatory_and_visible_champs_for(root_champs_public) |
14 | | - errors.blank? |
| 23 | + validate(:champs_public_completeness) |
15 | 24 | end |
16 | 25 |
|
17 | 26 | def champs_private_valid? |
18 | | - validate(:champs_private_value) |
19 | | - check_mandatory_and_visible_champs_for(root_champs_private) |
20 | | - errors.blank? |
| 27 | + validate(:champs_private_completeness) |
21 | 28 | end |
22 | 29 |
|
23 | 30 | private |
24 | 31 |
|
25 | 32 | def validate_champs_public_value |
26 | | - validate_projected_champs(flat_champs_public) |
| 33 | + validate_projected_champs(flat_champs_public, :champ_value) |
27 | 34 | end |
28 | 35 |
|
29 | 36 | def validate_champs_private_value |
30 | | - validate_projected_champs(flat_champs_private) |
| 37 | + validate_projected_champs(flat_champs_private, :champ_value) |
31 | 38 | end |
32 | 39 |
|
33 | | - def validate_projected_champs(champs) |
34 | | - champs.each do |champ| |
35 | | - next if champ.validate(:champ_value) |
36 | | - champ.errors.each { errors.import(it) } |
37 | | - end |
| 40 | + def validate_champs_public_completeness |
| 41 | + validate_projected_champs(flat_champs_public, [:champ_value, :champ_completeness]) |
38 | 42 | end |
39 | 43 |
|
40 | | - def check_mandatory_and_visible_champs_for(collection) |
41 | | - collection.filter(&:visible?).each do |champ| |
42 | | - if champ.mandatory_blank? && !champ.respond_to?(:validate_completed) |
43 | | - error = champ.errors.add(:value, :missing) |
44 | | - errors.import(error) |
45 | | - end |
| 44 | + def validate_champs_private_completeness |
| 45 | + validate_projected_champs(flat_champs_private, [:champ_value, :champ_completeness]) |
| 46 | + end |
46 | 47 |
|
47 | | - if champ.repetition? |
48 | | - champ.rows.each do |row| |
49 | | - row.flat_champs.filter(&:visible?).each do |champ| |
50 | | - if champ.respond_to?(:validate_completed) |
51 | | - champ.validate_completed |
52 | | - champ.errors.each { errors.import(it) } |
53 | | - elsif champ.mandatory_blank? |
54 | | - error = champ.errors.add(:value, :missing) |
55 | | - errors.import(error) |
56 | | - end |
57 | | - end |
58 | | - end |
59 | | - elsif champ.respond_to?(:validate_completed) |
60 | | - champ.validate_completed |
61 | | - champ.errors.each { errors.import(it) } |
62 | | - end |
| 48 | + # Both contexts must be validated in a single call: champ.validate clears |
| 49 | + # previous errors, so a second pass would erase the first one's errors. |
| 50 | + def validate_projected_champs(champs, contexts) |
| 51 | + champs.each do |champ| |
| 52 | + next if champ.validate(contexts) |
| 53 | + champ.errors.each { errors.objects.append(ChampNestedError.new(self, it)) } |
63 | 54 | end |
64 | | - errors |
65 | 55 | end |
66 | 56 | end |
0 commit comments