|
94 | 94 |
|
95 | 95 | context "ActiveRecord connection_pool" do |
96 | 96 | before do |
97 | | - ActiveRecord::Base.connection_handler.clear_active_connections! |
| 97 | + clear_active_connections! |
98 | 98 | end |
99 | 99 |
|
100 | 100 | context "#features" do |
101 | 101 | it "does not hold onto connections" do |
102 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 102 | + expect(active_connections?).to be(false) |
103 | 103 | subject.features |
104 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 104 | + expect(active_connections?).to be(false) |
105 | 105 | end |
106 | 106 |
|
107 | 107 | it "does not release previously held connection" do |
108 | 108 | ActiveRecord::Base.connection # establish a new connection |
109 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 109 | + expect(active_connections?).to be(true) |
110 | 110 | subject.features |
111 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 111 | + expect(active_connections?).to be(true) |
112 | 112 | end |
113 | 113 | end |
114 | 114 |
|
115 | 115 | context "#get_all" do |
116 | 116 | it "does not hold onto connections" do |
117 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 117 | + expect(active_connections?).to be(false) |
118 | 118 | subject.get_all |
119 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 119 | + expect(active_connections?).to be(false) |
120 | 120 | end |
121 | 121 |
|
122 | 122 | it "does not release previously held connection" do |
123 | 123 | ActiveRecord::Base.connection # establish a new connection |
124 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 124 | + expect(active_connections?).to be(true) |
125 | 125 | subject.get_all |
126 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 126 | + expect(active_connections?).to be(true) |
127 | 127 | end |
128 | 128 | end |
129 | 129 |
|
130 | 130 | context "#add / #remove / #clear" do |
131 | 131 | let(:feature) { Flipper::Feature.new(:search, subject) } |
132 | 132 |
|
133 | 133 | it "does not hold onto connections" do |
134 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 134 | + expect(active_connections?).to be(false) |
135 | 135 | subject.add(feature) |
136 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 136 | + expect(active_connections?).to be(false) |
137 | 137 | subject.remove(feature) |
138 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 138 | + expect(active_connections?).to be(false) |
139 | 139 | subject.clear(feature) |
140 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 140 | + expect(active_connections?).to be(false) |
141 | 141 | end |
142 | 142 |
|
143 | 143 | it "does not release previously held connection" do |
144 | 144 | ActiveRecord::Base.connection # establish a new connection |
145 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 145 | + expect(active_connections?).to be(true) |
146 | 146 | subject.add(feature) |
147 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 147 | + expect(active_connections?).to be(true) |
148 | 148 | subject.remove(feature) |
149 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 149 | + expect(active_connections?).to be(true) |
150 | 150 | subject.clear(feature) |
151 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 151 | + expect(active_connections?).to be(true) |
152 | 152 | end |
153 | 153 | end |
154 | 154 |
|
155 | 155 | context "#get_multi" do |
156 | 156 | let(:feature) { Flipper::Feature.new(:search, subject) } |
157 | 157 |
|
158 | 158 | it "does not hold onto connections" do |
159 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 159 | + expect(active_connections?).to be(false) |
160 | 160 | subject.get_multi([feature]) |
161 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 161 | + expect(active_connections?).to be(false) |
162 | 162 | end |
163 | 163 |
|
164 | 164 | it "does not release previously held connection" do |
165 | 165 | ActiveRecord::Base.connection # establish a new connection |
166 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 166 | + expect(active_connections?).to be(true) |
167 | 167 | subject.get_multi([feature]) |
168 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 168 | + expect(active_connections?).to be(true) |
169 | 169 | end |
170 | 170 | end |
171 | 171 |
|
|
174 | 174 | let(:gate) { feature.gate(:boolean)} |
175 | 175 |
|
176 | 176 | it "does not hold onto connections" do |
177 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 177 | + expect(active_connections?).to be(false) |
178 | 178 | subject.enable(feature, gate, gate.wrap(true)) |
179 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 179 | + expect(active_connections?).to be(false) |
180 | 180 | subject.disable(feature, gate, gate.wrap(false)) |
181 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 181 | + expect(active_connections?).to be(false) |
182 | 182 | end |
183 | 183 |
|
184 | 184 | it "does not release previously held connection" do |
185 | 185 | ActiveRecord::Base.connection # establish a new connection |
186 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 186 | + expect(active_connections?).to be(true) |
187 | 187 | subject.enable(feature, gate, gate.wrap(true)) |
188 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 188 | + expect(active_connections?).to be(true) |
189 | 189 | subject.disable(feature, gate, gate.wrap(false)) |
190 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 190 | + expect(active_connections?).to be(true) |
191 | 191 | end |
192 | 192 | end |
193 | 193 |
|
|
196 | 196 | let(:gate) { feature.gate(:group) } |
197 | 197 |
|
198 | 198 | it "does not hold onto connections" do |
199 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 199 | + expect(active_connections?).to be(false) |
200 | 200 | subject.enable(feature, gate, gate.wrap(:admin)) |
201 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 201 | + expect(active_connections?).to be(false) |
202 | 202 | subject.disable(feature, gate, gate.wrap(:admin)) |
203 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false) |
| 203 | + expect(active_connections?).to be(false) |
204 | 204 | end |
205 | 205 |
|
206 | 206 | it "does not release previously held connection" do |
207 | 207 | ActiveRecord::Base.connection # establish a new connection |
208 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 208 | + expect(active_connections?).to be(true) |
209 | 209 | subject.enable(feature, gate, gate.wrap(:admin)) |
210 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 210 | + expect(active_connections?).to be(true) |
211 | 211 | subject.disable(feature, gate, gate.wrap(:admin)) |
212 | | - expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true) |
| 212 | + expect(active_connections?).to be(true) |
| 213 | + end |
| 214 | + end |
| 215 | + end |
| 216 | + |
| 217 | + if ActiveRecord.version >= Gem::Version.new('7.1') |
| 218 | + context 'with read/write roles' do |
| 219 | + before do |
| 220 | + skip "connected_to with roles is not supported on #{config['adapter']}" if config["adapter"] == "sqlite3" |
| 221 | + end |
| 222 | + |
| 223 | + let(:abstract_class) do |
| 224 | + # Create a named abstract class (Rails requires names for connects_to) |
| 225 | + klass = Class.new(ActiveRecord::Base) do |
| 226 | + self.abstract_class = true |
| 227 | + end |
| 228 | + stub_const('TestApplicationRecord', klass) |
| 229 | + |
| 230 | + # Now configure connects_to with the same database for both roles |
| 231 | + # In production, these would be different (primary/replica) |
| 232 | + klass.connects_to database: { |
| 233 | + writing: config, |
| 234 | + reading: config |
| 235 | + } |
| 236 | + |
| 237 | + klass |
| 238 | + end |
| 239 | + |
| 240 | + after do |
| 241 | + # Disconnect role-based connections to avoid interfering with database cleanup |
| 242 | + clear_all_connections! |
| 243 | + end |
| 244 | + |
| 245 | + let(:feature_class) do |
| 246 | + klass = Class.new(abstract_class) do |
| 247 | + self.table_name = 'flipper_features' |
| 248 | + validates :key, presence: true |
| 249 | + end |
| 250 | + stub_const('TestFeature', klass) |
| 251 | + klass |
| 252 | + end |
| 253 | + |
| 254 | + let(:gate_class) do |
| 255 | + klass = Class.new(abstract_class) do |
| 256 | + self.table_name = 'flipper_gates' |
| 257 | + end |
| 258 | + stub_const('TestGate', klass) |
| 259 | + klass |
| 260 | + end |
| 261 | + |
| 262 | + let(:adapter_with_roles) do |
| 263 | + described_class.new( |
| 264 | + feature_class: feature_class, |
| 265 | + gate_class: gate_class |
| 266 | + ) |
| 267 | + end |
| 268 | + |
| 269 | + it 'can perform write operations when forced to reading role' do |
| 270 | + abstract_class.connected_to(role: :reading) do |
| 271 | + flipper = Flipper.new(adapter_with_roles) |
| 272 | + |
| 273 | + feature = flipper[:test_feature] |
| 274 | + expect { feature.enable }.not_to raise_error |
| 275 | + expect(feature.enabled?).to be(true) |
| 276 | + expect { feature.disable }.not_to raise_error |
| 277 | + expect(feature.enabled?).to be(false) |
| 278 | + |
| 279 | + feature = flipper[:actor_test] |
| 280 | + actor = Struct.new(:flipper_id).new(123) |
| 281 | + expect { feature.enable_actor(actor) }.not_to raise_error |
| 282 | + expect(feature.enabled?(actor)).to be(true) |
| 283 | + expect { feature.disable_actor(actor) }.not_to raise_error |
| 284 | + expect(feature.enabled?(actor)).to be(false) |
| 285 | + |
| 286 | + feature = flipper[:gate_test] |
| 287 | + expect { feature.enable_percentage_of_time(50) }.not_to raise_error |
| 288 | + expect { feature.disable_percentage_of_time }.not_to raise_error |
| 289 | + feature.enable |
| 290 | + expect { feature.remove }.not_to raise_error |
| 291 | + |
| 292 | + feature = flipper[:expression_test] |
| 293 | + expression = Flipper.property(:plan).eq("premium") |
| 294 | + expect { feature.enable_expression(expression) }.not_to raise_error |
| 295 | + expect(feature.expression).to eq(expression) |
| 296 | + expect { feature.disable_expression }.not_to raise_error |
| 297 | + expect(feature.expression).to be_nil |
| 298 | + end |
| 299 | + end |
| 300 | + |
| 301 | + it 'does not hold onto connections during write operations' do |
| 302 | + clear_active_connections! |
| 303 | + |
| 304 | + abstract_class.connected_to(role: :reading) do |
| 305 | + flipper = Flipper.new(adapter_with_roles) |
| 306 | + feature = flipper[:connection_test] |
| 307 | + |
| 308 | + feature.enable |
| 309 | + expect(active_connections?).to be(false) |
| 310 | + end |
213 | 311 | end |
214 | 312 | end |
215 | 313 | end |
|
265 | 363 | end |
266 | 364 | end |
267 | 365 | end |
| 366 | + |
| 367 | + def active_connections? |
| 368 | + method = ActiveRecord::Base.connection_handler.method(:active_connections?) |
| 369 | + method.arity == 0 ? method.call : method.call(:all) |
| 370 | + end |
| 371 | + |
| 372 | + def clear_active_connections! |
| 373 | + method = ActiveRecord::Base.connection_handler.method(:clear_active_connections!) |
| 374 | + method.arity == 0 ? method.call : method.call(:all) |
| 375 | + end |
| 376 | + |
| 377 | + def clear_all_connections! |
| 378 | + method = ActiveRecord::Base.connection_handler.method(:clear_all_connections!) |
| 379 | + method.arity == 0 ? method.call : method.call(:all) |
| 380 | + end |
268 | 381 | end |
0 commit comments