shopify-money 2.0.0 → 2.2.1

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.
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