1# frozen_string_literal: true
2
3require 'spec_helper'
4
5RSpec.describe 'DisableJoins' do
6  let(:primary_model) do
7    Class.new(ApplicationRecord) do
8      self.table_name = '_test_primary_records'
9
10      def self.name
11        'TestPrimary'
12      end
13    end
14  end
15
16  let(:bridge_model) do
17    Class.new(ApplicationRecord) do
18      self.table_name = '_test_bridge_records'
19
20      def self.name
21        'TestBridge'
22      end
23    end
24  end
25
26  let(:secondary_model) do
27    Class.new(ApplicationRecord) do
28      self.table_name = '_test_secondary_records'
29
30      def self.name
31        'TestSecondary'
32      end
33    end
34  end
35
36  context 'passing disable_joins as an association option' do
37    context 'when the association is a bare has_one' do
38      it 'disallows the disable_joins option' do
39        expect do
40          primary_model.has_one :test_bridge, disable_joins: true
41        end.to raise_error(ArgumentError, /Unknown key: :disable_joins/)
42      end
43    end
44
45    context 'when the association is a belongs_to' do
46      it 'disallows the disable_joins option' do
47        expect do
48          bridge_model.belongs_to :test_secondary, disable_joins: true
49        end.to raise_error(ArgumentError, /Unknown key: :disable_joins/)
50      end
51    end
52
53    context 'when the association is has_one :through' do
54      it 'allows the disable_joins option' do
55        primary_model.has_one :test_bridge
56        bridge_model.belongs_to :test_secondary
57
58        expect do
59          primary_model.has_one :test_secondary, through: :test_bridge, disable_joins: true
60        end.not_to raise_error
61      end
62    end
63
64    context 'when the association is a bare has_many' do
65      it 'disallows the disable_joins option' do
66        expect do
67          primary_model.has_many :test_bridges, disable_joins: true
68        end.to raise_error(ArgumentError, /Unknown key: :disable_joins/)
69      end
70    end
71
72    context 'when the association is a has_many :through' do
73      it 'allows the disable_joins option' do
74        primary_model.has_many :test_bridges
75        bridge_model.belongs_to :test_secondary
76
77        expect do
78          primary_model.has_many :test_secondaries, through: :test_bridges, disable_joins: true
79        end.not_to raise_error
80      end
81    end
82  end
83
84  context 'querying has_one :through when disable_joins is set' do
85    before do
86      create_tables(<<~SQL)
87        CREATE TABLE _test_primary_records (
88          id serial NOT NULL PRIMARY KEY);
89
90        CREATE TABLE _test_bridge_records (
91          id serial NOT NULL PRIMARY KEY,
92          primary_record_id int NOT NULL,
93          secondary_record_id int NOT NULL);
94
95        CREATE TABLE _test_secondary_records (
96          id serial NOT NULL PRIMARY KEY);
97      SQL
98
99      primary_model.has_one :test_bridge, anonymous_class: bridge_model, foreign_key: :primary_record_id
100      bridge_model.belongs_to :test_secondary, anonymous_class: secondary_model, foreign_key: :secondary_record_id
101      primary_model.has_one :test_secondary, through: :test_bridge, anonymous_class: secondary_model,
102        disable_joins: -> { joins_disabled_flag }
103
104      primary_record = primary_model.create!
105      secondary_record = secondary_model.create!
106      bridge_model.create!(primary_record_id: primary_record.id, secondary_record_id: secondary_record.id)
107    end
108
109    context 'when disable_joins evaluates to true' do
110      let(:joins_disabled_flag) { true }
111
112      it 'executes separate queries' do
113        primary_record = primary_model.first
114
115        query_count = ActiveRecord::QueryRecorder.new { primary_record.test_secondary }.count
116
117        expect(query_count).to eq(2)
118      end
119    end
120
121    context 'when disable_joins evalutes to false' do
122      let(:joins_disabled_flag) { false }
123
124      it 'executes a single query' do
125        primary_record = primary_model.first
126
127        query_count = ActiveRecord::QueryRecorder.new { primary_record.test_secondary }.count
128
129        expect(query_count).to eq(1)
130      end
131    end
132  end
133
134  context 'querying has_many :through when disable_joins is set' do
135    before do
136      create_tables(<<~SQL)
137        CREATE TABLE _test_primary_records (
138          id serial NOT NULL PRIMARY KEY);
139
140        CREATE TABLE _test_bridge_records (
141          id serial NOT NULL PRIMARY KEY,
142          primary_record_id int NOT NULL);
143
144        CREATE TABLE _test_secondary_records (
145          id serial NOT NULL PRIMARY KEY,
146          bridge_record_id int NOT NULL);
147      SQL
148
149      primary_model.has_many :test_bridges, anonymous_class: bridge_model, foreign_key: :primary_record_id
150      bridge_model.has_many :test_secondaries, anonymous_class: secondary_model, foreign_key: :bridge_record_id
151      primary_model.has_many :test_secondaries, through: :test_bridges, anonymous_class: secondary_model,
152        disable_joins: -> { disabled_join_flag }
153
154      primary_record = primary_model.create!
155      bridge_record = bridge_model.create!(primary_record_id: primary_record.id)
156      secondary_model.create!(bridge_record_id: bridge_record.id)
157    end
158
159    context 'when disable_joins evaluates to true' do
160      let(:disabled_join_flag) { true }
161
162      it 'executes separate queries' do
163        primary_record = primary_model.first
164
165        query_count = ActiveRecord::QueryRecorder.new { primary_record.test_secondaries.first }.count
166
167        expect(query_count).to eq(2)
168      end
169    end
170
171    context 'when disable_joins evalutes to false' do
172      let(:disabled_join_flag) { false }
173
174      it 'executes a single query' do
175        primary_record = primary_model.first
176
177        query_count = ActiveRecord::QueryRecorder.new { primary_record.test_secondaries.first }.count
178
179        expect(query_count).to eq(1)
180      end
181    end
182  end
183
184  context 'querying STI relationships' do
185    let(:child_bridge_model) do
186      Class.new(bridge_model) do
187        def self.name
188          'ChildBridge'
189        end
190      end
191    end
192
193    let(:child_secondary_model) do
194      Class.new(secondary_model) do
195        def self.name
196          'ChildSecondary'
197        end
198      end
199    end
200
201    before do
202      create_tables(<<~SQL)
203        CREATE TABLE _test_primary_records (
204          id serial NOT NULL PRIMARY KEY);
205
206        CREATE TABLE _test_bridge_records (
207          id serial NOT NULL PRIMARY KEY,
208          primary_record_id int NOT NULL,
209          type text);
210
211        CREATE TABLE _test_secondary_records (
212          id serial NOT NULL PRIMARY KEY,
213          bridge_record_id int NOT NULL,
214          type text);
215      SQL
216
217      primary_model.has_many :child_bridges, anonymous_class: child_bridge_model, foreign_key: :primary_record_id
218      child_bridge_model.has_one :child_secondary, anonymous_class: child_secondary_model, foreign_key: :bridge_record_id
219      primary_model.has_many :child_secondaries, through: :child_bridges, anonymous_class: child_secondary_model, disable_joins: true
220
221      primary_record = primary_model.create!
222      parent_bridge_record = bridge_model.create!(primary_record_id: primary_record.id)
223      child_bridge_record = child_bridge_model.create!(primary_record_id: primary_record.id)
224
225      secondary_model.create!(bridge_record_id: child_bridge_record.id)
226      child_secondary_model.create!(bridge_record_id: parent_bridge_record.id)
227      child_secondary_model.create!(bridge_record_id: child_bridge_record.id)
228    end
229
230    it 'filters correctly by the STI type across multiple queries' do
231      primary_record = primary_model.first
232
233      query_recorder = ActiveRecord::QueryRecorder.new do
234        expect(primary_record.child_secondaries.count).to eq(1)
235      end
236
237      expect(query_recorder.count).to eq(2)
238    end
239  end
240
241  context 'querying polymorphic relationships' do
242    before do
243      create_tables(<<~SQL)
244        CREATE TABLE _test_primary_records (
245          id serial NOT NULL PRIMARY KEY);
246
247        CREATE TABLE _test_bridge_records (
248          id serial NOT NULL PRIMARY KEY,
249          primaryable_id int NOT NULL,
250          primaryable_type text NOT NULL);
251
252        CREATE TABLE _test_secondary_records (
253          id serial NOT NULL PRIMARY KEY,
254          bridgeable_id int NOT NULL,
255          bridgeable_type text NOT NULL);
256      SQL
257
258      primary_model.has_many :test_bridges, anonymous_class: bridge_model, foreign_key: :primaryable_id, as: :primaryable
259      bridge_model.has_one :test_secondaries, anonymous_class: secondary_model, foreign_key: :bridgeable_id, as: :bridgeable
260      primary_model.has_many :test_secondaries, through: :test_bridges, anonymous_class: secondary_model, disable_joins: true
261
262      primary_record = primary_model.create!
263      primary_bridge_record = bridge_model.create!(primaryable_id: primary_record.id, primaryable_type: 'TestPrimary')
264      nonprimary_bridge_record = bridge_model.create!(primaryable_id: primary_record.id, primaryable_type: 'NonPrimary')
265
266      secondary_model.create!(bridgeable_id: primary_bridge_record.id, bridgeable_type: 'TestBridge')
267      secondary_model.create!(bridgeable_id: nonprimary_bridge_record.id, bridgeable_type: 'TestBridge')
268      secondary_model.create!(bridgeable_id: primary_bridge_record.id, bridgeable_type: 'NonBridge')
269    end
270
271    it 'filters correctly by the polymorphic type across multiple queries' do
272      primary_record = primary_model.first
273
274      query_recorder = ActiveRecord::QueryRecorder.new do
275        expect(primary_record.test_secondaries.count).to eq(1)
276      end
277
278      expect(query_recorder.count).to eq(2)
279    end
280  end
281
282  def create_tables(table_sql)
283    ApplicationRecord.connection.execute(table_sql)
284
285    bridge_model.reset_column_information
286    secondary_model.reset_column_information
287  end
288end
289