Skip to content

Commit 528c81a

Browse files
kbrockclaude
andcommitted
Validate ancestor_ids instead of raw ancestry column
The builder defines `def ancestry` to return `before_type_cast` for backwards compatibility. Validating the column meant the validator received raw strings on all Rails versions. Validating `ancestor_ids` receives the deserialized array directly. - Remove `alias_attribute :ancestor_ids` (builder already defines it via `read_attribute`, which goes through the Type) - Guard dirty-tracking methods (`_in_database`, `_before_last_save`) with explicit deserialize for Rails < 7.2 compatibility - `sane_ancestor_ids?` looks up validators on `:ancestor_ids` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a80c269 commit 528c81a

3 files changed

Lines changed: 10 additions & 12 deletions

File tree

lib/ancestry/ancestry_validator.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33
module Ancestry
44
class AncestryValidator < ActiveModel::EachValidator
55
def validate_each(record, attribute, value)
6-
ids = value.kind_of?(Array) ? value : record.ancestor_ids
7-
return if ids.empty?
6+
return if value.empty?
87

98
if options[:integer]
10-
unless ids.all? { |id| id.kind_of?(Integer) && id > 0 }
9+
unless value.all? { |id| id.kind_of?(Integer) && id > 0 }
1110
record.errors.add(attribute, :invalid)
1211
return
1312
end
1413
end
1514

16-
if ids.include?(record.id)
15+
if value.include?(record.id)
1716
record.errors.add(:base, I18n.t("ancestry.exclude_self", class_name: record.class.model_name.human))
1817
end
1918
end

lib/ancestry/has_ancestry.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,9 @@ def has_ancestry(options = {})
127127
attribute column, serializer, default: serializer.empty
128128
end
129129

130-
# ancestor_ids is the typed (array) view of the column
131-
alias_attribute :ancestor_ids, column unless column == :ancestor_ids
132-
133130
update_strategy = options[:update_strategy] || Ancestry.default_update_strategy
134131

135-
validates_with Ancestry::AncestryValidator, attributes: [column], integer: integer_pk
132+
validates_with Ancestry::AncestryValidator, attributes: [:ancestor_ids], integer: integer_pk
136133

137134
# Validate descendants' depths don't exceed max depth when moving them
138135
validate :ancestry_depth_of_descendants, if: :ancestry_changed?

lib/ancestry/instance_methods_builder.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@ def ancestor_ids
6262
end
6363

6464
def ancestor_ids_in_database
65-
attribute_in_database(:#{column})
65+
value = attribute_in_database(:#{column})
66+
value.kind_of?(Array) ? value : self.class.type_for_attribute("#{column}").deserialize(value)
6667
end
6768

6869
def ancestor_ids_before_last_save
69-
attribute_before_last_save(:#{column})
70+
value = attribute_before_last_save(:#{column})
71+
value.kind_of?(Array) ? value : self.class.type_for_attribute("#{column}").deserialize(value)
7072
end
7173

7274
def parent_id_in_database
@@ -114,8 +116,8 @@ def sane_ancestor_ids?
114116
ids = ancestor_ids
115117
return true if ids.empty?
116118

117-
self.class.validators_on(:#{column}).each do |validator|
118-
validator.validate_each(self, :#{column}, ids)
119+
self.class.validators_on(:ancestor_ids).each do |validator|
120+
validator.validate_each(self, :ancestor_ids, ids)
119121
end
120122
errors.none?
121123
ensure

0 commit comments

Comments
 (0)