shopify-money 2.0.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/spec/money_spec.rb CHANGED
@@ -30,15 +30,30 @@ RSpec.describe "Money" do
30
30
 
31
31
  it "returns itself with to_money" do
32
32
  expect(money.to_money).to eq(money)
33
+ expect(amount_money.to_money).to eq(amount_money)
33
34
  end
34
35
 
35
36
  it "#to_money uses the provided currency when it doesn't already have one" do
36
37
  expect(Money.new(1).to_money('CAD')).to eq(Money.new(1, 'CAD'))
37
38
  end
38
39
 
40
+ it "#to_money works with money objects of the same currency" do
41
+ expect(Money.new(1, 'CAD').to_money('CAD')).to eq(Money.new(1, 'CAD'))
42
+ end
43
+
44
+ it "#to_money works with money objects that doesn't have a currency" do
45
+ money = Money.new(1, Money::NULL_CURRENCY).to_money('USD')
46
+ expect(money.value).to eq(1)
47
+ expect(money.currency.to_s).to eq('USD')
48
+
49
+ money = Money.new(1, 'USD').to_money(Money::NULL_CURRENCY)
50
+ expect(money.value).to eq(1)
51
+ expect(money.currency.to_s).to eq('USD')
52
+ end
53
+
39
54
  it "legacy_deprecations #to_money doesn't overwrite the money object's currency" do
40
55
  configure(legacy_deprecations: true) do
41
- expect(Money).to receive(:deprecate).once
56
+ expect(Money).to receive(:deprecate).with(match(/to_money is attempting to change currency of an existing money object/)).once
42
57
  expect(Money.new(1, 'USD').to_money('CAD')).to eq(Money.new(1, 'USD'))
43
58
  end
44
59
  end
@@ -55,6 +70,40 @@ RSpec.describe "Money" do
55
70
  expect(Money.new('')).to eq(Money.new(0))
56
71
  end
57
72
 
73
+ it "can be constructed with a string" do
74
+ expect(Money.new('1')).to eq(Money.new(1))
75
+ end
76
+
77
+ it "can be constructed with a numeric" do
78
+ expect(Money.new(1.00)).to eq(Money.new(1))
79
+ end
80
+
81
+ it "can be constructed with a money object" do
82
+ expect(Money.new(Money.new(1))).to eq(Money.new(1))
83
+ expect(Money.new(Money.new(1, "USD"), "USD")).to eq(Money.new(1, "USD"))
84
+ end
85
+
86
+ it "can be constructed with a money object with a null currency" do
87
+ money = Money.new(Money.new(1, Money::NULL_CURRENCY), 'USD')
88
+ expect(money.value).to eq(1)
89
+ expect(money.currency.to_s).to eq('USD')
90
+
91
+ money = Money.new(Money.new(1, 'USD'), Money::NULL_CURRENCY)
92
+ expect(money.value).to eq(1)
93
+ expect(money.currency.to_s).to eq('USD')
94
+ end
95
+
96
+ it "constructor raises when changing currency" do
97
+ expect { Money.new(Money.new(1, 'USD'), 'CAD') }.to raise_error(Money::IncompatibleCurrencyError)
98
+ end
99
+
100
+ it "legacy_deprecations constructor with money used the constructor currency" do
101
+ configure(legacy_deprecations: true) do
102
+ expect(Money).to receive(:deprecate).with(match(/Money.new is attempting to change currency of an existing money object/)).once
103
+ expect(Money.new(Money.new(1, 'USD'), 'CAD')).to eq(Money.new(1, 'CAD'))
104
+ end
105
+ end
106
+
58
107
  it "legacy_deprecations defaults to 0 when constructed with an invalid string" do
59
108
  configure(legacy_deprecations: true) do
60
109
  expect(Money).to receive(:deprecate).once
@@ -192,7 +241,7 @@ RSpec.describe "Money" do
192
241
 
193
242
  it "logs a deprecation warning when adding across currencies" do
