From 7ede5da017cee7d4c0dd1db01315c0585e532dac Mon Sep 17 00:00:00 2001 From: Keenan Brock Date: Tue, 31 Mar 2026 04:47:35 -0400 Subject: [PATCH] Safe spawn and where.not - where.not chains no longer can get corrupted from simple searches. - find_by no longer mutates the relation. Fixes #338 Thanks Claude for the assist --- lib/active_hash/conditions.rb | 5 +++++ lib/active_hash/relation.rb | 4 ++-- spec/active_hash/base_spec.rb | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/active_hash/conditions.rb b/lib/active_hash/conditions.rb index ebbd755c..9b919838 100644 --- a/lib/active_hash/conditions.rb +++ b/lib/active_hash/conditions.rb @@ -7,6 +7,11 @@ def initialize(conditions = []) @conditions = conditions end + def initialize_copy(orig) + super + @conditions = @conditions.dup + end + def matches?(record) conditions.all? do |condition| condition.matches?(record) diff --git a/lib/active_hash/relation.rb b/lib/active_hash/relation.rb index b7f5e41f..d0debc65 100644 --- a/lib/active_hash/relation.rb +++ b/lib/active_hash/relation.rb @@ -19,7 +19,7 @@ def initialize(klass, all_records, conditions = nil, order_values = nil) end def where(conditions_hash = :chain) - return WhereChain.new(self) if conditions_hash == :chain + return WhereChain.new(spawn) if conditions_hash == :chain spawn.where!(conditions_hash) end @@ -80,7 +80,7 @@ def invert_where! end def spawn - self.class.new(klass, all_records, conditions, order_values) + self.class.new(klass, all_records, conditions.dup, order_values) end def order!(*options) diff --git a/spec/active_hash/base_spec.rb b/spec/active_hash/base_spec.rb index c9420e52..731889ec 100644 --- a/spec/active_hash/base_spec.rb +++ b/spec/active_hash/base_spec.rb @@ -497,6 +497,12 @@ class Region < ActiveHash::Base it "filters records for multiple conditions" do expect(Country.where.not(:id => 1, :name => 'Mexico')).to match_array([Country.find(2)]) end + + it "does not mutate the parent relation" do + english = Country.where(language: 'English') + english.where.not(name: 'US') + expect(english.map(&:name)).to match_array(%w[US Canada]) + end end describe ".find_by" do @@ -574,6 +580,14 @@ class Region < ActiveHash::Base it "doesn't finds nil records when searching for ''" do expect(Country.find_by(:language => '')).to be_nil end + + it "does not mutate the relation when called multiple times" do + countries = Country.all + expect(countries.find_by(id: 1).name).to eq("US") + expect(countries.find_by(id: 2).name).to eq("Canada") + expect(countries.find_by(id: 1).name).to eq("US") + expect(countries.length).to eq(4) + end end describe ".find_by!" do