shopify-money 2.2.0 → 2.2.1

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: 75140fb176288187177a79f673b9741b69a4ae96e02b314139318d7ff817b8b9
4
+ data.tar.gz: d98030d40029dbb686b83b090ab11e43628a194bd2902ba130f5161e860dc839
5
5
  SHA512:
6
- metadata.gz: ffa8231621bc6d26f446425cab725e4a3fdcef5a5ec1c7bc240f6397c6fd07514863b56a44c89b413c4750f4a20eb8b218500ea2ab5c0120b7c158f5a24b9a46
7
- data.tar.gz: 6b9582714d55a4e1150b01fcb3eada86751f6408b943811570470c88fda04a7e9db8fdc8a1da65ff46bdf465eb641be521ac474fe1ec6dcc952598d6c32594c0
6
+ metadata.gz: 3e140de24fea5b7c90b5044f4b9732e013187a6cad34c7ea6bd9d8119cd118907c7faeb934ed533085389e3e55f7ba0649697e7563ec87380c6fc62ff9a1c935
7
+ data.tar.gz: 5a1d77f26c7886fc1a96edee8a920df4262385fce001209d640c9427e986e9d95d2d9a8232fca17a670758f45a7bc219d728386c080606d1f8bc348cb71ba0a3
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.1)
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
data/lib/money/money.rb CHANGED
@@ -106,7 +106,15 @@ 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
+
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
110
118
 
111
119
  msg = "Money.new is attempting to change currency of an existing money object"
112
120
  if Money.config.legacy_deprecations
@@ -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.1"
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'
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
@@ -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.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-04-18 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
@@ -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