194
243
  configure(legacy_deprecations: true) do
195
- expect(Money).to receive(:deprecate)
244
+ expect(Money).to receive(:deprecate).with(match(/mathematical operation not permitted for Money objects with different currencies/))
196
245
  expect(Money.new(10, 'USD') - Money.new(1, 'JPY')).to eq(Money.new(9, 'USD'))
197
246
  end
198
247
  end
@@ -528,13 +577,49 @@ RSpec.describe "Money" do
528
577
  it { expect(cad_10 == usd_10).to(eq(false)) }
529
578
  end
530
579
 
531
- describe('to_money types') do
580
+ describe('coerced types') do
532
581
  it { expect(cad_10 <=> 10.00).to(eq(0)) }
533
582
  it { expect(cad_10 > 10.00).to(eq(false)) }
534
583
  it { expect(cad_10 >= 10.00).to(eq(true)) }
535
584
  it { expect(cad_10 == 10.00).to(eq(false)) }
536
585
  it { expect(cad_10 <= 10.00).to(eq(true)) }
537
586
  it { expect(cad_10 < 10.00).to(eq(false)) }
587
+ it { expect(cad_10 <=>'10.00').to(eq(0)) }
588
+ it { expect(cad_10 > '10.00').to(eq(false)) }
589
+ it { expect(cad_10 >= '10.00').to(eq(true)) }
590
+ it { expect(cad_10 == '10.00').to(eq(false)) }
591
+ it { expect(cad_10 <= '10.00').to(eq(true)) }
592
+ it { expect(cad_10 < '10.00').to(eq(false)) }
593
+ end
594
+
595
+ describe('to_money coerced types') do
596
+ let(:coercible_object) do
597
+ double("coercible_object").tap do |mock|
598
+ allow(mock).to receive(:to_money).with(any_args) { |currency| Money.new(10, currency) }
599
+ end
600
+ end
601
+
602
+ it { expect { cad_10 <=> coercible_object }.to(raise_error(TypeError)) }
603
+ it { expect { cad_10 > coercible_object }.to(raise_error(TypeError)) }
604
+ it { expect { cad_10 >= coercible_object }.to(raise_error(TypeError)) }
605
+ it { expect { cad_10 <= coercible_object }.to(raise_error(TypeError)) }
606
+ it { expect { cad_10 < coercible_object }.to(raise_error(TypeError)) }
607
+ it { expect { cad_10 + coercible_object }.to(raise_error(TypeError)) }
608
+ it { expect { cad_10 - coercible_object }.to(raise_error(TypeError)) }
609
+
610
+ describe('with legacy_deprecations') do
611
+ around(:each) do |test|
612
+ configure(legacy_deprecations: true) { test.run }
613
+ end
614
+
615
+ it { expect(Money).to(receive(:deprecate).once); expect(cad_10 <=> coercible_object).to(eq(0)) }
616
+ it { expect(Money).to(receive(:deprecate).once); expect(cad_10 > coercible_object).to(eq(false)) }
617
+ it { expect(Money).to(receive(:deprecate).once); expect(cad_10 >= coercible_object).to(eq(true)) }
618
+ it { expect(Money).to(receive(:deprecate).once); expect(cad_10 <= coercible_object).to(eq(true)) }
619
+ it { expect(Money).to(receive(:deprecate).once); expect(cad_10 < coercible_object).to(eq(false)) }
620
+ it { expect(Money).to(receive(:deprecate).once); expect(cad_10 + coercible_object).to(eq(Money.new(20, 'CAD'))) }
621
+ it { expect(Money).to(receive(:deprecate).once); expect(cad_10 - coercible_object).to(eq(Money.new(0, 'CAD'))) }
622
+ end
538
623
  end
539
624
  end
540
625
 
@@ -723,22 +808,32 @@ RSpec.describe "Money" do
723
808
  specify "#split needs at least one party" do
