Skip to content

Commit e833146

Browse files
authored
Merge pull request #1145 from pranavbabu/main
Add Money.with_bank for Dynamic Currency Stores
2 parents 531b435 + e99ada0 commit e833146

File tree

4 files changed

+95
-0
lines changed

4 files changed

+95
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- Add `Currency#cents_based?` to check if currency is cents-based
2323
- Add ability to nest `Money.with_rounding_mode` blocks
2424
- Allow `nil` to be used as a default_currency
25+
- Add ability to nest `Money.with_bank` blocks
2526

2627
## 6.19.0
2728

lib/money/money.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,37 @@ def self.default_currency=(currency)
154154
@default_currency = currency
155155
end
156156

157+
# Modified to support thread-local bank override
157158
def self.default_bank
159+
# Check for thread-local bank first, then fall back to global default
160+
return Thread.current[:money_bank] if Thread.current[:money_bank]
161+
158162
if @default_bank.respond_to?(:call)
159163
@default_bank.call
160164
else
161165
@default_bank
162166
end
163167
end
164168

169+
# Thread-safe bank switching method
170+
# Temporarily changes the default bank in the current thread only
171+
#
172+
# @param [Money::Bank::Base] bank The bank to use within the block
173+
# @yield The block within which the bank will be changed
174+
# @return [Object] block results
175+
#
176+
# @example
177+
# Money.with_bank(european_bank) do
178+
# Money.new(100, "USD").exchange_to("EUR")
179+
# end
180+
def self.with_bank(bank)
181+
original_bank = Thread.current[:money_bank]
182+
Thread.current[:money_bank] = bank
183+
yield
184+
ensure
185+
Thread.current[:money_bank] = original_bank
186+
end
187+
165188
def self.locale_backend=(value)
166189
@locale_backend = value ? LocaleBackend.find(value) : nil
167190
end

sig/lib/money/money.rbs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,18 @@ class Money
233233
# end
234234
def self.with_rounding_mode: (untyped mode) { () -> untyped } -> untyped
235235

236+
# Temporarily changes the default bank in the current thread only
237+
#
238+
# @param [Money::Bank::Base] bank The bank to use within the block
239+
# @yield The block within which the bank will be changed
240+
# @return [Object] block results
241+
#
242+
# @example
243+
# Money.with_bank(european_bank) do
244+
# Money.new(100, "USD").exchange_to("EUR")
245+
# end
246+
def self.with_bank: (Money::Bank::Base bank) { () -> untyped } -> untyped
247+
236248
# Adds a new exchange rate to the default bank and return the rate.
237249
#
238250
# @param [Currency, String, Symbol] from_currency Currency to exchange from.

spec/money_spec.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,65 @@
310310
end
311311
end
312312

313+
describe '.with_bank' do
314+
it 'fallbacks to old bank after block' do
315+
old_bank = Money.default_bank
316+
317+
bank = Money::Bank::VariableExchange.new
318+
319+
Money.with_bank(bank) {}
320+
expect(Money.default_bank).to eq(old_bank)
321+
end
322+
323+
it "uses the correct bank inside block" do
324+
old_bank = Money.default_bank
325+
custom_store = double
326+
bank = Money::Bank::VariableExchange.new(custom_store)
327+
Money.with_bank(bank) do
328+
expect(custom_store).to receive(:add_rate).with("USD", "EUR", 0.5)
329+
Money.add_rate("USD", "EUR", 0.5)
330+
end
331+
332+
expect(old_bank).to receive(:add_rate).with("UAH", "NOK", 0.8)
333+
Money.add_rate("UAH", "NOK", 0.8)
334+
end
335+
336+
it 'safely handles concurrent usage in different threads' do
337+
results = []
338+
results_mutex = Mutex.new
339+
threads = []
340+
341+
custom_store = double
342+
custom_bank = Money::Bank::VariableExchange.new(custom_store)
343+
344+
2.times do |i|
345+
threads << Thread.new do
346+
bank_to_use = custom_bank
347+
expected_bank = custom_bank
348+
349+
Money.with_bank(bank_to_use) do
350+
sleep(0.01)
351+
352+
actual_bank = ::Money.default_bank
353+
result = {
354+
thread_id: Thread.current.object_id,
355+
expected: expected_bank,
356+
actual: actual_bank,
357+
match: expected_bank == actual_bank
358+
}
359+
results_mutex.synchronize do
360+
results << result
361+
end
362+
end
363+
end
364+
end
365+
366+
threads.each(&:join)
367+
mismatches = results.reject { |r| r[:match] }
368+
expect(mismatches.length).to be_zero
369+
end
370+
end
371+
313372
%w[cents pence].each do |units|
314373
describe "##{units}" do
315374
it "is a synonym of #fractional" do

0 commit comments

Comments
 (0)