From 07dbfdb418db970551722b6c18e5b041c7211681 Mon Sep 17 00:00:00 2001 From: opsys-saito Date: Thu, 19 Feb 2026 09:18:46 +0900 Subject: [PATCH] Add Relation#or support --- lib/active_hash/relation.rb | 16 +++++++++++ spec/active_hash/relation_spec.rb | 48 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/lib/active_hash/relation.rb b/lib/active_hash/relation.rb index 47b9afd..b7f5e41 100644 --- a/lib/active_hash/relation.rb +++ b/lib/active_hash/relation.rb @@ -24,6 +24,22 @@ def where(conditions_hash = :chain) spawn.where!(conditions_hash) end + def or(other) + unless other.is_a?(self.class) + raise ArgumentError, "or() expects an ActiveHash::Relation" + end + + unless other.klass == klass + raise ArgumentError, "or() expects relations for the same model" + end + + merged = (records + other.records).uniq do |record| + record.respond_to?(:id) ? record.id : record.object_id + end + + self.class.new(klass, merged, [], order_values) + end + def pretty_print(pp) pp.pp(entries.to_ary) end diff --git a/spec/active_hash/relation_spec.rb b/spec/active_hash/relation_spec.rb index bf89269..9463c21 100644 --- a/spec/active_hash/relation_spec.rb +++ b/spec/active_hash/relation_spec.rb @@ -93,4 +93,52 @@ expect(out.string).to_not match(/ActiveHash::Relation/) end end + + describe '#or' do + it 'returns the union of two where relations' do + r1 = model_class.where(name: "US") + r2 = model_class.where(name: "Canada") + + result = r1.or(r2) + + expect(result.pluck(:id)).to match_array([1, 2]) + end + + it 'deduplicates records by id' do + r1 = model_class.where(name: "US") + r2 = model_class.where(name: "US") + + result = r1.or(r2) + + expect(result.pluck(:id)).to eq([1]) + end + + it 'returns a relation that can be chained' do + r1 = model_class.where(name: "US") + r2 = model_class.where(name: "Canada") + + result = r1.or(r2).where(id: 2) + + expect(result.pluck(:id)).to eq([2]) + end + + it 'raises when OR-ing relations from different models' do + other_model = Class.new(ActiveHash::Base) do + self.data = [{ id: 1, name: "X" }] + end + + expect { + model_class.where(name: "US").or(other_model.where(name: "X")) + }.to raise_error(ArgumentError) + end + + it 'works with order applied after or' do + r1 = model_class.where(id: 1) + r2 = model_class.where(id: [2]) + + result = r1.or(r2).order(id: :desc) + + expect(result.pluck(:id)).to eq([2, 1]) + end + end end