724
809
  expect {Money.new(1).split(0)}.to raise_error(ArgumentError)
725
810
  expect {Money.new(1).split(-1)}.to raise_error(ArgumentError)
811
+ expect {Money.new(1).split(0.1)}.to raise_error(ArgumentError)
812
+ expect(Money.new(1).split(BigDecimal("0.1e1")).to_a).to eq([Money.new(1)])
813
+ end
814
+
815
+ specify "#split can be zipped" do
816
+ expect(Money.new(100).split(3).zip(Money.new(50).split(3)).to_a).to eq([
817
+ [Money.new(33.34), Money.new(16.67)],
818
+ [Money.new(33.33), Money.new(16.67)],
819
+ [Money.new(33.33), Money.new(16.66)],
820
+ ])
726
821
  end
727
822
 
728
823
  specify "#gives 1 cent to both people if we start with 2" do
729
- expect(Money.new(0.02).split(2)).to eq([Money.new(0.01), Money.new(0.01)])
824
+ expect(Money.new(0.02).split(2).to_a).to eq([Money.new(0.01), Money.new(0.01)])
730
825
  end
731
826
 
732
827
  specify "#split may distribute no money to some parties if there isnt enough to go around" do
733
- expect(Money.new(0.02).split(3)).to eq([Money.new(0.01), Money.new(0.01), Money.new(0)])
828
+ expect(Money.new(0.02).split(3).to_a).to eq([Money.new(0.01), Money.new(0.01), Money.new(0)])
734
829
  end
735
830
 
736
831
  specify "#split does not lose pennies" do
737
- expect(Money.new(0.05).split(2)).to eq([Money.new(0.03), Money.new(0.02)])
832
+ expect(Money.new(0.05).split(2).to_a).to eq([Money.new(0.03), Money.new(0.02)])
738
833
  end
739
834
 
740
835
  specify "#split does not lose dollars with non-decimal currencies" do
741
- expect(Money.new(5, 'JPY').split(2)).to eq([Money.new(3, 'JPY'), Money.new(2, 'JPY')])
836
+ expect(Money.new(5, 'JPY').split(2).to_a).to eq([Money.new(3, 'JPY'), Money.new(2, 'JPY')])
742
837
  end
743
838
 
744
839
  specify "#split a dollar" do
@@ -754,6 +849,41 @@ RSpec.describe "Money" do
754
849
  expect(moneys[1].value).to eq(33)
755
850
  expect(moneys[2].value).to eq(33)
756
851
  end
852
+
853
+ specify "#split return respond to #first" do
854
+ expect(Money.new(100).split(3).first).to eq(Money.new(33.34))
855
+ expect(Money.new(100).split(3).first(2)).to eq([Money.new(33.34), Money.new(33.33)])
856
+
857
+ expect(Money.new(100).split(10).first).to eq(Money.new(10))
858
+ expect(Money.new(100).split(10).first(2)).to eq([Money.new(10), Money.new(10)])
859
+ expect(Money.new(20).split(2).first(4)).to eq([Money.new(10), Money.new(10)])
860
+ end
861
+
862
+ specify "#split return respond to #last" do
863
+ expect(Money.new(100).split(3).last).to eq(Money.new(33.33))
864
+ expect(Money.new(100).split(3).last(2)).to eq([Money.new(33.33), Money.new(33.33)])
865
+ expect(Money.new(20).split(2).last(4)).to eq([Money.new(10), Money.new(10)])
866
+ end
867
+
868
+ specify "#split return supports destructuring" do
869
+ first, second = Money.new(100).split(3)
870
+ expect(first).to eq(Money.new(33.34))
871
+ expect(second).to eq(Money.new(33.33))
872
+
873
+ first, *rest = Money.new(100).split(3)
874
+ expect(first).to eq(Money.new(33.34))
875
+ expect(rest).to eq([Money.new(33.33), Money.new(33.33)])
876
+ end
877
+
878
+ specify "#split return can be reversed" do
879
+ list = Money.new(100).split(3)
880
+ expect(list.first).to eq(Money.new(33.34))
881
+ expect(list.last).to eq(Money.new(33.33))
882
+
883
+ list = list.reverse
884
+ expect(list.first).to eq(Money.new(33.33))
885
+ expect(list.last).to eq(Money.new(33.34))
886
+ end
757
887
  end
