shopify-money 3.2.2 → 3.2.3
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 +1 -1
- data/lib/money/version.rb +1 -1
- data/lib/money_column/active_record_hooks.rb +3 -3
- data/spec/money_column_spec.rb +548 -1
- 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: 36ae50865f51d78857ad4aadffe1a9acc8976fd248ba736d3e99b69487b5ba8a
|
4
|
+
data.tar.gz: 4c8365727256cc699e4f0429e45e85b458234c98a2509773a9c19bae303e9af4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbc2bac8bffd0456c62d2722108fd0c869a716018b43e99636f161efe72b7bbd000753b256d843d97d29bca34263e514d0f26f6d989b9898339c04fdfed724ee
|
7
|
+
data.tar.gz: 0eb55e57aeb3309adae9de14fc6cedad266708fa738809002c4d627751d699c7382062a81703e93476e34b40a5d045ca653c5e9f0761f5acb2f10f0277982bf7
|
data/Gemfile.lock
CHANGED
data/lib/money/version.rb
CHANGED
@@ -74,9 +74,9 @@ module MoneyColumn
|
|
74
74
|
|
75
75
|
def compatible_currency?(money, options)
|
76
76
|
currency_column = options[:currency_column]
|
77
|
-
currency = options[:currency]
|
78
|
-
|
79
|
-
|
77
|
+
currency = options[:currency]
|
78
|
+
currency ||= @money_raw_new_attributes[currency_column.to_sym] if @money_raw_new_attributes
|
79
|
+
currency ||= try(currency_column)
|
80
80
|
|
81
81
|
currency.nil? || money.currency.compatible?(Money::Helpers.value_to_currency(currency))
|
82
82
|
end
|
data/spec/money_column_spec.rb
CHANGED
@@ -222,12 +222,30 @@ RSpec.describe 'MoneyColumn' do
|
|
222
222
|
end
|
223
223
|
|
224
224
|
describe 'read_only_currency true' do
|
225
|
-
it '
|
225
|
+
it 'raises CurrencyReadOnlyError when updating price with different currency' do
|
226
226
|
record = MoneyWithReadOnlyCurrency.create
|
227
227
|
record.update_columns(currency: 'USD')
|
228
228
|
expect { record.update(price: Money.new(4, 'CAD')) }.to raise_error(MoneyColumn::CurrencyReadOnlyError)
|
229
229
|
end
|
230
230
|
|
231
|
+
it 'raises CurrencyReadOnlyError when assigning money with different currency' do
|
232
|
+
record = MoneyWithReadOnlyCurrency.create(currency: 'USD', price: 1)
|
233
|
+
expect { record.price = Money.new(2, 'CAD') }.to raise_error(MoneyColumn::CurrencyReadOnlyError)
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'allows updating price when currency matches existing currency' do
|
237
|
+
record = MoneyWithReadOnlyCurrency.create
|
238
|
+
record.update_columns(currency: 'USD')
|
239
|
+
record.update(price: Money.new(4, 'USD'))
|
240
|
+
expect(record.price.value).to eq(4)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'allows assigning price when currency matches existing currency' do
|
244
|
+
record = MoneyWithReadOnlyCurrency.create(currency: 'CAD', price: 1)
|
245
|
+
record.price = Money.new(2, 'CAD')
|
246
|
+
expect(record.price.value).to eq(2)
|
247
|
+
end
|
248
|
+
|
231
249
|
it 'legacy_deprecations does not write the currency to the db' do
|
232
250
|
configure(legacy_deprecations: true) do
|
233
251
|
record = MoneyWithReadOnlyCurrency.create
|
@@ -427,4 +445,533 @@ RSpec.describe 'MoneyColumn' do
|
|
427
445
|
expect(record.currency).to eq('USD')
|
428
446
|
end
|
429
447
|
end
|
448
|
+
|
449
|
+
describe 'multiple money columns' do
|
450
|
+
it 'handles multiple money columns with different currencies' do
|
451
|
+
record = MoneyRecord.create!(
|
452
|
+
price: Money.new(100, 'USD'),
|
453
|
+
prix: Money.new(200, 'EUR'),
|
454
|
+
devise: 'EUR'
|
455
|
+
)
|
456
|
+
record.reload
|
457
|
+
expect(record.price.value).to eq(100)
|
458
|
+
expect(record.price.currency.to_s).to eq('USD')
|
459
|
+
expect(record.prix.value).to eq(200)
|
460
|
+
expect(record.prix.currency.to_s).to eq('EUR')
|
461
|
+
# price_usd is calculated from price * RATE (1.17) in before_validation
|
462
|
+
expect(record.price_usd.value).to eq(117)
|
463
|
+
expect(record.price_usd.currency.to_s).to eq('USD')
|
464
|
+
end
|
465
|
+
|
466
|
+
it 'maintains separate caches for each money column' do
|
467
|
+
record = MoneyRecord.new
|
468
|
+
record.price = Money.new(100, 'USD')
|
469
|
+
record.prix = Money.new(200, 'EUR')
|
470
|
+
|
471
|
+
expect(record.price).to eq(Money.new(100, 'USD'))
|
472
|
+
expect(record.prix).to eq(Money.new(200, 'EUR'))
|
473
|
+
|
474
|
+
# Verify they're independent by changing one
|
475
|
+
record.price = Money.new(300, 'CAD')
|
476
|
+
expect(record.price).to eq(Money.new(300, 'CAD'))
|
477
|
+
expect(record.prix).to eq(Money.new(200, 'EUR'))
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
describe 'blank money handling' do
|
482
|
+
it 'handles empty string as nil' do
|
483
|
+
record = MoneyRecord.new(price: '')
|
484
|
+
expect(record.price).to be_nil
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'handles whitespace string as nil' do
|
488
|
+
record = MoneyRecord.new(price: ' ')
|
489
|
+
expect(record.price).to be_nil
|
490
|
+
end
|
491
|
+
|
492
|
+
it 'clears cache when setting to blank' do
|
493
|
+
record = MoneyRecord.new(price: Money.new(100, 'USD'))
|
494
|
+
expect(record.price).to eq(Money.new(100, 'USD'))
|
495
|
+
|
496
|
+
record.price = ''
|
497
|
+
expect(record.price).to be_nil
|
498
|
+
|
499
|
+
# Verify the cache was cleared by setting a new value
|
500
|
+
record.price = Money.new(200, 'EUR')
|
501
|
+
expect(record.price).to eq(Money.new(200, 'EUR'))
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
describe 'currency column cache clearing' do
|
506
|
+
it 'clears all money column caches when currency changes' do
|
507
|
+
record = MoneyRecord.new(
|
508
|
+
price: Money.new(100, 'USD'),
|
509
|
+
currency: 'USD'
|
510
|
+
)
|
511
|
+
|
512
|
+
expect(record.price).to eq(Money.new(100, 'USD'))
|
513
|
+
|
514
|
+
# Change currency should invalidate the cache
|
515
|
+
record.currency = 'EUR'
|
516
|
+
expect(record.price.currency.to_s).to eq('EUR')
|
517
|
+
end
|
518
|
+
|
519
|
+
it 'only defines currency setter once for shared currency columns' do
|
520
|
+
class MoneyWithSharedCurrency < ActiveRecord::Base
|
521
|
+
self.table_name = 'money_records'
|
522
|
+
money_column :price, currency_column: 'currency'
|
523
|
+
money_column :prix, currency_column: 'currency'
|
524
|
+
end
|
525
|
+
|
526
|
+
record = MoneyWithSharedCurrency.new
|
527
|
+
methods_count = record.methods.count { |m| m.to_s == 'currency=' }
|
528
|
+
expect(methods_count).to eq(1)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
describe 'no_currency handling' do
|
533
|
+
it 'does not write currency when money has no_currency' do
|
534
|
+
record = MoneyRecord.create!(currency: 'USD')
|
535
|
+
record.price = Money.new(100, Money::NULL_CURRENCY)
|
536
|
+
record.save!
|
537
|
+
record.reload
|
538
|
+
expect(record.currency).to eq('USD')
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
describe 'edge cases' do
|
543
|
+
it 'handles BigDecimal values' do
|
544
|
+
record = MoneyRecord.new(price: BigDecimal('123.45'))
|
545
|
+
expect(record.price.value).to eq(123.45)
|
546
|
+
end
|
547
|
+
|
548
|
+
it 'handles negative values' do
|
549
|
+
record = MoneyRecord.new(price: Money.new(-100, 'USD'))
|
550
|
+
record.save!
|
551
|
+
record.reload
|
552
|
+
expect(record.price.value).to eq(-100)
|
553
|
+
expect(record.price.currency.to_s).to eq('USD')
|
554
|
+
end
|
555
|
+
|
556
|
+
it 'handles very large values' do
|
557
|
+
large_value = BigDecimal('999999999999999.999')
|
558
|
+
record = MoneyRecord.new(price: Money.new(large_value, 'USD'))
|
559
|
+
record.save!
|
560
|
+
record.reload
|
561
|
+
# Database might round very large values
|
562
|
+
expect(record.price.value).to be_within(0.001).of(large_value)
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'handles zero values' do
|
566
|
+
record = MoneyRecord.new(price: Money.new(0, 'USD'))
|
567
|
+
record.save!
|
568
|
+
record.reload
|
569
|
+
expect(record.price.value).to eq(0)
|
570
|
+
expect(record.price.currency.to_s).to eq('USD')
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
describe 'ActiveRecord callbacks integration' do
|
575
|
+
class MoneyWithCallbacks < ActiveRecord::Base
|
576
|
+
self.table_name = 'money_records'
|
577
|
+
money_column :price, currency_column: 'currency'
|
578
|
+
|
579
|
+
before_save :double_price
|
580
|
+
|
581
|
+
private
|
582
|
+
|
583
|
+
def double_price
|
584
|
+
self.price = price * 2 if price
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
it 'works with before_save callbacks' do
|
589
|
+
record = MoneyWithCallbacks.new(price: Money.new(50, 'USD'))
|
590
|
+
record.save!
|
591
|
+
expect(record.price.value).to eq(100)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
describe 'validation integration' do
|
596
|
+
class MoneyWithCustomValidation < ActiveRecord::Base
|
597
|
+
self.table_name = 'money_records'
|
598
|
+
money_column :price, currency_column: 'currency'
|
599
|
+
|
600
|
+
validate :price_must_be_positive
|
601
|
+
|
602
|
+
private
|
603
|
+
|
604
|
+
def price_must_be_positive
|
605
|
+
errors.add(:price, 'must be positive') if price && price.value < 0
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
it 'works with custom validations' do
|
610
|
+
record = MoneyWithCustomValidation.new(price: Money.new(-10, 'USD'))
|
611
|
+
expect(record).not_to be_valid
|
612
|
+
expect(record.errors[:price]).to include('must be positive')
|
613
|
+
end
|
614
|
+
|
615
|
+
it 'allows valid values' do
|
616
|
+
record = MoneyWithCustomValidation.new(price: Money.new(10, 'USD'))
|
617
|
+
expect(record).to be_valid
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
describe 'ActiveRecord query interface' do
|
622
|
+
before do
|
623
|
+
MoneyRecord.delete_all
|
624
|
+
MoneyRecord.create!(price: Money.new(100, 'USD'), currency: 'USD')
|
625
|
+
MoneyRecord.create!(price: Money.new(200, 'USD'), currency: 'USD')
|
626
|
+
MoneyRecord.create!(price: Money.new(150, 'EUR'), currency: 'EUR')
|
627
|
+
end
|
628
|
+
|
629
|
+
it 'supports where queries with money values' do
|
630
|
+
records = MoneyRecord.where(price: 100)
|
631
|
+
expect(records.count).to eq(1)
|
632
|
+
expect(records.first.price.value).to eq(100)
|
633
|
+
end
|
634
|
+
|
635
|
+
it 'supports range queries' do
|
636
|
+
records = MoneyRecord.where(price: 100..200)
|
637
|
+
expect(records.count).to eq(3)
|
638
|
+
end
|
639
|
+
|
640
|
+
it 'supports ordering by money columns' do
|
641
|
+
records = MoneyRecord.order(:price)
|
642
|
+
expect(records.map { |r| r.price.value }).to eq([100, 150, 200])
|
643
|
+
end
|
644
|
+
|
645
|
+
it 'supports pluck with money columns' do
|
646
|
+
values = MoneyRecord.pluck(:price)
|
647
|
+
expect(values).to contain_exactly(100, 200, 150)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
describe 'thread safety' do
|
652
|
+
it 'maintains separate caches per instance' do
|
653
|
+
record1 = MoneyRecord.new
|
654
|
+
record2 = MoneyRecord.new
|
655
|
+
|
656
|
+
record1.price = Money.new(100, 'USD')
|
657
|
+
record2.price = Money.new(200, 'EUR')
|
658
|
+
|
659
|
+
expect(record1.price).to eq(Money.new(100, 'USD'))
|
660
|
+
expect(record2.price).to eq(Money.new(200, 'EUR'))
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
describe 'attribute assignment' do
|
665
|
+
it 'handles hash assignment with string keys' do
|
666
|
+
record = MoneyRecord.new('price' => 100, 'currency' => 'USD')
|
667
|
+
expect(record.price.value).to eq(100)
|
668
|
+
expect(record.price.currency.to_s).to eq('USD')
|
669
|
+
end
|
670
|
+
|
671
|
+
it 'handles hash assignment with symbol keys' do
|
672
|
+
record = MoneyRecord.new(price: 100, currency: 'USD')
|
673
|
+
expect(record.price.value).to eq(100)
|
674
|
+
expect(record.price.currency.to_s).to eq('USD')
|
675
|
+
end
|
676
|
+
|
677
|
+
it 'handles update_attributes' do
|
678
|
+
record = MoneyRecord.create!(price: Money.new(100, 'USD'))
|
679
|
+
record.update!(price: Money.new(200, 'EUR'))
|
680
|
+
expect(record.price.value).to eq(200)
|
681
|
+
expect(record.price.currency.to_s).to eq('EUR')
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
describe 'error handling' do
|
686
|
+
it 'provides helpful error message for invalid currency in money object' do
|
687
|
+
expect {
|
688
|
+
MoneyRecord.new(price: Money.new(100, 'INVALID'))
|
689
|
+
}.to raise_error(Money::Currency::UnknownCurrency)
|
690
|
+
end
|
691
|
+
|
692
|
+
it 'handles non-numeric string values' do
|
693
|
+
expect {
|
694
|
+
MoneyRecord.new(price: 'not a number')
|
695
|
+
}.to raise_error(ArgumentError)
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
describe 'coerce_null with different scenarios' do
|
700
|
+
it 'coerces nil to zero money with proper currency from column' do
|
701
|
+
record = MoneyRecordCoerceNull.new(currency: 'EUR')
|
702
|
+
expect(record.price.value).to eq(0)
|
703
|
+
expect(record.price.currency.to_s).to eq('EUR')
|
704
|
+
end
|
705
|
+
|
706
|
+
it 'coerces nil to zero money with hardcoded currency' do
|
707
|
+
record = MoneyRecordCoerceNull.new
|
708
|
+
expect(record.price_usd.value).to eq(0)
|
709
|
+
expect(record.price_usd.currency.to_s).to eq('USD')
|
710
|
+
end
|
711
|
+
|
712
|
+
it 'does not coerce non-nil values' do
|
713
|
+
record = MoneyRecordCoerceNull.new(price: Money.new(100, 'USD'))
|
714
|
+
expect(record.price.value).to eq(100)
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
describe 'currency_read_only with edge cases' do
|
719
|
+
it 'allows setting money when currency column is nil' do
|
720
|
+
record = MoneyWithReadOnlyCurrency.new
|
721
|
+
record.price = Money.new(100, 'USD')
|
722
|
+
expect(record.price.value).to eq(100)
|
723
|
+
# Currency is not written for read_only columns when not saved
|
724
|
+
expect(record.currency).to be_nil
|
725
|
+
end
|
726
|
+
|
727
|
+
it 'allows setting money with compatible currency using string' do
|
728
|
+
record = MoneyWithReadOnlyCurrency.create!(currency: 'USD')
|
729
|
+
record.price = Money.new(100, 'USD')
|
730
|
+
expect(record.price.value).to eq(100)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
describe 'initialize_dup behavior' do
|
735
|
+
it 'creates independent cache for duplicated record' do
|
736
|
+
original = MoneyRecord.new(price: Money.new(100, 'USD'))
|
737
|
+
duplicate = original.dup
|
738
|
+
|
739
|
+
duplicate.price = Money.new(200, 'EUR')
|
740
|
+
|
741
|
+
expect(original.price).to eq(Money.new(100, 'USD'))
|
742
|
+
expect(duplicate.price).to eq(Money.new(200, 'EUR'))
|
743
|
+
end
|
744
|
+
|
745
|
+
it 'preserves money values when duplicating' do
|
746
|
+
original = MoneyRecord.create!(
|
747
|
+
price: Money.new(100, 'USD'),
|
748
|
+
prix: Money.new(200, 'EUR')
|
749
|
+
)
|
750
|
+
|
751
|
+
duplicate = original.dup
|
752
|
+
expect(duplicate.price).to eq(Money.new(100, 'USD'))
|
753
|
+
expect(duplicate.prix).to eq(Money.new(200, 'EUR'))
|
754
|
+
expect(duplicate).to be_new_record
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
describe 'ActiveRecord dirty tracking' do
|
759
|
+
it 'tracks changes to money columns' do
|
760
|
+
record = MoneyRecord.create!(price: Money.new(100, 'USD'))
|
761
|
+
record.price = Money.new(200, 'USD')
|
762
|
+
|
763
|
+
expect(record.price_changed?).to be true
|
764
|
+
expect(record.price_was).to eq(100)
|
765
|
+
expect(record.price_change).to eq([100, 200])
|
766
|
+
end
|
767
|
+
|
768
|
+
it 'tracks currency changes' do
|
769
|
+
record = MoneyRecord.create!(currency: 'USD', price: 100)
|
770
|
+
record.currency = 'EUR'
|
771
|
+
|
772
|
+
expect(record.currency_changed?).to be true
|
773
|
+
expect(record.currency_was).to eq('USD')
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
describe 'mass assignment with currency updates' do
|
778
|
+
it 'handles simultaneous updates of money and currency in mass assignment' do
|
779
|
+
record = MoneyWithReadOnlyCurrency.create!(currency: 'USD', price: 100)
|
780
|
+
|
781
|
+
record.assign_attributes(
|
782
|
+
currency: 'EUR',
|
783
|
+
price: Money.new(200, 'EUR')
|
784
|
+
)
|
785
|
+
|
786
|
+
expect { record.save! }.not_to raise_error
|
787
|
+
expect(record.price.value).to eq(200)
|
788
|
+
expect(record.currency).to eq('EUR')
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
792
|
+
describe 'decimal precision handling' do
|
793
|
+
it 'preserves precision up to currency minor units' do
|
794
|
+
# USD has 2 minor units, so 123.456 will be rounded to 123.46
|
795
|
+
record = MoneyRecord.create!(price: Money.new(123.456, 'USD'))
|
796
|
+
record.reload
|
797
|
+
expect(record.price.value.to_f).to eq(123.46)
|
798
|
+
end
|
799
|
+
|
800
|
+
it 'preserves full precision for currencies with 3 decimal places' do
|
801
|
+
# JOD has 3 minor units, so it preserves 3 decimal places
|
802
|
+
record = MoneyRecord.create!(price: Money.new(123.456, 'JOD'), currency: 'JOD')
|
803
|
+
record.reload
|
804
|
+
expect(record.price.value).to eq(123.456)
|
805
|
+
end
|
806
|
+
|
807
|
+
it 'rounds database values beyond 3 decimal places' do
|
808
|
+
record = MoneyRecord.new
|
809
|
+
record['price'] = 123.4567
|
810
|
+
record.currency = 'USD'
|
811
|
+
record.save!
|
812
|
+
record.reload
|
813
|
+
expect(record['price'].to_f.round(3)).to eq(123.457)
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
describe 'ActiveRecord Type integration' do
|
818
|
+
it 'uses MoneyColumn::ActiveRecordType for money columns' do
|
819
|
+
type = MoneyRecord.attribute_types['price']
|
820
|
+
expect(type).to be_a(MoneyColumn::ActiveRecordType)
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
describe 'money column options inheritance' do
|
825
|
+
it 'does not share options between different models' do
|
826
|
+
class MoneyModel1 < ActiveRecord::Base
|
827
|
+
self.table_name = 'money_records'
|
828
|
+
money_column :price, currency_column: 'currency'
|
829
|
+
end
|
830
|
+
|
831
|
+
class MoneyModel2 < ActiveRecord::Base
|
832
|
+
self.table_name = 'money_records'
|
833
|
+
money_column :price, currency: 'EUR'
|
834
|
+
end
|
835
|
+
|
836
|
+
expect(MoneyModel1.money_column_options['price'][:currency_column]).to eq('currency')
|
837
|
+
expect(MoneyModel1.money_column_options['price'][:currency]).to be_nil
|
838
|
+
|
839
|
+
expect(MoneyModel2.money_column_options['price'][:currency]).to eq('EUR')
|
840
|
+
expect(MoneyModel2.money_column_options['price'][:currency_column]).to be_nil
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
describe 'raw attributes access' do
|
845
|
+
it 'allows direct access to raw decimal value' do
|
846
|
+
record = MoneyRecord.create!(price: Money.new(123.45, 'USD'))
|
847
|
+
expect(record['price']).to eq(123.45)
|
848
|
+
expect(record.read_attribute(:price)).to eq(123.45)
|
849
|
+
end
|
850
|
+
|
851
|
+
it 'allows direct writing of raw decimal value' do
|
852
|
+
record = MoneyRecord.new
|
853
|
+
record['price'] = 99.99
|
854
|
+
record.currency = 'EUR'
|
855
|
+
expect(record.price.value).to eq(99.99)
|
856
|
+
expect(record.price.currency.to_s).to eq('EUR')
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
describe 'nil handling' do
|
861
|
+
it 'returns Money with default currency for zero values' do
|
862
|
+
record = MoneyRecord.new
|
863
|
+
# The default value in the schema is 0.000, not nil
|
864
|
+
expect(record['price']).to eq(0)
|
865
|
+
# With default currency CAD, it returns Money with 0 value
|
866
|
+
expect(record.price).to eq(Money.new(0, 'CAD'))
|
867
|
+
end
|
868
|
+
|
869
|
+
it 'returns nil when value is explicitly nil' do
|
870
|
+
record = MoneyRecord.new
|
871
|
+
record['price'] = nil
|
872
|
+
expect(record.price).to be_nil
|
873
|
+
end
|
874
|
+
|
875
|
+
it 'handles nil assignment' do
|
876
|
+
record = MoneyRecord.create!(price: Money.new(100, 'USD'))
|
877
|
+
record.price = nil
|
878
|
+
record.save!
|
879
|
+
record.reload
|
880
|
+
expect(record.price).to be_nil
|
881
|
+
end
|
882
|
+
end
|
883
|
+
|
884
|
+
describe 'currency normalization' do
|
885
|
+
it 'normalizes currency strings to uppercase' do
|
886
|
+
record = MoneyRecord.new(price: Money.new(100, 'usd'))
|
887
|
+
expect(record.price.currency.to_s).to eq('USD')
|
888
|
+
end
|
889
|
+
|
890
|
+
it 'freezes currency strings for performance' do
|
891
|
+
class MoneyWithFrozenCurrency < ActiveRecord::Base
|
892
|
+
self.table_name = 'money_records'
|
893
|
+
money_column :price, currency: 'USD'
|
894
|
+
end
|
895
|
+
|
896
|
+
expect(MoneyWithFrozenCurrency.money_column_options['price'][:currency]).to be_frozen
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
describe 'error messages' do
|
901
|
+
it 'provides clear error for missing currency when default_currency is nil' do
|
902
|
+
configure(default_currency: nil) do
|
903
|
+
record = MoneyRecord.create!(price: 100, currency: nil)
|
904
|
+
expect { record.reload.price }.to raise_error(ArgumentError, 'missing currency')
|
905
|
+
end
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
describe 'money column with different column names' do
|
910
|
+
class MoneyWithCustomColumns < ActiveRecord::Base
|
911
|
+
self.table_name = 'money_records'
|
912
|
+
money_column :price, currency_column: :devise
|
913
|
+
money_column :prix, currency_column: 'currency'
|
914
|
+
end
|
915
|
+
|
916
|
+
it 'supports both string and symbol currency column names' do
|
917
|
+
record = MoneyWithCustomColumns.new(
|
918
|
+
price: Money.new(100, 'EUR'),
|
919
|
+
devise: 'EUR',
|
920
|
+
prix: Money.new(200, 'USD'),
|
921
|
+
currency: 'USD'
|
922
|
+
)
|
923
|
+
|
924
|
+
expect(record.price.currency.to_s).to eq('EUR')
|
925
|
+
expect(record.prix.currency.to_s).to eq('USD')
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
describe 'money column array syntax' do
|
930
|
+
class MoneyWithArrayColumns < ActiveRecord::Base
|
931
|
+
self.table_name = 'money_records'
|
932
|
+
money_column [:price, :prix], currency_column: 'currency'
|
933
|
+
end
|
934
|
+
|
935
|
+
it 'supports defining multiple columns at once' do
|
936
|
+
record = MoneyWithArrayColumns.new(
|
937
|
+
price: Money.new(100, 'USD'),
|
938
|
+
prix: Money.new(200, 'USD'),
|
939
|
+
currency: 'USD'
|
940
|
+
)
|
941
|
+
|
942
|
+
expect(record.price).to eq(Money.new(100, 'USD'))
|
943
|
+
expect(record.prix).to eq(Money.new(200, 'USD'))
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
947
|
+
describe 'ActiveRecord scopes' do
|
948
|
+
it 'works with ActiveRecord scopes' do
|
949
|
+
MoneyRecord.delete_all
|
950
|
+
cheap = MoneyRecord.create!(price: Money.new(10, 'USD'))
|
951
|
+
expensive = MoneyRecord.create!(price: Money.new(100, 'USD'))
|
952
|
+
|
953
|
+
scope = MoneyRecord.where('price < ?', 50)
|
954
|
+
expect(scope.to_a).to eq([cheap])
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
describe 'JSON serialization' do
|
959
|
+
it 'includes money values in as_json' do
|
960
|
+
record = MoneyRecord.new(price: Money.new(100, 'USD'))
|
961
|
+
json = record.as_json
|
962
|
+
# Money columns are serialized as a hash with symbol keys
|
963
|
+
expect(json['price']).to eq({ currency: 'USD', value: '100.00' })
|
964
|
+
expect(json['currency']).to eq('USD')
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
describe 'update_columns behavior' do
|
969
|
+
it 'bypasses money column methods when using update_columns' do
|
970
|
+
record = MoneyRecord.create!(price: Money.new(100, 'USD'))
|
971
|
+
record.update_columns(price: 200)
|
972
|
+
record.reload
|
973
|
+
expect(record.price.value).to eq(200)
|
974
|
+
expect(record.price.currency.to_s).to eq('USD')
|
975
|
+
end
|
976
|
+
end
|
430
977
|
end
|