diff --git a/CHANGELOG.md b/CHANGELOG.md index bb95949d..9481bf23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 2.4.0.findy.1 2026-05-13 +* [Findy fork] Guard `visit obj.alias` calls in `MultiTenant::ArelVisitorsDepthFirst` with `respond_to?(:alias)` so the visitor works on Rails 8.1, which removed the `alias` attribute from `Arel::Nodes::Function` (and its `Avg`/`Exists`/`Max`/`Min`/`Sum`/`NamedFunction`/`Count` subclasses). Preserves existing behavior on Rails 6/7/8.0. + ## 2.4.0 2023-09-22 * Adds citus 12 to test matrix (#210) * Adds Support for rails 7.1 (#208) diff --git a/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb b/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb index a0c0f3b2..89fb875d 100644 --- a/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb +++ b/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb @@ -39,7 +39,7 @@ def unary(obj) def function(obj) visit obj.expressions - visit obj.alias + visit obj.alias if obj.respond_to?(:alias) visit obj.distinct end alias visit_Arel_Nodes_Avg function @@ -54,12 +54,12 @@ def visit_Arel_Nodes_NamedFunction(obj) visit obj.name visit obj.expressions visit obj.distinct - visit obj.alias + visit obj.alias if obj.respond_to?(:alias) end def visit_Arel_Nodes_Count(obj) visit obj.expressions - visit obj.alias + visit obj.alias if obj.respond_to?(:alias) visit obj.distinct end diff --git a/lib/activerecord-multi-tenant/version.rb b/lib/activerecord-multi-tenant/version.rb index 20299e68..206d1667 100644 --- a/lib/activerecord-multi-tenant/version.rb +++ b/lib/activerecord-multi-tenant/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module MultiTenant - VERSION = '2.4.0' + VERSION = '2.4.0.findy.1' end diff --git a/spec/activerecord-multi-tenant/arel_visitors_depth_first_spec.rb b/spec/activerecord-multi-tenant/arel_visitors_depth_first_spec.rb new file mode 100644 index 00000000..00bba97e --- /dev/null +++ b/spec/activerecord-multi-tenant/arel_visitors_depth_first_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'arel' +require 'activerecord-multi-tenant/arel_visitors_depth_first' + +describe MultiTenant::ArelVisitorsDepthFirst do + let(:visited) { [] } + let(:visitor) { described_class.new(->(node) { visited << node }) } + let(:table) { Arel::Table.new(:projects) } + let(:expr) { table[:id] } + + describe 'function nodes' do + %i[Avg Exists Max Min Sum].each do |const_name| + it "traverses Arel::Nodes::#{const_name} without raising" do + node = Arel::Nodes.const_get(const_name).new([expr]) + + expect { visitor.accept(node) }.not_to raise_error + expect(visited).to include(expr) + end + end + end + + describe 'Arel::Nodes::NamedFunction' do + it 'traverses the node without raising' do + node = Arel::Nodes::NamedFunction.new('coalesce', [expr]) + + expect { visitor.accept(node) }.not_to raise_error + expect(visited).to include(expr) + end + + it 'visits the alias when the node still exposes #alias (Rails <= 8.0)' do + node = Arel::Nodes::NamedFunction.new('coalesce', [expr]) + skip 'Arel::Nodes::NamedFunction#alias was removed in this Rails version' unless node.respond_to?(:alias) + + node.as('id_alias') + visitor.accept(node) + + literals = visited.grep(Arel::Nodes::SqlLiteral).map(&:to_s) + expect(literals).to include('id_alias') + end + end + + describe 'Arel::Nodes::Count' do + it 'traverses the node without raising' do + node = Arel::Nodes::Count.new([expr]) + + expect { visitor.accept(node) }.not_to raise_error + expect(visited).to include(expr) + end + end +end