758
888
 
759
889
  describe "calculate_splits" do
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'yaml'
4
+
5
+ RSpec.describe "Money::Splitter" do
6
+ specify "#split needs at least one party" do
7
+ expect {Money.new(1).split(0)}.to raise_error(ArgumentError)
8
+ expect {Money.new(1).split(-1)}.to raise_error(ArgumentError)
9
+ expect {Money.new(1).split(0.1)}.to raise_error(ArgumentError)
10
+ expect(Money.new(1).split(BigDecimal("0.1e1")).to_a).to eq([Money.new(1)])
11
+ end
12
+
13
+ specify "#split can be zipped" do
14
+ expect(Money.new(100).split(3).zip(Money.new(50).split(3)).to_a).to eq([
15
+ [Money.new(33.34), Money.new(16.67)],
16
+ [Money.new(33.33), Money.new(16.67)],
17
+ [Money.new(33.33), Money.new(16.66)],
18
+ ])
19
+ end
20
+
21
+ specify "#gives 1 cent to both people if we start with 2" do
22
+ expect(Money.new(0.02).split(2).to_a).to eq([Money.new(0.01), Money.new(0.01)])
23
+ end
24
+
25
+ specify "#split may distribute no money to some parties if there isnt enough to go around" do
26
+ expect(Money.new(0.02).split(3).to_a).to eq([Money.new(0.01), Money.new(0.01), Money.new(0)])
27
+ end
28
+
29
+ specify "#split does not lose pennies" do
30
+ expect(Money.new(0.05).split(2).to_a).to eq([Money.new(0.03), Money.new(0.02)])
31
+ end
32
+
33
+ specify "#split does not lose dollars with non-decimal currencies" do
34
+ expect(Money.new(5, 'JPY').split(2).to_a).to eq([Money.new(3, 'JPY'), Money.new(2, 'JPY')])
35
+ end
36
+
37
+ specify "#split a dollar" do
38
+ moneys = Money.new(1).split(3)
39
+ expect(moneys[0].subunits).to eq(34)
40
+ expect(moneys[1].subunits).to eq(33)
41
+ expect(moneys[2].subunits).to eq(33)
42
+ end
43
+
44
+ specify "#split a 100 yen" do
45
+ moneys = Money.new(100, 'JPY').split(3)
46
+ expect(moneys[0].value).to eq(34)
47
+ expect(moneys[1].value).to eq(33)
48
+ expect(moneys[2].value).to eq(33)
49
+ end
50
+
51
+ specify "#split return respond to #first" do
52
+ expect(Money.new(100).split(3).first).to eq(Money.new(33.34))
53
+ expect(Money.new(100).split(3).first(2)).to eq([Money.new(33.34), Money.new(33.33)])
54
+
55
+ expect(Money.new(100).split(10).first).to eq(Money.new(10))
56
+ expect(Money.new(100).split(10).first(2)).to eq([Money.new(10), Money.new(10)])
57
+ expect(Money.new(20).split(2).first(4)).to eq([Money.new(10), Money.new(10)])
58
+ end
59
+
60
+ specify "#split return respond to #last" do
61
+ expect(Money.new(100).split(3).last).to eq(Money.new(33.33))
62
+ expect(Money.new(100).split(3).last(2)).to eq([Money.new(33.33), Money.new(33.33)])
63
+ expect(Money.new(20).split(2).last(4)).to eq([Money.new(10), Money.new(10)])
64
+ end
65
+
66
+ specify "#split return supports destructuring" do
67
+ first, second = Money.new(100).split(3)
68
+ expect(first).to eq(Money.new(33.34))
69
+ expect(second).to eq(Money.new(33.33))
70
+
71
+ first, *rest = Money.new(100).split(3)
72
+ expect(first).to eq(Money.new(33.34))
73
+ expect(rest).to eq([Money.new(33.33), Money.new(33.33)])
74
+ end
75
+
76
+ specify "#split return can be reversed" do
77
+ list = Money.new(100).split(3)
78
+ expect(list.first).to eq(Money.new(33.34))
79
+ expect(list.last).to eq(Money.new(33.33))
80
+
81
+ list = list.reverse
82
+ expect(list.first).to eq(Money.new(33.33))
83
+ expect(list.last).to eq(Money.new(33.34))
84
+ end
85
+
86
+ describe "calculate_splits" do
87
+ specify "#calculate_splits gives 1 cent to both people if we start with 2" do
88
+ actual = Money.new(0.02, 'CAD').calculate_splits(2)
89
+
90
+ expect(actual).to eq({
91
+ Money.new(0.01, 'CAD') => 2,
92
+ })
93
+ end
94
+
95
+ specify "#calculate_splits gives an extra penny to one" do
96
+ actual = Money.new(0.04, 'CAD').calculate_splits(3)
97
+
98
+ expect(actual).to eq({
99
+ Money.new(0.02, 'CAD') => 1,
100
+ Money.new(0.01, 'CAD') => 2,
101
+ })
102
+ end
103
+ end
104
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify-money
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-30 00:00:00.000000000 Z
11
+ date: 2024-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -84,16 +84,16 @@ dependencies:
84
84
  name: sqlite3
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: 1.4.2
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: 1.4.2
96
+ version: '0'
97
97
  description: Manage money in Shopify with a class that wont lose pennies during division!
