shopify-money 3.2.3 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/lib/money/version.rb +1 -1
- data/lib/money_column/active_record_hooks.rb +52 -20
- data/spec/money_column_spec.rb +98 -73
- data/spec/schema.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22d6b7f34e2df9d08722a1b5a3c4297bb95d68557b7e82c448476478336cf339
|
4
|
+
data.tar.gz: c267cac812901641a799ddc08afd446051f808c632d75ef62c8c80ad4237bf72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4da3c3318564210997ae4e3a56c99e46d4702de1c19d258f61948f7aee0b978c7ccaea8f9be13d270216af9ad23d52d25e48a44e514e154014bd179ca217403f
|
7
|
+
data.tar.gz: e1378b9689d6e7a22cf4d9892a4802e09ecdca0e67b4bb3bc5a5c7c924fa06c3fdf0bea6ab5b4f65db206cd06c4becd5f23b0f46381ad425350aa4925d4b9e8b
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shopify-money (3.2.
|
4
|
+
shopify-money (3.2.4)
|
5
5
|
bigdecimal (>= 3.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -81,7 +81,7 @@ GEM
|
|
81
81
|
ast (2.4.3)
|
82
82
|
base64 (0.2.0)
|
83
83
|
benchmark (0.4.0)
|
84
|
-
bigdecimal (3.
|
84
|
+
bigdecimal (3.2.2)
|
85
85
|
builder (3.3.0)
|
86
86
|
byebug (12.0.0)
|
87
87
|
coderay (1.1.3)
|
data/lib/money/version.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module MoneyColumn
|
4
|
-
class
|
4
|
+
class Error < StandardError; end
|
5
|
+
class CurrencyReadOnlyError < Error; end
|
6
|
+
class CurrencyMismatchError < Error; end
|
5
7
|
|
6
8
|
module ActiveRecordHooks
|
7
9
|
def self.included(base)
|
@@ -52,33 +54,63 @@ module MoneyColumn
|
|
52
54
|
return self[column] = nil
|
53
55
|
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
+
if money.is_a?(Money)
|
58
|
+
write_currency(column, money, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
self[column] = Money::Helpers.value_to_decimal(money)
|
62
|
+
end
|
63
|
+
|
64
|
+
def write_currency(column, money, options)
|
65
|
+
currency_column = options[:currency_column]
|
66
|
+
|
67
|
+
if options[:currency]
|
68
|
+
validate_hardcoded_currency_compatibility!(column, money, options[:currency])
|
69
|
+
return
|
57
70
|
end
|
58
71
|
|
59
72
|
if options[:currency_read_only]
|
60
|
-
|
61
|
-
|
62
|
-
if Money::Config.current.legacy_deprecations
|
63
|
-
Money.deprecate(msg)
|
64
|
-
else
|
65
|
-
raise MoneyColumn::CurrencyReadOnlyError, msg
|
66
|
-
end
|
67
|
-
end
|
68
|
-
else
|
69
|
-
self[options[:currency_column]] = money.currency.to_s unless money.no_currency?
|
73
|
+
validate_currency_compatibility!(column, money, currency_column)
|
74
|
+
return
|
70
75
|
end
|
71
76
|
|
72
|
-
|
77
|
+
if currency_column && !money.no_currency?
|
78
|
+
self[currency_column] = money.currency.to_s
|
79
|
+
end
|
73
80
|
end
|
74
81
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
82
|
+
def read_currency_column(currency_column)
|
83
|
+
if @money_raw_new_attributes&.key?(currency_column.to_sym)
|
84
|
+
# currency column in the process of being updated
|
85
|
+
return @money_raw_new_attributes[currency_column.to_sym]
|
86
|
+
end
|
80
87
|
|
81
|
-
|
88
|
+
try(currency_column)
|
89
|
+
end
|
90
|
+
|
91
|
+
def validate_hardcoded_currency_compatibility!(column, money, expected_currency)
|
92
|
+
return if money.currency.compatible?(Money::Helpers.value_to_currency(expected_currency))
|
93
|
+
|
94
|
+
msg = "Invalid #{column}: attempting to write a money object with currency '#{money.currency}' to a record with hard-coded currency '#{expected_currency}'."
|
95
|
+
if Money::Config.current.legacy_deprecations
|
96
|
+
Money.deprecate(msg)
|
97
|
+
else
|
98
|
+
raise MoneyColumn::CurrencyMismatchError, msg
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_currency_compatibility!(column, money, currency_column)
|
103
|
+
current_currency = read_currency_column(currency_column)
|
104
|
+
return if current_currency.nil? || money.currency.compatible?(Money::Helpers.value_to_currency(current_currency))
|
105
|
+
|
106
|
+
msg = "Invalid #{column}: attempting to write a money object with currency '#{money.currency}' to a record with currency '#{current_currency}'. " \
|
107
|
+
"If you do want to change the record's currency, either remove `currency_read_only` or update the record's currency manually"
|
108
|
+
|
109
|
+
if Money::Config.current.legacy_deprecations
|
110
|
+
Money.deprecate(msg)
|
111
|
+
else
|
112
|
+
raise MoneyColumn::CurrencyReadOnlyError, msg
|
113
|
+
end
|
82
114
|
end
|
83
115
|
|
84
116
|
def _assign_attributes(new_attributes)
|
data/spec/money_column_spec.rb
CHANGED
@@ -6,47 +6,47 @@ class MoneyRecord < ActiveRecord::Base
|
|
6
6
|
before_validation do
|
7
7
|
self.price_usd = Money.new(self["price"] * RATE, 'USD') if self["price"]
|
8
8
|
end
|
9
|
-
money_column :price, currency_column: '
|
10
|
-
money_column :prix, currency_column: :
|
9
|
+
money_column :price, currency_column: 'price_currency'
|
10
|
+
money_column :prix, currency_column: :prix_currency
|
11
11
|
money_column :price_usd, currency: 'USD'
|
12
12
|
end
|
13
13
|
|
14
14
|
class MoneyWithValidation < ActiveRecord::Base
|
15
15
|
self.table_name = 'money_records'
|
16
|
-
validates :price, :
|
17
|
-
money_column :price, currency_column: '
|
16
|
+
validates :price, :price_currency, presence: true
|
17
|
+
money_column :price, currency_column: 'price_currency'
|
18
18
|
end
|
19
19
|
|
20
20
|
class MoneyWithReadOnlyCurrency < ActiveRecord::Base
|
21
21
|
self.table_name = 'money_records'
|
22
|
-
money_column :price, currency_column: '
|
22
|
+
money_column :price, currency_column: 'price_currency', currency_read_only: true
|
23
23
|
end
|
24
24
|
|
25
25
|
class MoneyRecordCoerceNull < ActiveRecord::Base
|
26
26
|
self.table_name = 'money_records'
|
27
|
-
money_column :price, currency_column: '
|
27
|
+
money_column :price, currency_column: 'price_currency', coerce_null: true
|
28
28
|
money_column :price_usd, currency: 'USD', coerce_null: true
|
29
29
|
end
|
30
30
|
|
31
31
|
class MoneyWithDelegatedCurrency < ActiveRecord::Base
|
32
32
|
self.table_name = 'money_records'
|
33
|
-
delegate :
|
34
|
-
money_column :price, currency_column: '
|
33
|
+
delegate :price_currency, to: :delegated_record
|
34
|
+
money_column :price, currency_column: 'price_currency', currency_read_only: true
|
35
35
|
money_column :prix, currency_column: 'currency2', currency_read_only: true
|
36
36
|
def currency2
|
37
|
-
delegated_record.
|
37
|
+
delegated_record.price_currency
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
41
41
|
|
42
42
|
def delegated_record
|
43
|
-
MoneyRecord.new(
|
43
|
+
MoneyRecord.new(price_currency: 'USD')
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
class MoneyWithCustomAccessors < ActiveRecord::Base
|
48
48
|
self.table_name = 'money_records'
|
49
|
-
money_column :price, currency_column: '
|
49
|
+
money_column :price, currency_column: 'price_currency'
|
50
50
|
def price
|
51
51
|
read_money_attribute(:price)
|
52
52
|
end
|
@@ -56,7 +56,7 @@ class MoneyWithCustomAccessors < ActiveRecord::Base
|
|
56
56
|
end
|
57
57
|
|
58
58
|
class MoneyClassInheritance < MoneyWithCustomAccessors
|
59
|
-
money_column :prix, currency_column: '
|
59
|
+
money_column :prix, currency_column: 'price_currency'
|
60
60
|
end
|
61
61
|
|
62
62
|
class MoneyClassInheritance2 < MoneyWithCustomAccessors
|
@@ -71,7 +71,7 @@ RSpec.describe 'MoneyColumn' do
|
|
71
71
|
let(:toonie) { Money.new(2.00, 'CAD') }
|
72
72
|
let(:subject) { MoneyRecord.new(price: money, prix: toonie) }
|
73
73
|
let(:record) do
|
74
|
-
subject.
|
74
|
+
subject.prix_currency = 'CAD'
|
75
75
|
subject.save
|
76
76
|
subject.reload
|
77
77
|
end
|
@@ -81,7 +81,7 @@ RSpec.describe 'MoneyColumn' do
|
|
81
81
|
end
|
82
82
|
|
83
83
|
it 'writes the currency to the db' do
|
84
|
-
record.update(
|
84
|
+
record.update(price_currency: nil)
|
85
85
|
record.update(price: Money.new(4, 'JPY'))
|
86
86
|
record.reload
|
87
87
|
expect(record.price.value).to eq(4)
|
@@ -101,10 +101,35 @@ RSpec.describe 'MoneyColumn' do
|
|
101
101
|
expect(record.price_usd).to eq(Money.new(1.44, 'USD'))
|
102
102
|
end
|
103
103
|
|
104
|
+
describe 'hard-coded currency (currency: "USD")' do
|
105
|
+
let(:record) { MoneyRecord.new }
|
106
|
+
|
107
|
+
it 'raises CurrencyMismatchError when assigning Money with wrong currency' do
|
108
|
+
expect {
|
109
|
+
record.price_usd = Money.new(5, 'EUR')
|
110
|
+
}.to raise_error(MoneyColumn::CurrencyMismatchError)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'allows assigning Money with the correct currency' do
|
114
|
+
record.price_usd = Money.new(8, 'USD')
|
115
|
+
expect(record.price_usd.value).to eq(8)
|
116
|
+
expect(record.price_usd.currency.to_s).to eq('USD')
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'deprecates (but does not raise) under legacy_deprecations' do
|
120
|
+
configure(legacy_deprecations: true) do
|
121
|
+
expect(Money).to receive(:deprecate).once
|
122
|
+
record.price_usd = Money.new(9, 'EUR')
|
123
|
+
expect(record.price_usd.value).to eq(9)
|
124
|
+
expect(record.price_usd.currency.to_s).to eq('USD')
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
104
129
|
it 'returns money with null currency when the currency in the DB is invalid' do
|
105
130
|
configure(legacy_deprecations: true) do
|
106
131
|
expect(Money).to receive(:deprecate).once
|
107
|
-
record.update_columns(
|
132
|
+
record.update_columns(price_currency: 'invalid')
|
108
133
|
record.reload
|
109
134
|
expect(record.price.currency).to be_a(Money::NullCurrency)
|
110
135
|
expect(record.price.value).to eq(1.23)
|
@@ -142,15 +167,15 @@ RSpec.describe 'MoneyColumn' do
|
|
142
167
|
end
|
143
168
|
|
144
169
|
it 'does not overwrite a currency column with a default currency when saving zero' do
|
145
|
-
expect(record.
|
170
|
+
expect(record.price_currency.to_s).to eq('EUR')
|
146
171
|
record.update(price: Money.new(0, Money::NULL_CURRENCY))
|
147
|
-
expect(record.
|
172
|
+
expect(record.price_currency.to_s).to eq('EUR')
|
148
173
|
end
|
149
174
|
|
150
175
|
it 'does overwrite a currency' do
|
151
|
-
expect(record.
|
176
|
+
expect(record.price_currency.to_s).to eq('EUR')
|
152
177
|
record.update(price: Money.new(4, 'JPY'))
|
153
|
-
expect(record.
|
178
|
+
expect(record.price_currency.to_s).to eq('JPY')
|
154
179
|
end
|
155
180
|
|
156
181
|
describe 'non-fractional-currencies' do
|
@@ -217,31 +242,31 @@ RSpec.describe 'MoneyColumn' do
|
|
217
242
|
|
218
243
|
it 'is not allowed to be saved because `to_s` returns a blank string' do
|
219
244
|
subject.valid?
|
220
|
-
expect(subject.errors[:
|
245
|
+
expect(subject.errors[:price_currency]).to include("can't be blank")
|
221
246
|
end
|
222
247
|
end
|
223
248
|
|
224
249
|
describe 'read_only_currency true' do
|
225
250
|
it 'raises CurrencyReadOnlyError when updating price with different currency' do
|
226
251
|
record = MoneyWithReadOnlyCurrency.create
|
227
|
-
record.update_columns(
|
252
|
+
record.update_columns(price_currency: 'USD')
|
228
253
|
expect { record.update(price: Money.new(4, 'CAD')) }.to raise_error(MoneyColumn::CurrencyReadOnlyError)
|
229
254
|
end
|
230
255
|
|
231
256
|
it 'raises CurrencyReadOnlyError when assigning money with different currency' do
|
232
|
-
record = MoneyWithReadOnlyCurrency.create(
|
257
|
+
record = MoneyWithReadOnlyCurrency.create(price_currency: 'USD', price: 1)
|
233
258
|
expect { record.price = Money.new(2, 'CAD') }.to raise_error(MoneyColumn::CurrencyReadOnlyError)
|
234
259
|
end
|
235
260
|
|
236
261
|
it 'allows updating price when currency matches existing currency' do
|
237
262
|
record = MoneyWithReadOnlyCurrency.create
|
238
|
-
record.update_columns(
|
263
|
+
record.update_columns(price_currency: 'USD')
|
239
264
|
record.update(price: Money.new(4, 'USD'))
|
240
265
|
expect(record.price.value).to eq(4)
|
241
266
|
end
|
242
267
|
|
243
268
|
it 'allows assigning price when currency matches existing currency' do
|
244
|
-
record = MoneyWithReadOnlyCurrency.create(
|
269
|
+
record = MoneyWithReadOnlyCurrency.create(price_currency: 'CAD', price: 1)
|
245
270
|
record.price = Money.new(2, 'CAD')
|
246
271
|
expect(record.price.value).to eq(2)
|
247
272
|
end
|
@@ -249,7 +274,7 @@ RSpec.describe 'MoneyColumn' do
|
|
249
274
|
it 'legacy_deprecations does not write the currency to the db' do
|
250
275
|
configure(legacy_deprecations: true) do
|
251
276
|
record = MoneyWithReadOnlyCurrency.create
|
252
|
-
record.update_columns(
|
277
|
+
record.update_columns(price_currency: 'USD')
|
253
278
|
|
254
279
|
expect(Money).to receive(:deprecate).once
|
255
280
|
record.update(price: Money.new(4, 'CAD'))
|
@@ -260,7 +285,7 @@ RSpec.describe 'MoneyColumn' do
|
|
260
285
|
|
261
286
|
it 'reads the currency that is already in the db' do
|
262
287
|
record = MoneyWithReadOnlyCurrency.create
|
263
|
-
record.update_columns(
|
288
|
+
record.update_columns(price_currency: 'USD', price: 1)
|
264
289
|
record.reload
|
265
290
|
expect(record.price.value).to eq(1)
|
266
291
|
expect(record.price.currency.to_s).to eq('USD')
|
@@ -270,7 +295,7 @@ RSpec.describe 'MoneyColumn' do
|
|
270
295
|
configure(legacy_deprecations: true) do
|
271
296
|
expect(Money).to receive(:deprecate).once
|
272
297
|
record = MoneyWithReadOnlyCurrency.create
|
273
|
-
record.update_columns(
|
298
|
+
record.update_columns(price_currency: 'invalid', price: 1)
|
274
299
|
record.reload
|
275
300
|
expect(record.price.value).to eq(1)
|
276
301
|
expect(record.price.currency.to_s).to eq('')
|
@@ -278,8 +303,8 @@ RSpec.describe 'MoneyColumn' do
|
|
278
303
|
end
|
279
304
|
|
280
305
|
it 'sets the currency correctly when the currency is changed' do
|
281
|
-
record = MoneyWithReadOnlyCurrency.create(
|
282
|
-
record.
|
306
|
+
record = MoneyWithReadOnlyCurrency.create(price_currency: 'CAD', price: 1)
|
307
|
+
record.price_currency = 'USD'
|
283
308
|
expect(record.price.currency.to_s).to eq('USD')
|
284
309
|
end
|
285
310
|
|
@@ -386,7 +411,7 @@ RSpec.describe 'MoneyColumn' do
|
|
386
411
|
|
387
412
|
describe 'class inheritance' do
|
388
413
|
it 'shares money columns declared on the parent class' do
|
389
|
-
expect(MoneyClassInheritance.instance_variable_get(:@money_column_options).dig('price', :currency_column)).to eq('
|
414
|
+
expect(MoneyClassInheritance.instance_variable_get(:@money_column_options).dig('price', :currency_column)).to eq('price_currency')
|
390
415
|
expect(MoneyClassInheritance.instance_variable_get(:@money_column_options).dig('price', :currency)).to eq(nil)
|
391
416
|
expect(MoneyClassInheritance.new(price: Money.new(1, 'USD')).price).to eq(Money.new(2, 'USD'))
|
392
417
|
end
|
@@ -410,7 +435,7 @@ RSpec.describe 'MoneyColumn' do
|
|
410
435
|
end
|
411
436
|
|
412
437
|
it 'writes currency from input value to the db' do
|
413
|
-
record.update(
|
438
|
+
record.update(price_currency: nil)
|
414
439
|
record.update(price: Money.new(7, 'GBP'))
|
415
440
|
record.reload
|
416
441
|
expect(record.price.value).to eq(7)
|
@@ -418,13 +443,13 @@ RSpec.describe 'MoneyColumn' do
|
|
418
443
|
end
|
419
444
|
|
420
445
|
it 'raises missing currency error reading a value that was saved using legacy non-money object' do
|
421
|
-
record.update(
|
446
|
+
record.update(price_currency: nil, price: 3)
|
422
447
|
expect { record.price }.to raise_error(ArgumentError, 'missing currency')
|
423
448
|
end
|
424
449
|
|
425
450
|
it 'handles legacy support for saving price and currency separately' do
|
426
|
-
record.update(
|
427
|
-
record.update(price: 7,
|
451
|
+
record.update(price_currency: nil)
|
452
|
+
record.update(price: 7, price_currency: 'GBP')
|
428
453
|
record.reload
|
429
454
|
expect(record.price.value).to eq(7)
|
430
455
|
expect(record.price.currency.to_s).to eq('GBP')
|
@@ -432,17 +457,17 @@ RSpec.describe 'MoneyColumn' do
|
|
432
457
|
end
|
433
458
|
|
434
459
|
describe 'updating amount and currency simultaneously' do
|
435
|
-
let(:record) { MoneyWithReadOnlyCurrency.create!(
|
460
|
+
let(:record) { MoneyWithReadOnlyCurrency.create!(price_currency: "CAD") }
|
436
461
|
|
437
462
|
it 'allows updating both amount and currency at the same time' do
|
438
463
|
record.update!(
|
439
464
|
price: Money.new(10, 'USD'),
|
440
|
-
|
465
|
+
price_currency: 'USD'
|
441
466
|
)
|
442
467
|
record.reload
|
443
468
|
expect(record.price.value).to eq(10)
|
444
469
|
expect(record.price.currency.to_s).to eq('USD')
|
445
|
-
expect(record.
|
470
|
+
expect(record.price_currency).to eq('USD')
|
446
471
|
end
|
447
472
|
end
|
448
473
|
|
@@ -451,7 +476,7 @@ RSpec.describe 'MoneyColumn' do
|
|
451
476
|
record = MoneyRecord.create!(
|
452
477
|
price: Money.new(100, 'USD'),
|
453
478
|
prix: Money.new(200, 'EUR'),
|
454
|
-
|
479
|
+
prix_currency: 'EUR'
|
455
480
|
)
|
456
481
|
record.reload
|
457
482
|
expect(record.price.value).to eq(100)
|
@@ -506,36 +531,36 @@ RSpec.describe 'MoneyColumn' do
|
|
506
531
|
it 'clears all money column caches when currency changes' do
|
507
532
|
record = MoneyRecord.new(
|
508
533
|
price: Money.new(100, 'USD'),
|
509
|
-
|
534
|
+
price_currency: 'USD'
|
510
535
|
)
|
511
536
|
|
512
537
|
expect(record.price).to eq(Money.new(100, 'USD'))
|
513
538
|
|
514
539
|
# Change currency should invalidate the cache
|
515
|
-
record.
|
540
|
+
record.price_currency = 'EUR'
|
516
541
|
expect(record.price.currency.to_s).to eq('EUR')
|
517
542
|
end
|
518
543
|
|
519
544
|
it 'only defines currency setter once for shared currency columns' do
|
520
545
|
class MoneyWithSharedCurrency < ActiveRecord::Base
|
521
546
|
self.table_name = 'money_records'
|
522
|
-
money_column :price, currency_column: '
|
523
|
-
money_column :prix, currency_column: '
|
547
|
+
money_column :price, currency_column: 'price_currency'
|
548
|
+
money_column :prix, currency_column: 'price_currency'
|
524
549
|
end
|
525
550
|
|
526
551
|
record = MoneyWithSharedCurrency.new
|
527
|
-
methods_count = record.methods.count { |m| m.to_s == '
|
552
|
+
methods_count = record.methods.count { |m| m.to_s == 'price_currency=' }
|
528
553
|
expect(methods_count).to eq(1)
|
529
554
|
end
|
530
555
|
end
|
531
556
|
|
532
557
|
describe 'no_currency handling' do
|
533
558
|
it 'does not write currency when money has no_currency' do
|
534
|
-
record = MoneyRecord.create!(
|
559
|
+
record = MoneyRecord.create!(price_currency: 'USD')
|
535
560
|
record.price = Money.new(100, Money::NULL_CURRENCY)
|
536
561
|
record.save!
|
537
562
|
record.reload
|
538
|
-
expect(record.
|
563
|
+
expect(record.price_currency).to eq('USD')
|
539
564
|
end
|
540
565
|
end
|
541
566
|
|
@@ -574,7 +599,7 @@ RSpec.describe 'MoneyColumn' do
|
|
574
599
|
describe 'ActiveRecord callbacks integration' do
|
575
600
|
class MoneyWithCallbacks < ActiveRecord::Base
|
576
601
|
self.table_name = 'money_records'
|
577
|
-
money_column :price, currency_column: '
|
602
|
+
money_column :price, currency_column: 'price_currency'
|
578
603
|
|
579
604
|
before_save :double_price
|
580
605
|
|
@@ -595,7 +620,7 @@ RSpec.describe 'MoneyColumn' do
|
|
595
620
|
describe 'validation integration' do
|
596
621
|
class MoneyWithCustomValidation < ActiveRecord::Base
|
597
622
|
self.table_name = 'money_records'
|
598
|
-
money_column :price, currency_column: '
|
623
|
+
money_column :price, currency_column: 'price_currency'
|
599
624
|
|
600
625
|
validate :price_must_be_positive
|
601
626
|
|
@@ -621,9 +646,9 @@ RSpec.describe 'MoneyColumn' do
|
|
621
646
|
describe 'ActiveRecord query interface' do
|
622
647
|
before do
|
623
648
|
MoneyRecord.delete_all
|
624
|
-
MoneyRecord.create!(price: Money.new(100, 'USD'),
|
625
|
-
MoneyRecord.create!(price: Money.new(200, 'USD'),
|
626
|
-
MoneyRecord.create!(price: Money.new(150, 'EUR'),
|
649
|
+
MoneyRecord.create!(price: Money.new(100, 'USD'), price_currency: 'USD')
|
650
|
+
MoneyRecord.create!(price: Money.new(200, 'USD'), price_currency: 'USD')
|
651
|
+
MoneyRecord.create!(price: Money.new(150, 'EUR'), price_currency: 'EUR')
|
627
652
|
end
|
628
653
|
|
629
654
|
it 'supports where queries with money values' do
|
@@ -663,13 +688,13 @@ RSpec.describe 'MoneyColumn' do
|
|
663
688
|
|
664
689
|
describe 'attribute assignment' do
|
665
690
|
it 'handles hash assignment with string keys' do
|
666
|
-
record = MoneyRecord.new('price' => 100, '
|
691
|
+
record = MoneyRecord.new('price' => 100, 'price_currency' => 'USD')
|
667
692
|
expect(record.price.value).to eq(100)
|
668
693
|
expect(record.price.currency.to_s).to eq('USD')
|
669
694
|
end
|
670
695
|
|
671
696
|
it 'handles hash assignment with symbol keys' do
|
672
|
-
record = MoneyRecord.new(price: 100,
|
697
|
+
record = MoneyRecord.new(price: 100, price_currency: 'USD')
|
673
698
|
expect(record.price.value).to eq(100)
|
674
699
|
expect(record.price.currency.to_s).to eq('USD')
|
675
700
|
end
|
@@ -698,7 +723,7 @@ RSpec.describe 'MoneyColumn' do
|
|
698
723
|
|
699
724
|
describe 'coerce_null with different scenarios' do
|
700
725
|
it 'coerces nil to zero money with proper currency from column' do
|
701
|
-
record = MoneyRecordCoerceNull.new(
|
726
|
+
record = MoneyRecordCoerceNull.new(price_currency: 'EUR')
|
702
727
|
expect(record.price.value).to eq(0)
|
703
728
|
expect(record.price.currency.to_s).to eq('EUR')
|
704
729
|
end
|
@@ -721,11 +746,11 @@ RSpec.describe 'MoneyColumn' do
|
|
721
746
|
record.price = Money.new(100, 'USD')
|
722
747
|
expect(record.price.value).to eq(100)
|
723
748
|
# Currency is not written for read_only columns when not saved
|
724
|
-
expect(record.
|
749
|
+
expect(record.price_currency).to be_nil
|
725
750
|
end
|
726
751
|
|
727
752
|
it 'allows setting money with compatible currency using string' do
|
728
|
-
record = MoneyWithReadOnlyCurrency.create!(
|
753
|
+
record = MoneyWithReadOnlyCurrency.create!(price_currency: 'USD')
|
729
754
|
record.price = Money.new(100, 'USD')
|
730
755
|
expect(record.price.value).to eq(100)
|
731
756
|
end
|
@@ -766,26 +791,26 @@ RSpec.describe 'MoneyColumn' do
|
|
766
791
|
end
|
767
792
|
|
768
793
|
it 'tracks currency changes' do
|
769
|
-
record = MoneyRecord.create!(
|
770
|
-
record.
|
794
|
+
record = MoneyRecord.create!(price_currency: 'USD', price: 100)
|
795
|
+
record.price_currency = 'EUR'
|
771
796
|
|
772
|
-
expect(record.
|
773
|
-
expect(record.
|
797
|
+
expect(record.price_currency_changed?).to be true
|
798
|
+
expect(record.price_currency_was).to eq('USD')
|
774
799
|
end
|
775
800
|
end
|
776
801
|
|
777
802
|
describe 'mass assignment with currency updates' do
|
778
803
|
it 'handles simultaneous updates of money and currency in mass assignment' do
|
779
|
-
record = MoneyWithReadOnlyCurrency.create!(
|
804
|
+
record = MoneyWithReadOnlyCurrency.create!(price_currency: 'USD', price: 100)
|
780
805
|
|
781
806
|
record.assign_attributes(
|
782
|
-
|
807
|
+
price_currency: 'EUR',
|
783
808
|
price: Money.new(200, 'EUR')
|
784
809
|
)
|
785
810
|
|
786
811
|
expect { record.save! }.not_to raise_error
|
787
812
|
expect(record.price.value).to eq(200)
|
788
|
-
expect(record.
|
813
|
+
expect(record.price_currency).to eq('EUR')
|
789
814
|
end
|
790
815
|
end
|
791
816
|
|
@@ -799,7 +824,7 @@ RSpec.describe 'MoneyColumn' do
|
|
799
824
|
|
800
825
|
it 'preserves full precision for currencies with 3 decimal places' do
|
801
826
|
# JOD has 3 minor units, so it preserves 3 decimal places
|
802
|
-
record = MoneyRecord.create!(price: Money.new(123.456, 'JOD'),
|
827
|
+
record = MoneyRecord.create!(price: Money.new(123.456, 'JOD'), price_currency: 'JOD')
|
803
828
|
record.reload
|
804
829
|
expect(record.price.value).to eq(123.456)
|
805
830
|
end
|
@@ -807,7 +832,7 @@ RSpec.describe 'MoneyColumn' do
|
|
807
832
|
it 'rounds database values beyond 3 decimal places' do
|
808
833
|
record = MoneyRecord.new
|
809
834
|
record['price'] = 123.4567
|
810
|
-
record.
|
835
|
+
record.price_currency = 'USD'
|
811
836
|
record.save!
|
812
837
|
record.reload
|
813
838
|
expect(record['price'].to_f.round(3)).to eq(123.457)
|
@@ -851,7 +876,7 @@ RSpec.describe 'MoneyColumn' do
|
|
851
876
|
it 'allows direct writing of raw decimal value' do
|
852
877
|
record = MoneyRecord.new
|
853
878
|
record['price'] = 99.99
|
854
|
-
record.
|
879
|
+
record.price_currency = 'EUR'
|
855
880
|
expect(record.price.value).to eq(99.99)
|
856
881
|
expect(record.price.currency.to_s).to eq('EUR')
|
857
882
|
end
|
@@ -900,7 +925,7 @@ RSpec.describe 'MoneyColumn' do
|
|
900
925
|
describe 'error messages' do
|
901
926
|
it 'provides clear error for missing currency when default_currency is nil' do
|
902
927
|
configure(default_currency: nil) do
|
903
|
-
record = MoneyRecord.create!(price: 100,
|
928
|
+
record = MoneyRecord.create!(price: 100, price_currency: nil)
|
904
929
|
expect { record.reload.price }.to raise_error(ArgumentError, 'missing currency')
|
905
930
|
end
|
906
931
|
end
|
@@ -909,16 +934,16 @@ RSpec.describe 'MoneyColumn' do
|
|
909
934
|
describe 'money column with different column names' do
|
910
935
|
class MoneyWithCustomColumns < ActiveRecord::Base
|
911
936
|
self.table_name = 'money_records'
|
912
|
-
money_column :price, currency_column: :
|
913
|
-
money_column :prix, currency_column: '
|
937
|
+
money_column :price, currency_column: :prix_currency
|
938
|
+
money_column :prix, currency_column: 'price_currency'
|
914
939
|
end
|
915
940
|
|
916
941
|
it 'supports both string and symbol currency column names' do
|
917
942
|
record = MoneyWithCustomColumns.new(
|
918
943
|
price: Money.new(100, 'EUR'),
|
919
|
-
|
944
|
+
prix_currency: 'EUR',
|
920
945
|
prix: Money.new(200, 'USD'),
|
921
|
-
|
946
|
+
price_currency: 'USD'
|
922
947
|
)
|
923
948
|
|
924
949
|
expect(record.price.currency.to_s).to eq('EUR')
|
@@ -929,14 +954,14 @@ RSpec.describe 'MoneyColumn' do
|
|
929
954
|
describe 'money column array syntax' do
|
930
955
|
class MoneyWithArrayColumns < ActiveRecord::Base
|
931
956
|
self.table_name = 'money_records'
|
932
|
-
money_column [:price, :prix], currency_column: '
|
957
|
+
money_column [:price, :prix], currency_column: 'price_currency'
|
933
958
|
end
|
934
959
|
|
935
960
|
it 'supports defining multiple columns at once' do
|
936
961
|
record = MoneyWithArrayColumns.new(
|
937
962
|
price: Money.new(100, 'USD'),
|
938
963
|
prix: Money.new(200, 'USD'),
|
939
|
-
|
964
|
+
price_currency: 'USD'
|
940
965
|
)
|
941
966
|
|
942
967
|
expect(record.price).to eq(Money.new(100, 'USD'))
|
@@ -961,7 +986,7 @@ RSpec.describe 'MoneyColumn' do
|
|
961
986
|
json = record.as_json
|
962
987
|
# Money columns are serialized as a hash with symbol keys
|
963
988
|
expect(json['price']).to eq({ currency: 'USD', value: '100.00' })
|
964
|
-
expect(json['
|
989
|
+
expect(json['price_currency']).to eq('USD')
|
965
990
|
end
|
966
991
|
end
|
967
992
|
|
data/spec/schema.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
ActiveRecord::Schema.define do
|
3
3
|
create_table "money_records", :force => true do |t|
|
4
4
|
t.decimal "price", precision: 20, scale: 3, default: '0.000'
|
5
|
-
t.string "
|
5
|
+
t.string "price_currency", limit: 3
|
6
6
|
t.decimal "prix", precision: 20, scale: 3, default: '0.000'
|
7
|
-
t.string "
|
7
|
+
t.string "prix_currency", limit: 3
|
8
8
|
t.decimal "price_usd"
|
9
9
|
end
|
10
10
|
end
|