From 5a28447bd7be47a35c891f1bfc5cd7aa7afcb048 Mon Sep 17 00:00:00 2001 From: Mohith26 <109003762+Mohith26@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:45:22 -0500 Subject: [PATCH] Map class names back to integers in _write_attribute Rails 7.1+ CollectionAssociation#merge_target_lists copies reloaded values onto in-memory records with _write_attribute(name, record[name]). The gem's [] returns the mapped class name for polymorphic integer type columns, so the integer column ended up cast to 0 and the in-memory record pointed at the wrong type. Fixes #69. --- CHANGELOG.md | 6 ++++++ lib/polymorphic_integer_type/extensions.rb | 13 ++++++++++++ spec/polymorphic_integer_type_spec.rb | 24 ++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 517b404..8071e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixed + +- On Rails 7.1+, reloading a `has_many` while holding records already built into it wrote the mapped class name into the integer type column (cast to `0`), so those in-memory records pointed at the wrong type. `_write_attribute` now maps the class name back to its integer ([#69](https://github.com/clio/polymorphic_integer_type/issues/69)). + ## v3.5.0 (2026-04-30) ### Added diff --git a/lib/polymorphic_integer_type/extensions.rb b/lib/polymorphic_integer_type/extensions.rb index 8e2144f..ef32a87 100644 --- a/lib/polymorphic_integer_type/extensions.rb +++ b/lib/polymorphic_integer_type/extensions.rb @@ -153,6 +153,19 @@ def []=(attr_name, value) end end + # Rails 7.1+ copies reloaded has_many rows onto records the caller already + # holds with `_write_attribute(name, record[name])` + # (CollectionAssociation#merge_target_lists). `[]` above returns the mapped + # class name for these columns, so map it back to the integer here instead + # of letting the integer column cast "Drink" to 0. + def _write_attribute(attr_name, value) + name = attr_name.to_s + if value.is_a?(String) && _polymorphic_foreign_types.include?(name) + value = self.class.send("#{name}_mapping").key(value) || value + end + super(attr_name, value) + end + end end diff --git a/spec/polymorphic_integer_type_spec.rb b/spec/polymorphic_integer_type_spec.rb index 73a4cdd..7458e8a 100644 --- a/spec/polymorphic_integer_type_spec.rb +++ b/spec/polymorphic_integer_type_spec.rb @@ -184,6 +184,30 @@ end end end + context "When a has_many reload merges into records the caller still holds" do + # Rails 7.1+ copies the reloaded values onto the in-memory records with + # _write_attribute (CollectionAssociation#merge_target_lists), reading + # them with [] first, which returns the mapped class name (issue #69). + let(:source) { cat } + + it "keeps the integer type on the in-memory records" do + first = source.source_links.new(target: kibble) + first.save! + second = source.source_links.new(target: milk) + second.save! + + expect(source.source_links).not_to be_loaded + source.source_links.length + + expect(source.source_links).to include(second) + expect(second._read_attribute("target_type")).to eql 2 + expect(second.target_type).to eql "Drink" + expect(second.target).to eql milk + expect(first.target_type).to eql "Food" + expect(first._read_attribute("source_type")).to eql 2 + end + end + context "When a link is given polymorphic record" do let(:link) { Link.create(source: source) } let(:source) { cat }