98
98
  email: gems@shopify.com
99
99
  executables:
@@ -106,7 +106,9 @@ files:
106
106
  - ".github/workflows/tests.yml"
107
107
  - ".gitignore"
108
108
  - ".rspec"
109
+ - ".ruby-version"
109
110
  - Gemfile
111
+ - Gemfile.lock
110
112
  - LICENSE.txt
111
113
  - README.md
112
114
  - Rakefile
@@ -133,6 +135,7 @@ files:
133
135
  - lib/money/parser/simple.rb
134
136
  - lib/money/rails/job_argument_serializer.rb
135
137
  - lib/money/railtie.rb
138
+ - lib/money/splitter.rb
136
139
  - lib/money/version.rb
137
140
  - lib/money_column.rb
138
141
  - lib/money_column/active_record_hooks.rb
@@ -140,7 +143,6 @@ files:
140
143
  - lib/money_column/railtie.rb
141
144
  - lib/rubocop/cop/money.rb
142
145
  - lib/rubocop/cop/money/missing_currency.rb
143
- - lib/rubocop/cop/money/unsafe_to_money.rb
144
146
  - lib/rubocop/cop/money/zero_money.rb
145
147
  - lib/shopify-money.rb
146
148
  - money.gemspec
@@ -161,11 +163,11 @@ files:
161
163
  - spec/rails/job_argument_serializer_spec.rb
162
164
  - spec/rails_spec_helper.rb
163
165
  - spec/rubocop/cop/money/missing_currency_spec.rb
164
- - spec/rubocop/cop/money/unsafe_to_money_spec.rb
165
166
  - spec/rubocop/cop/money/zero_money_spec.rb
166
167
  - spec/rubocop_helper.rb
167
168
  - spec/schema.rb
168
169
  - spec/spec_helper.rb
170
+ - spec/splitter_spec.rb
169
171
  homepage: https://github.com/Shopify/money
170
172
  licenses:
171
173
  - MIT
@@ -186,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
188
  - !ruby/object:Gem::Version
187
189
  version: '0'
188
190
  requirements: []
