shopify-money 2.2.0 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75ba2eb41fa1e50dac837fab61774cdb4a05407b030cb7ba2989b60de27bbfe4
4
- data.tar.gz: e612602f0f07fe052e6a2e53046e7b6b9a05c56775a75c6a3e4d1f7984671a49
3
+ metadata.gz: afe8dd7e74692587c2a65622a5ee6f1703ef9c0af100c0b0940f225399f2391f
4
+ data.tar.gz: e925f921795d0317f06d9d01c88f981560f353a5746e6d293e103a309600f881
5
5
  SHA512:
6
- metadata.gz: ffa8231621bc6d26f446425cab725e4a3fdcef5a5ec1c7bc240f6397c6fd07514863b56a44c89b413c4750f4a20eb8b218500ea2ab5c0120b7c158f5a24b9a46
7
- data.tar.gz: 6b9582714d55a4e1150b01fcb3eada86751f6408b943811570470c88fda04a7e9db8fdc8a1da65ff46bdf465eb641be521ac474fe1ec6dcc952598d6c32594c0
6
+ metadata.gz: 99b9dccc03de12fd78021c9af07b26fe1897395f7762d0b14d0397064cbffc64eeb96e1e504a91591703c1e1d04c681416fbf51f60fff839939915e600a9bfe9
7
+ data.tar.gz: 062a4c38ce1fff9b93df7a33d19323e995ebd3a0b74cae3882f8f3f34654febc19bfb761cdb3092838db738868b475ef7fe84002f9c04c8736d49b7484742f85
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify-money (2.2.0)
4
+ shopify-money (2.2.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![tests](https://github.com/Shopify/money/workflows/tests/badge.svg)](https://github.com/Shopify/money/actions?query=workflow%3Atests+branch%3Amain)
4
4
 
5
5
 
6
- money_column expects a DECIMAL(21,3) database field.
6
+ `money_column` expects a `DECIMAL(21,3)` database field.
7
7
 
8
8
  ### Features
9
9
 
@@ -195,17 +195,11 @@ require:
195
195
 
196
196
  Money/MissingCurrency:
197
197
  Enabled: true
198
- # If your application is currently handling only one currency,
199
- # it can autocorrect this by specifying a default currency.
200
- ReplacementCurrency: CAD
198
+ # ReplacementCurrency: CAD
201
199
 
202
200
  Money/ZeroMoney:
203
201
  Enabled: true
204
- # Same here:
205
202
  # ReplacementCurrency: CAD
206
-
207
- Money/UnsafeToMoney:
208
- Enabled: true
209
203
  ```
210
204
 
211
205
  ## Contributing to money
@@ -218,6 +212,21 @@ Money/UnsafeToMoney:
218
212
  - Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
219
213
  - Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
220
214
 
215
+ ### Releasing
216
+
217
+ To release a new version of the gem, follow these steps:
218
+
219
+ - Audit what has changed since the last version of the gem was released.
220
+ - Determine what the next version number should be, according to [Semantic Versioning](https://semver.org/).
221
+ - Open a PR to update the version accordingly in `lib/money/version`.
222
+ - After getting approval, merge the PR.
223
+ - [**Publish** a release in Github](https://github.com/Shopify/money/releases/new):
224
+ - Target the `main` branch with a tag matching the new version, prefixed with `v` (e.g. `v1.2.3`).
225
+ - Use the "Generate Release Notes" button to help generate the copy for the release. Include **consumer facing changes** in the release notes.
226
+ - Deploy the new version to Rubygems using [ShipIt](https://shipit.shopify.io/shopify/money/production).
227
+ - For more information see [the publish a gem vault page](https://vault.shopify.io/page/Publish-a-new-version-of-an-internal-gem~dhbc57d.md)
228
+ - You are now responsible to [merge the bump PR in core](https://github.com/Shopify/shopify/pulls?q=is%3Aopen+is%3Apr+author%3Aapp%2Fdependabot+shopify-money+)
229
+
221
230
  ## Copyright
222
231
 
223
232
  Copyright (c) 2011 Shopify. See LICENSE.txt for
@@ -14,14 +14,26 @@ end
14
14
  # '100.37'.to_money => #<Money @cents=10037>
15
15
  class String
16
16
  def to_money(currency = nil)
17
- if Money.config.legacy_deprecations
18
- Money::Parser::Fuzzy.parse(self, currency).tap do |money|
19
- message = "`#{self}.to_money` will behave like `Money.new` and raise on the next release. " \
20
- "To parse user input, do so on the browser and use the user's locale."
21
- Money.deprecate(message) if money.value != BigDecimal(self, exception: false)
17
+ currency = Money::Helpers.value_to_currency(currency)
18
+
19
+ unless Money.config.legacy_deprecations
20
+ return Money.new(self, currency)
21
+ end
22
+
23
+ Money::Parser::Fuzzy.parse(self, currency).tap do |money|
24
+ new_value = BigDecimal(self, exception: false)&.round(currency.minor_units)
25
+ old_value = money.value
26
+
27
+ if new_value != old_value
28
+ message = "`\"#{self}\".to_money` will soon behave like `Money.new(\"#{self}\")` and "
29
+ message +=
30
+ if new_value.nil?
31
+ "raise an ArgumentError exception. Use the browser's locale to parse money strings."
32
+ else
33
+ "return #{new_value} instead of #{old_value}."
34
+ end
35
+ Money.deprecate(message)
22
36
  end
23
- else
24
- Money.new(self, currency)
25
37
  end
26
38
  end
27
39
  end
data/lib/money/money.rb CHANGED
@@ -106,9 +106,17 @@ class Money
106
106
  private
107
107
 
108
108
  def new_from_money(amount, currency)
109
- return amount if amount.currency.compatible?(Helpers.value_to_currency(currency))
109
+ currency = Helpers.value_to_currency(currency)
110
110
 
111
- msg = "Money.new is attempting to change currency of an existing money object"
111
+ if amount.no_currency?
112
+ return Money.new(amount.value, currency)
113
+ end
114
+
115
+ if amount.currency.compatible?(currency)
116
+ return amount
117
+ end
118
+
119
+ msg = "Money.new(Money.new(amount, #{amount.currency}), #{currency}) is changing the currency of an existing money object"
112
120
  if Money.config.legacy_deprecations
113
121
  Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
114
122
  return Money.new(amount.value, currency)
@@ -216,14 +224,8 @@ class Money
216
224
  return Money.new(value, new_currency)
217
225
  end
218
226
 
219
- unless currency.compatible?(Helpers.value_to_currency(new_currency))
220
- msg = "to_money is attempting to change currency of an existing money object from #{currency} to #{new_currency}"
221
- if Money.config.legacy_deprecations
222
- Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
223
- else
224
- raise Money::IncompatibleCurrencyError, msg
225
- end
226
- end
227
+ ensure_compatible_currency(Helpers.value_to_currency(new_currency),
228
+ "to_money is attempting to change currency of an existing money object from #{currency} to #{new_currency}")
227
229
 
228
230
  self
229
231
  end
@@ -358,24 +360,33 @@ class Money
358
360
 
359
361
  private
360
362
 
361
- def arithmetic(money_or_numeric)
362
- case money_or_numeric
363
+ def arithmetic(other)
364
+ case other
363
365
  when Money
364
- unless currency.compatible?(money_or_numeric.currency)
365
- msg = "mathematical operation not permitted for Money objects with different currencies #{money_or_numeric.currency} and #{currency}."
366
- if Money.config.legacy_deprecations
367
- Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
368
- else
369
- raise Money::IncompatibleCurrencyError, msg
370
- end
366
+ ensure_compatible_currency(other.currency,
367
+ "mathematical operation not permitted for Money objects with different currencies #{other.currency} and #{currency}.")
368
+ yield(other)
369
+
370
+ when Numeric, String
371
+ yield(Money.new(other, currency))
372
+
373
+ else
374
+ if Money.config.legacy_deprecations && other.respond_to?(:to_money)
375
+ Money.deprecate("#{other.inspect} is being implicitly coerced into a Money object. Call `to_money` on this object to transform it into a money explicitly. An TypeError will raise in the next major release")
376
+ yield(other.to_money(currency))
377
+ else
378
+ raise TypeError, "#{other.class.name} can't be coerced into Money"
371
379
  end
372
- yield(money_or_numeric)
380
+ end
381
+ end
373
382
 
374
- when Numeric
375
- yield(Money.new(money_or_numeric, currency))
383
+ def ensure_compatible_currency(other_currency, msg)
384
+ return if currency.compatible?(other_currency)
376
385
 
386
+ if Money.config.legacy_deprecations
387
+ Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
377
388
  else
378
- raise TypeError, "#{money_or_numeric.class.name} can't be coerced into Money"
389
+ raise Money::IncompatibleCurrencyError, msg
379
390
  end
380
391
  end
381
392
 
data/lib/money/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  class Money
3
- VERSION = "2.2.0"
3
+ VERSION = "2.2.2"
4
4
  end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rubocop/cop/money/missing_currency'
4
- require 'rubocop/cop/money/unsafe_to_money'
5
4
  require 'rubocop/cop/money/zero_money'
@@ -61,6 +61,14 @@ RSpec.describe String do
61
61
  end
62
62
  end
63
63
 
64
+ it "#to_money does not warn when it already behaves like Money.new" do
65
+ configure(legacy_deprecations: true) do
66
+ expect(Money).to receive(:deprecate).never
67
+ expect("71.94999999999999".to_money("USD")).to eq(Money.new("71.95", "USD"))
68
+ expect("0.001".to_money("USD")).to eq(Money.new("0", "USD"))
69
+ end
70
+ end
71
+
64
72
  it "#to_money should behave like Money.new with three decimal places amounts" do
65
73
  expect("29.000".to_money("USD")).to eq(Money.new("29.00", "USD"))
66
74
  end
data/spec/money_spec.rb CHANGED
@@ -41,6 +41,16 @@ RSpec.describe "Money" do
41
41
  expect(Money.new(1, 'CAD').to_money('CAD')).to eq(Money.new(1, 'CAD'))
42
42
  end
43
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
+
44
54
  it "legacy_deprecations #to_money doesn't overwrite the money object's currency" do
45
55
  configure(legacy_deprecations: true) do
46
56
  expect(Money).to receive(:deprecate).with(match(/to_money is attempting to change currency of an existing money object/)).once
@@ -70,6 +80,17 @@ RSpec.describe "Money" do
70
80
 
71
81
  it "can be constructed with a money object" do
72
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')
73
94
  end
74
95
 
75
96
  it "constructor raises when changing currency" do
@@ -78,7 +99,7 @@ RSpec.describe "Money" do
78
99
 
79
100
  it "legacy_deprecations constructor with money used the constructor currency" do
80
101
  configure(legacy_deprecations: true) do
81
- expect(Money).to receive(:deprecate).with(match(/Money.new is attempting to change currency of an existing money object/)).once
102
+ expect(Money).to receive(:deprecate).with(match(/Money.new\(Money.new\(amount, USD\), CAD\) is changing the currency of an existing money object/)).once
82
103
  expect(Money.new(Money.new(1, 'USD'), 'CAD')).to eq(Money.new(1, 'CAD'))
83
104
  end
84
105
  end
@@ -556,13 +577,49 @@ RSpec.describe "Money" do
556
577
  it { expect(cad_10 == usd_10).to(eq(false)) }
557
578
  end
558
579
 
559
- describe('to_money types') do
580
+ describe('coerced types') do
560
581
  it { expect(cad_10 <=> 10.00).to(eq(0)) }
561
582
  it { expect(cad_10 > 10.00).to(eq(false)) }
562
583
  it { expect(cad_10 >= 10.00).to(eq(true)) }
563
584
  it { expect(cad_10 == 10.00).to(eq(false)) }
564
585
  it { expect(cad_10 <= 10.00).to(eq(true)) }
565
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
566
623
  end
567
624
  end
568
625
 
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.2.0
4
+ version: 2.2.2
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-04-18 00:00:00.000000000 Z
11
+ date: 2024-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -143,7 +143,6 @@ files:
143
143
  - lib/money_column/railtie.rb
144
144
  - lib/rubocop/cop/money.rb
145
145
  - lib/rubocop/cop/money/missing_currency.rb
146
- - lib/rubocop/cop/money/unsafe_to_money.rb
147
146
  - lib/rubocop/cop/money/zero_money.rb
148
147
  - lib/shopify-money.rb
149
148
  - money.gemspec
@@ -164,7 +163,6 @@ files:
164
163
  - spec/rails/job_argument_serializer_spec.rb
165
164
  - spec/rails_spec_helper.rb
166
165
  - spec/rubocop/cop/money/missing_currency_spec.rb
167
- - spec/rubocop/cop/money/unsafe_to_money_spec.rb
168
166
  - spec/rubocop/cop/money/zero_money_spec.rb
169
167
  - spec/rubocop_helper.rb
170
168
  - spec/schema.rb
@@ -212,7 +210,6 @@ test_files:
212
210
  - spec/rails/job_argument_serializer_spec.rb
213
211
  - spec/rails_spec_helper.rb
214
212
  - spec/rubocop/cop/money/missing_currency_spec.rb
215
- - spec/rubocop/cop/money/unsafe_to_money_spec.rb
216
213
  - spec/rubocop/cop/money/zero_money_spec.rb
217
214
  - spec/rubocop_helper.rb
218
215
  - spec/schema.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