shopify-money 0.12.0 → 0.14.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -3
- data/Gemfile +2 -1
- data/README.md +20 -1
- data/Rakefile +1 -0
- data/bin/console +1 -0
- data/config/currency_iso.json +1 -1
- data/dev.yml +1 -1
- data/lib/money.rb +4 -0
- data/lib/money/allocator.rb +144 -0
- data/lib/money/core_extensions.rb +1 -0
- data/lib/money/currency.rb +1 -0
- data/lib/money/currency/loader.rb +1 -0
- data/lib/money/deprecations.rb +1 -0
- data/lib/money/errors.rb +1 -0
- data/lib/money/helpers.rb +5 -15
- data/lib/money/money.rb +8 -111
- data/lib/money/null_currency.rb +1 -0
- data/lib/money/version.rb +2 -1
- data/lib/money_accessor.rb +1 -0
- data/lib/money_column.rb +1 -0
- data/lib/money_column/active_record_hooks.rb +2 -1
- data/lib/money_column/active_record_type.rb +1 -0
- data/lib/money_column/railtie.rb +1 -0
- data/lib/rubocop/cop/money.rb +3 -0
- data/lib/rubocop/cop/money/missing_currency.rb +75 -0
- data/lib/shopify-money.rb +2 -0
- data/money.gemspec +7 -3
- data/spec/accounting_money_parser_spec.rb +1 -0
- data/spec/allocator_spec.rb +148 -0
- data/spec/core_extensions_spec.rb +2 -1
- data/spec/currency/loader_spec.rb +1 -0
- data/spec/currency_spec.rb +1 -0
- data/spec/helpers_spec.rb +1 -6
- data/spec/money_accessor_spec.rb +1 -0
- data/spec/money_column_spec.rb +1 -0
- data/spec/money_parser_spec.rb +1 -0
- data/spec/money_spec.rb +13 -96
- data/spec/null_currency_spec.rb +1 -0
- data/spec/rubocop/cop/money/missing_currency_spec.rb +108 -0
- data/spec/rubocop_helper.rb +10 -0
- data/spec/schema.rb +1 -0
- data/spec/spec_helper.rb +1 -5
- metadata +20 -24
data/lib/money/null_currency.rb
CHANGED
data/lib/money/version.rb
CHANGED
data/lib/money_accessor.rb
CHANGED
data/lib/money_column.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module MoneyColumn
|
2
3
|
module ActiveRecordHooks
|
3
4
|
def self.included(base)
|
@@ -57,7 +58,7 @@ module MoneyColumn
|
|
57
58
|
if options[:currency_read_only]
|
58
59
|
currency_source = Money::Helpers.value_to_currency(currency_raw_source)
|
59
60
|
if currency_raw_source && !money.currency.compatible?(currency_source)
|
60
|
-
Money.deprecate("[money_column] currency mismatch between #{currency_source} and #{money.currency}.")
|
61
|
+
Money.deprecate("[money_column] currency mismatch between #{currency_source} and #{money.currency} in column #{column}.")
|
61
62
|
end
|
62
63
|
else
|
63
64
|
self[options[:currency_column]] = money.currency.to_s unless money.no_currency?
|
data/lib/money_column/railtie.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Money
|
6
|
+
class MissingCurrency < Cop
|
7
|
+
# `Money.new()` without a currency argument cannot guarantee correctness:
|
8
|
+
# - no error raised for cross-currency computation (e.g. 5 CAD + 5 USD)
|
9
|
+
# - #subunits returns wrong values for 0 and 3 decimals currencies
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# Money.new(123.45)
|
14
|
+
# Money.new
|
15
|
+
# "1,234.50".to_money
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Money.new(123.45, 'CAD')
|
19
|
+
# "1,234.50".to_money('CAD')
|
20
|
+
#
|
21
|
+
|
22
|
+
def_node_matcher :money_new, <<~PATTERN
|
23
|
+
(send (const nil? :Money) {:new :from_amount :from_cents} $...)
|
24
|
+
PATTERN
|
25
|
+
|
26
|
+
def_node_matcher :to_money_without_currency?, <<~PATTERN
|
27
|
+
(send _ :to_money)
|
28
|
+
PATTERN
|
29
|
+
|
30
|
+
def_node_matcher :to_money_block?, <<~PATTERN
|
31
|
+
(send _ _ (block_pass (sym :to_money)))
|
32
|
+
PATTERN
|
33
|
+
|
34
|
+
def on_send(node)
|
35
|
+
money_new(node) do |_amount, currency_arg|
|
36
|
+
return if currency_arg
|
37
|
+
|
38
|
+
add_offense(node, message: 'Money is missing currency argument')
|
39
|
+
end
|
40
|
+
|
41
|
+
if to_money_block?(node) || to_money_without_currency?(node)
|
42
|
+
add_offense(node, message: 'to_money is missing currency argument')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def autocorrect(node)
|
47
|
+
currency = cop_config['ReplacementCurrency']
|
48
|
+
return unless currency
|
49
|
+
|
50
|
+
receiver, method, _ = *node
|
51
|
+
|
52
|
+
lambda do |corrector|
|
53
|
+
money_new(node) do |amount, currency_arg|
|
54
|
+
return if currency_arg
|
55
|
+
|
56
|
+
corrector.replace(
|
57
|
+
node.loc.expression,
|
58
|
+
"#{receiver.source}.#{method}(#{amount&.source || 0}, '#{currency}')"
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
if to_money_without_currency?(node)
|
63
|
+
corrector.insert_after(node.loc.expression, "('#{currency}')")
|
64
|
+
elsif to_money_block?(node)
|
65
|
+
corrector.replace(
|
66
|
+
node.loc.expression,
|
67
|
+
"#{receiver.source}.#{method} { |x| x.to_money('#{currency}') }"
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/money.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
require_relative "lib/money/version"
|
3
4
|
|
4
5
|
Gem::Specification.new do |s|
|
@@ -12,13 +13,16 @@ Gem::Specification.new do |s|
|
|
12
13
|
s.licenses = "MIT"
|
13
14
|
s.summary = "Shopify's money gem"
|
14
15
|
|
16
|
+
s.metadata['allowed_push_host'] = "https://rubygems.org"
|
17
|
+
|
15
18
|
s.add_development_dependency("bundler", ">= 1.5")
|
16
19
|
s.add_development_dependency("simplecov", ">= 0")
|
17
|
-
s.add_development_dependency("rails", "~>
|
20
|
+
s.add_development_dependency("rails", "~> 6.0")
|
18
21
|
s.add_development_dependency("rspec", "~> 3.2")
|
19
22
|
s.add_development_dependency("database_cleaner", "~> 1.6")
|
20
|
-
s.add_development_dependency("sqlite3", "~> 1.
|
21
|
-
|
23
|
+
s.add_development_dependency("sqlite3", "~> 1.4.2")
|
24
|
+
|
25
|
+
s.required_ruby_version = '>= 2.6'
|
22
26
|
|
23
27
|
s.files = `git ls-files`.split($/)
|
24
28
|
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
RSpec.describe "Allocator" do
|
5
|
+
describe "allocate"do
|
6
|
+
specify "#allocate takes no action when one gets all" do
|
7
|
+
expect(new_allocator(5).allocate([1])).to eq([Money.new(5)])
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "#allocate does not lose pennies" do
|
11
|
+
moneys = new_allocator(0.05).allocate([0.3,0.7])
|
12
|
+
expect(moneys[0]).to eq(Money.new(0.02))
|
13
|
+
expect(moneys[1]).to eq(Money.new(0.03))
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "#allocate does not lose dollars with non-decimal currency" do
|
17
|
+
moneys = new_allocator(5, 'JPY').allocate([0.3,0.7])
|
18
|
+
expect(moneys[0]).to eq(Money.new(2, 'JPY'))
|
19
|
+
expect(moneys[1]).to eq(Money.new(3, 'JPY'))
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "#allocate does not lose dollars with three decimal currency" do
|
23
|
+
moneys = new_allocator(0.005, 'JOD').allocate([0.3,0.7])
|
24
|
+
expect(moneys[0]).to eq(Money.new(0.002, 'JOD'))
|
25
|
+
expect(moneys[1]).to eq(Money.new(0.003, 'JOD'))
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "#allocate does not lose pennies even when given a lossy split" do
|
29
|
+
moneys = new_allocator(1).allocate([0.333,0.333, 0.333])
|
30
|
+
expect(moneys[0].subunits).to eq(34)
|
31
|
+
expect(moneys[1].subunits).to eq(33)
|
32
|
+
expect(moneys[2].subunits).to eq(33)
|
33
|
+
end
|
34
|
+
|
35
|
+
specify "#allocate requires total to be less than 1" do
|
36
|
+
expect { new_allocator(0.05).allocate([0.5,0.6]) }.to raise_error(ArgumentError)
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "#allocate will use rationals if provided" do
|
40
|
+
splits = [128400,20439,14589,14589,25936].map{ |num| Rational(num, 203953) } # sums to > 1 if converted to float
|
41
|
+
expect(new_allocator(2.25).allocate(splits)).to eq([Money.new(1.42), Money.new(0.23), Money.new(0.16), Money.new(0.16), Money.new(0.28)])
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "#allocate will convert rationals with high precision" do
|
45
|
+
ratios = [Rational(1, 1), Rational(0)]
|
46
|
+
expect(new_allocator("858993456.12").allocate(ratios)).to eq([Money.new("858993456.12"), Money.empty])
|
47
|
+
ratios = [Rational(1, 6), Rational(5, 6)]
|
48
|
+
expect(new_allocator("3.00").allocate(ratios)).to eq([Money.new("0.50"), Money.new("2.50")])
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "#allocate doesn't raise with weird negative rational ratios" do
|
52
|
+
rate = Rational(-5, 1201)
|
53
|
+
expect { new_allocator(1).allocate([rate, 1 - rate]) }.not_to raise_error
|
54
|
+
end
|
55
|
+
|
56
|
+
specify "#allocate fills pennies from beginning to end with roundrobin strategy" do
|
57
|
+
moneys = new_allocator(0.05).allocate([0.3,0.7], :roundrobin)
|
58
|
+
expect(moneys[0]).to eq(Money.new(0.02))
|
59
|
+
expect(moneys[1]).to eq(Money.new(0.03))
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "#allocate fills pennies from end to beginning with roundrobin_reverse strategy" do
|
63
|
+
moneys = new_allocator(0.05).allocate([0.3,0.7], :roundrobin_reverse)
|
64
|
+
expect(moneys[0]).to eq(Money.new(0.01))
|
65
|
+
expect(moneys[1]).to eq(Money.new(0.04))
|
66
|
+
end
|
67
|
+
|
68
|
+
specify "#allocate raise ArgumentError when invalid strategy is provided" do
|
69
|
+
expect { new_allocator(0.03).allocate([0.5, 0.5], :bad_strategy_name) }.to raise_error(ArgumentError, "Invalid strategy. Valid options: :roundrobin, :roundrobin_reverse")
|
70
|
+
end
|
71
|
+
|
72
|
+
specify "#allocate does not raise ArgumentError when invalid splits types are provided" do
|
73
|
+
moneys = new_allocator(0.03).allocate([0.5, 0.5], :roundrobin)
|
74
|
+
expect(moneys[0]).to eq(Money.new(0.02))
|
75
|
+
expect(moneys[1]).to eq(Money.new(0.01))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'allocate_max_amounts' do
|
80
|
+
specify "#allocate_max_amounts returns the weighted allocation without exceeding the maxima when there is room for the remainder" do
|
81
|
+
expect(
|
82
|
+
new_allocator(30.75).allocate_max_amounts([Money.new(26), Money.new(4.75)]),
|
83
|
+
).to eq([Money.new(26), Money.new(4.75)])
|
84
|
+
end
|
85
|
+
|
86
|
+
specify "#allocate_max_amounts returns the weighted allocation without exceeding the maxima when there is room for the remainder with currency" do
|
87
|
+
expect(
|
88
|
+
new_allocator(3075, 'JPY').allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')]),
|
89
|
+
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
90
|
+
end
|
91
|
+
|
92
|
+
specify "#allocate_max_amounts legal computation with no currency objects" do
|
93
|
+
expect(
|
94
|
+
new_allocator(3075, 'JPY').allocate_max_amounts([2600, 475]),
|
95
|
+
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
96
|
+
|
97
|
+
expect(
|
98
|
+
new_allocator(3075, Money::NULL_CURRENCY).allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')]),
|
99
|
+
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
100
|
+
end
|
101
|
+
|
102
|
+
specify "#allocate_max_amounts illegal computation across currencies" do
|
103
|
+
expect {
|
104
|
+
new_allocator(3075, 'USD').allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
105
|
+
}.to raise_error(ArgumentError)
|
106
|
+
end
|
107
|
+
|
108
|
+
specify "#allocate_max_amounts drops the remainder when returning the weighted allocation without exceeding the maxima when there is no room for the remainder" do
|
109
|
+
expect(
|
110
|
+
new_allocator(30.75).allocate_max_amounts([Money.new(26), Money.new(4.74)]),
|
111
|
+
).to eq([Money.new(26), Money.new(4.74)])
|
112
|
+
end
|
113
|
+
|
114
|
+
specify "#allocate_max_amounts returns the weighted allocation when there is no remainder" do
|
115
|
+
expect(
|
116
|
+
new_allocator(30).allocate_max_amounts([Money.new(15), Money.new(15)]),
|
117
|
+
).to eq([Money.new(15), Money.new(15)])
|
118
|
+
end
|
119
|
+
|
120
|
+
specify "#allocate_max_amounts allocates the remainder round-robin when the maxima are not reached" do
|
121
|
+
expect(
|
122
|
+
new_allocator(1).allocate_max_amounts([Money.new(33), Money.new(33), Money.new(33)]),
|
123
|
+
).to eq([Money.new(0.34), Money.new(0.33), Money.new(0.33)])
|
124
|
+
end
|
125
|
+
|
126
|
+
specify "#allocate_max_amounts allocates up to the maxima specified" do
|
127
|
+
expect(
|
128
|
+
new_allocator(100).allocate_max_amounts([Money.new(5), Money.new(2)]),
|
129
|
+
).to eq([Money.new(5), Money.new(2)])
|
130
|
+
end
|
131
|
+
|
132
|
+
specify "#allocate_max_amounts supports all-zero maxima" do
|
133
|
+
expect(
|
134
|
+
new_allocator(3).allocate_max_amounts([Money.empty, Money.empty, Money.empty]),
|
135
|
+
).to eq([Money.empty, Money.empty, Money.empty])
|
136
|
+
end
|
137
|
+
|
138
|
+
specify "#allocate_max_amounts allocates the right amount without rounding error" do
|
139
|
+
expect(
|
140
|
+
new_allocator(24.2).allocate_max_amounts([Money.new(46), Money.new(46), Money.new(50), Money.new(50),Money.new(50)]),
|
141
|
+
).to eq([Money.new(4.6), Money.new(4.6), Money.new(5), Money.new(5), Money.new(5)])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def new_allocator(amount, currency = nil)
|
146
|
+
Money::Allocator.new(Money.new(amount, currency))
|
147
|
+
end
|
148
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'spec_helper'
|
2
3
|
|
3
4
|
RSpec.shared_examples_for "an object supporting to_money" do
|
@@ -56,6 +57,6 @@ RSpec.describe BigDecimal do
|
|
56
57
|
it_should_behave_like "an object supporting to_money"
|
57
58
|
|
58
59
|
it "parses a zero BigDecimal to Money.zero" do
|
59
|
-
expect(BigDecimal
|
60
|
+
expect(BigDecimal("-0.000").to_money).to eq(Money.zero)
|
60
61
|
end
|
61
62
|
end
|
data/spec/currency_spec.rb
CHANGED
data/spec/helpers_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'spec_helper'
|
2
3
|
|
3
4
|
RSpec.describe Money::Helpers do
|
@@ -49,12 +50,6 @@ RSpec.describe Money::Helpers do
|
|
49
50
|
expect(subject.value_to_decimal('invalid')).to eq(0)
|
50
51
|
end
|
51
52
|
|
52
|
-
it 'returns the bigdecimal representation of numbers while they are deprecated' do
|
53
|
-
expect(Money).to receive(:deprecate).exactly(2).times
|
54
|
-
expect(subject.value_to_decimal('1.23abc')).to eq(amount)
|
55
|
-
expect(subject.value_to_decimal("1.23\n23")).to eq(amount)
|
56
|
-
end
|
57
|
-
|
58
53
|
it 'raises on invalid object' do
|
59
54
|
expect { subject.value_to_decimal(OpenStruct.new(amount: 1)) }.to raise_error(ArgumentError)
|
60
55
|
end
|
data/spec/money_accessor_spec.rb
CHANGED
data/spec/money_column_spec.rb
CHANGED
data/spec/money_parser_spec.rb
CHANGED
data/spec/money_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'spec_helper'
|
2
3
|
require 'yaml'
|
3
4
|
|
@@ -583,113 +584,29 @@ RSpec.describe "Money" do
|
|
583
584
|
end
|
584
585
|
|
585
586
|
describe "allocation"do
|
586
|
-
specify "#allocate
|
587
|
-
|
587
|
+
specify "#allocate is calculated by Money::Allocator#allocate" do
|
588
|
+
expected = [Money.new(1), [Money.new(1)]]
|
589
|
+
expect_any_instance_of(Money::Allocator).to receive(:allocate).with([0.5, 0.5], :roundrobin).and_return(expected)
|
590
|
+
expect(Money.new(2).allocate([0.5, 0.5])).to eq(expected)
|
588
591
|
end
|
589
592
|
|
590
|
-
specify "#allocate does not lose pennies" do
|
593
|
+
specify "#allocate does not lose pennies (integration test)" do
|
591
594
|
moneys = Money.new(0.05).allocate([0.3,0.7])
|
592
595
|
expect(moneys[0]).to eq(Money.new(0.02))
|
593
596
|
expect(moneys[1]).to eq(Money.new(0.03))
|
594
597
|
end
|
595
598
|
|
596
|
-
specify "#
|
597
|
-
|
598
|
-
|
599
|
-
expect(
|
599
|
+
specify "#allocate_max_amounts is calculated by Money::Allocator#allocate_max_amounts" do
|
600
|
+
expected = [Money.new(1), [Money.new(1)]]
|
601
|
+
expect_any_instance_of(Money::Allocator).to receive(:allocate_max_amounts).and_return(expected)
|
602
|
+
expect(Money.new(2).allocate_max_amounts([0.5, 0.5])).to eq(expected)
|
600
603
|
end
|
601
604
|
|
602
|
-
specify "#
|
603
|
-
moneys = Money.new(0.005, 'JOD').allocate([0.3,0.7])
|
604
|
-
expect(moneys[0]).to eq(Money.new(0.002, 'JOD'))
|
605
|
-
expect(moneys[1]).to eq(Money.new(0.003, 'JOD'))
|
606
|
-
end
|
607
|
-
|
608
|
-
specify "#allocate does not lose pennies even when given a lossy split" do
|
609
|
-
moneys = Money.new(1).allocate([0.333,0.333, 0.333])
|
610
|
-
expect(moneys[0].subunits).to eq(34)
|
611
|
-
expect(moneys[1].subunits).to eq(33)
|
612
|
-
expect(moneys[2].subunits).to eq(33)
|
613
|
-
end
|
614
|
-
|
615
|
-
specify "#allocate requires total to be less than 1" do
|
616
|
-
expect { Money.new(0.05).allocate([0.5,0.6]) }.to raise_error(ArgumentError)
|
617
|
-
end
|
618
|
-
|
619
|
-
specify "#allocate will use rationals if provided" do
|
620
|
-
splits = [128400,20439,14589,14589,25936].map{ |num| Rational(num, 203953) } # sums to > 1 if converted to float
|
621
|
-
expect(Money.new(2.25).allocate(splits)).to eq([Money.new(1.42), Money.new(0.23), Money.new(0.16), Money.new(0.16), Money.new(0.28)])
|
622
|
-
end
|
623
|
-
|
624
|
-
specify "#allocate will convert rationals with high precision" do
|
625
|
-
ratios = [Rational(1, 1), Rational(0)]
|
626
|
-
expect(Money.new("858993456.12").allocate(ratios)).to eq([Money.new("858993456.12"), Money.empty])
|
627
|
-
ratios = [Rational(1, 6), Rational(5, 6)]
|
628
|
-
expect(Money.new("3.00").allocate(ratios)).to eq([Money.new("0.50"), Money.new("2.50")])
|
629
|
-
end
|
630
|
-
|
631
|
-
specify "#allocate doesn't raise with weird negative rational ratios" do
|
632
|
-
rate = Rational(-5, 1201)
|
633
|
-
expect { Money.new(1).allocate([rate, 1 - rate]) }.not_to raise_error
|
634
|
-
end
|
635
|
-
|
636
|
-
specify "#allocate_max_amounts returns the weighted allocation without exceeding the maxima when there is room for the remainder" do
|
605
|
+
specify "#allocate_max_amounts returns the weighted allocation without exceeding the maxima when there is room for the remainder (integration test)" do
|
637
606
|
expect(
|
638
607
|
Money.new(30.75).allocate_max_amounts([Money.new(26), Money.new(4.75)]),
|
639
608
|
).to eq([Money.new(26), Money.new(4.75)])
|
640
609
|
end
|
641
|
-
|
642
|
-
specify "#allocate_max_amounts returns the weighted allocation without exceeding the maxima when there is room for the remainder with currency" do
|
643
|
-
expect(
|
644
|
-
Money.new(3075, 'JPY').allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')]),
|
645
|
-
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
646
|
-
end
|
647
|
-
|
648
|
-
specify "#allocate_max_amounts legal computation with no currency objects" do
|
649
|
-
expect(
|
650
|
-
Money.new(3075, 'JPY').allocate_max_amounts([2600, 475]),
|
651
|
-
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
652
|
-
|
653
|
-
expect(
|
654
|
-
Money.new(3075, Money::NULL_CURRENCY).allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')]),
|
655
|
-
).to eq([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
656
|
-
end
|
657
|
-
|
658
|
-
specify "#allocate_max_amounts illegal computation across currencies" do
|
659
|
-
expect {
|
660
|
-
Money.new(3075, 'USD').allocate_max_amounts([Money.new(2600, 'JPY'), Money.new(475, 'JPY')])
|
661
|
-
}.to raise_error(ArgumentError)
|
662
|
-
end
|
663
|
-
|
664
|
-
specify "#allocate_max_amounts drops the remainder when returning the weighted allocation without exceeding the maxima when there is no room for the remainder" do
|
665
|
-
expect(
|
666
|
-
Money.new(30.75).allocate_max_amounts([Money.new(26), Money.new(4.74)]),
|
667
|
-
).to eq([Money.new(26), Money.new(4.74)])
|
668
|
-
end
|
669
|
-
|
670
|
-
specify "#allocate_max_amounts returns the weighted allocation when there is no remainder" do
|
671
|
-
expect(
|
672
|
-
Money.new(30).allocate_max_amounts([Money.new(15), Money.new(15)]),
|
673
|
-
).to eq([Money.new(15), Money.new(15)])
|
674
|
-
end
|
675
|
-
|
676
|
-
specify "#allocate_max_amounts allocates the remainder round-robin when the maxima are not reached" do
|
677
|
-
expect(
|
678
|
-
Money.new(1).allocate_max_amounts([Money.new(33), Money.new(33), Money.new(33)]),
|
679
|
-
).to eq([Money.new(0.34), Money.new(0.33), Money.new(0.33)])
|
680
|
-
end
|
681
|
-
|
682
|
-
specify "#allocate_max_amounts allocates up to the maxima specified" do
|
683
|
-
expect(
|
684
|
-
Money.new(100).allocate_max_amounts([Money.new(5), Money.new(2)]),
|
685
|
-
).to eq([Money.new(5), Money.new(2)])
|
686
|
-
end
|
687
|
-
|
688
|
-
specify "#allocate_max_amounts supports all-zero maxima" do
|
689
|
-
expect(
|
690
|
-
Money.new(3).allocate_max_amounts([Money.empty, Money.empty, Money.empty]),
|
691
|
-
).to eq([Money.empty, Money.empty, Money.empty])
|
692
|
-
end
|
693
610
|
end
|
694
611
|
|
695
612
|
describe "split" do
|
@@ -828,8 +745,8 @@ RSpec.describe "Money" do
|
|
828
745
|
end
|
829
746
|
|
830
747
|
it "accepts Rational number" do
|
831
|
-
expect(Money.from_amount(Rational("999999999999999999.999")).value).to eql(BigDecimal
|
832
|
-
expect(Money.from_amount(Rational("999999999999999999.99")).value).to eql(BigDecimal
|
748
|
+
expect(Money.from_amount(Rational("999999999999999999.999")).value).to eql(BigDecimal("1000000000000000000", Money::Helpers::MAX_DECIMAL))
|
749
|
+
expect(Money.from_amount(Rational("999999999999999999.99")).value).to eql(BigDecimal("999999999999999999.99", Money::Helpers::MAX_DECIMAL))
|
833
750
|
end
|
834
751
|
|
835
752
|
it "raises ArgumentError with unsupported argument" do
|