189
- rubygems_version: 3.5.5
191
+ rubygems_version: 3.5.9
190
192
  signing_key:
191
193
  specification_version: 4
192
194
  summary: Shopify's money gem
@@ -208,8 +210,8 @@ test_files:
208
210
  - spec/rails/job_argument_serializer_spec.rb
209
211
  - spec/rails_spec_helper.rb
210
212
  - spec/rubocop/cop/money/missing_currency_spec.rb
211
- - spec/rubocop/cop/money/unsafe_to_money_spec.rb
212
213
  - spec/rubocop/cop/money/zero_money_spec.rb
213
214
  - spec/rubocop_helper.rb
214
215
  - spec/schema.rb
215
216
  - spec/spec_helper.rb
217
+ - spec/splitter_spec.rb
@@ -1,35 +0,0 @@
1
-
2
- module RuboCop
3
- module Cop
4
- module Money
5
- # Prevents the use of `to_money` because it has inconsistent behaviour.
6
- # Use `Money.new` instead.
7
- #
8
- # @example
9
- # # bad
10
- # "2.000".to_money("USD") #<Money value:2000.00 currency:USD>
11
- #
12
- # # good
13
- # Money.new("2.000", "USD") #<Money value:2.00 currency:USD>
14
- class UnsafeToMoney < Cop
15
- MSG = '`to_money` has inconsistent behaviour. Use `Money.new` instead.'.freeze
16
-
17
- def on_send(node)
18
- return unless node.method?(:to_money)
19
- return if node.receiver.nil? || node.receiver.is_a?(AST::NumericNode)
20
-
21
- add_offense(node, location: :selector)
22
- end
23
-
24
- def autocorrect(node)
25
- lambda do |corrector|
26
- receiver = node.receiver.source
27
- args = node.arguments.map(&:source)
28
- args.prepend(receiver)
29
- corrector.replace(node.loc.expression, "Money.new(#{args.join(', ')})")
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../../rubocop_helper'
4
- require 'rubocop/cop/money/unsafe_to_money'
5
-
6
- RSpec.describe RuboCop::Cop::Money::UnsafeToMoney do
7
- subject(:cop) { described_class.new(config) }
8
-
9
- let(:config) { RuboCop::Config.new }
10
-
11
- context 'with default configuration' do
12
- it 'does not register an offense for literal integer' do
13
- expect_no_offenses(<<~RUBY)
14
- 1.to_money
15
- RUBY
16
- end
17
-
18
- it 'does not register an offense for literal float' do
19
- expect_no_offenses(<<~RUBY)
20
- 1.000.to_money
21
- RUBY
22
- end
23
-
24
- it 'registers an offense and corrects for Money.new without a currency argument' do
25
- expect_offense(<<~RUBY)
26
- '2.000'.to_money
27
- ^^^^^^^^ #{described_class::MSG}
28
- RUBY
29
-
30
- expect_correction(<<~RUBY)
31
- Money.new('2.000')
32
- RUBY
33
- end
34
-
35
- it 'registers an offense and corrects for Money.new with a currency argument' do
36
- expect_offense(<<~RUBY)
37
- '2.000'.to_money('USD')
38
- ^^^^^^^^ #{described_class::MSG}
39
- RUBY
40
-
41
- expect_correction(<<~RUBY)
42
- Money.new('2.000', 'USD')
43
- RUBY
44
- end
45
-
46
- it 'registers an offense and corrects for Money.new with a complex receiver' do
47
- expect_offense(<<~RUBY)
48
- obj.money.to_money('USD')
49
- ^^^^^^^^ #{described_class::MSG}
50
- RUBY
51
-
52
- expect_correction(<<~RUBY)
53
- Money.new(obj.money, 'USD')
54
- RUBY
55
- end
56
-
57
- it 'does not register an offense for receiver-less calls' do
58
- expect_no_offenses(<<~RUBY)
59
- a = to_money
60
- RUBY
61
- end
62
- end
63
- end