shopify-money 3.1.2 → 3.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03fea29a7733cd7ac14493fff381a197b42a16de9369c8a15b0bba9905f7a8a0
4
- data.tar.gz: '025213910adf2f5090dfb7afaa85f715f05515094e15f14c25b5805eaef569eb'
3
+ metadata.gz: c927d4e18e7362a5ad3ddfb642785343d4f504f804b61e0bdac87f410a323c6e
4
+ data.tar.gz: 6eaa57e295c85d1d94e271a8cda1a7fa0f702198878e6087b25b17ea651bf38a
5
5
  SHA512:
6
- metadata.gz: dad64b959edcde085fc92904dd442babdc33122c7b2c30b91e204e597fd38e547ccf62c3e6881d78d1a2b2005581d27dcad1177b1d64ae066d2bfbafabe89cb5
7
- data.tar.gz: 2047c647afe7e4a9b373997184bde9c1a84e47e410571531266fc60075a99da1a65ec4de685c6e79fd852e7de61af427a40440e317ad8a13014a8cc4ccd4f372
6
+ metadata.gz: 76a3752201eec5bceba4252233a754114de80c3e4e223af5fb2ee9f52b86c280436d55b52457b0d972451392b394aaca40c1240d47535e6c9b24d59f0f0e6dbb
7
+ data.tar.gz: e4ec1cd96dd48a5ca060cf773843f834c97a8be50eb0d0b55e390556966d439d5aab597a2af9ab139a7008a714fb7c9f20168dac081436b2fb5091e1be602a7d
@@ -1,5 +1,9 @@
1
1
  version: 2
2
2
  updates:
3
+ - package-ecosystem: github-actions
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
3
7
  - package-ecosystem: bundler
4
8
  directory: "/"
5
9
  schedule:
@@ -13,12 +13,12 @@ jobs:
13
13
 
14
14
  name: Ruby ${{ matrix.ruby }}
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17
17
  - name: Set up Ruby ${{ matrix.ruby }}
18
- uses: ruby/setup-ruby@v1
18
+ uses: ruby/setup-ruby@e34163cd15f4bb403dcd72d98e295997e6a55798 # v1.238.0
19
19
  with:
20
20
  ruby-version: ${{ matrix.ruby }}
21
- - uses: actions/cache@v4
21
+ - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
22
22
  with:
23
23
  path: vendor/bundle
24
24
  key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify-money (3.1.2)
4
+ shopify-money (3.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/lib/money/config.rb CHANGED
@@ -2,7 +2,62 @@
2
2
 
3
3
  class Money
4
4
  class Config
5
- attr_accessor :default_currency, :legacy_json_format, :legacy_deprecations, :experimental_crypto_currencies
5
+ CONFIG_THREAD = :shopify_money__configs
6
+
7
+ class << self
8
+ def global
9
+ @config ||= new
10
+ end
11
+
12
+ def current
13
+ thread_local_config[Fiber.current.object_id] ||= global.dup
14
+ end
15
+
16
+ def current=(config)
17
+ thread_local_config[Fiber.current.object_id] = config
18
+ end
19
+
20
+ def configure_current(**configs, &block)
21
+ old_config = current.dup
22
+ current.tap do |config|
23
+ configs.each do |k, v|
24
+ config.public_send("#{k}=", v)
25
+ end
26
+ end
27
+ yield
28
+ ensure
29
+ self.current = old_config
30
+ end
31
+
32
+ def reset_current
33
+ thread_local_config.delete(Fiber.current.object_id)
34
+ Thread.current[CONFIG_THREAD] = nil if thread_local_config.empty?
35
+ end
36
+
37
+ private
38
+
39
+ def thread_local_config
40
+ Thread.current[CONFIG_THREAD] ||= {}
41
+ end
42
+ end
43
+
44
+ attr_accessor :legacy_json_format, :legacy_deprecations, :experimental_crypto_currencies
45
+
46
+ attr_reader :default_currency
47
+ alias_method :currency, :default_currency
48
+
49
+ def default_currency=(value)
50
+ @default_currency =
51
+ case value
52
+ when String
53
+ Currency.find!(value)
54
+ when Money::Currency, Money::NullCurrency, nil
55
+ value
56
+ else
57
+ raise ArgumentError, "Invalid currency"
58
+ end
59
+ end
60
+ alias_method :currency=, :default_currency=
6
61
 
7
62
  def legacy_default_currency!
8
63
  @default_currency ||= Money::NULL_CURRENCY
@@ -26,13 +81,5 @@ class Money
26
81
  @legacy_deprecations = false
27
82
  @experimental_crypto_currencies = false
28
83
  end
29
-
30
- def without_legacy_deprecations(&block)
31
- old_legacy_deprecations = @legacy_deprecations
32
- @legacy_deprecations = false
33
- yield
34
- ensure
35
- @legacy_deprecations = old_legacy_deprecations
36
- end
37
84
  end
38
85
  end
@@ -17,7 +17,7 @@ class String
17
17
  def to_money(currency = nil)
18
18
  currency = Money::Helpers.value_to_currency(currency)
19
19
 
20
- unless Money.config.legacy_deprecations
20
+ unless Money::Config.current.legacy_deprecations
21
21
  return Money.new(self, currency)
22
22
  end
23
23
 
@@ -30,6 +30,10 @@ class Money
30
30
  def crypto_currencies
31
31
  @@crypto_currencies ||= Loader.load_crypto_currencies
32
32
  end
33
+
34
+ def reset_loaded_currencies
35
+ @@loaded_currencies = {}
36
+ end
33
37
  end
34
38
 
35
39
  attr_reader :iso_code,
@@ -45,7 +49,10 @@ class Money
45
49
 
46
50
  def initialize(currency_iso)
47
51
  data = self.class.currencies[currency_iso]
48
- data = self.class.crypto_currencies[currency_iso] if data.nil? && Money.config.experimental_crypto_currencies
52
+ if data.nil? && Money::Config.current.experimental_crypto_currencies
53
+ data = self.class.crypto_currencies[currency_iso]
54
+ end
55
+
49
56
  raise UnknownCurrency, "Invalid iso4217 currency '#{currency_iso}'" unless data
50
57
  @symbol = data['symbol']
51
58
  @disambiguate_symbol = data['disambiguate_symbol'] || data['symbol']
data/lib/money/helpers.rb CHANGED
@@ -44,7 +44,7 @@ class Money
44
44
  when Money::Currency, Money::NullCurrency
45
45
  currency
46
46
  when nil, ''
47
- default = Money.current_currency || Money.default_currency
47
+ default = Money::Config.current.currency
48
48
  raise(Money::Currency::UnknownCurrency, 'missing currency') if default.nil? || default == ''
49
49
  value_to_currency(default)
50
50
  when 'xxx', 'XXX'
@@ -53,7 +53,7 @@ class Money
53
53
  begin
54
54
  Currency.find!(currency)
55
55
  rescue Money::Currency::UnknownCurrency => error
56
- if Money.config.legacy_deprecations
56
+ if Money::Config.current.legacy_deprecations
57
57
  Money.deprecate(error.message)
58
58
  Money::NULL_CURRENCY
59
59
  else
data/lib/money/money.rb CHANGED
@@ -39,19 +39,37 @@ class Money
39
39
 
40
40
  class << self
41
41
  extend Forwardable
42
- def_delegators :config, :default_currency, :default_currency=, :without_legacy_deprecations
42
+ def_delegators :'Money::Config.global', :default_currency, :default_currency=
43
+
44
+ def without_legacy_deprecations(&block)
45
+ with_config(legacy_deprecations: false, &block)
46
+ end
47
+
48
+ def with_config(**configs, &block)
49
+ Money::Config.configure_current(**configs, &block)
50
+ end
43
51
 
44
52
  def config
45
- Thread.current[:shopify_money__config] ||= @config.dup
53
+ Money::Config.global
46
54
  end
47
55
 
48
- def config=(config)
49
- Thread.current[:shopify_money__config] = config
56
+ def configure(&block)
57
+ Money::Config.global.tap(&block)
50
58
  end
51
59
 
52
- def configure
53
- @config ||= Config.new
54
- yield(@config) if block_given?
60
+ def current_currency
61
+ Money::Config.current.currency
62
+ end
63
+
64
+ def current_currency=(value)
65
+ Money::Config.current.currency = value
66
+ end
67
+
68
+ def with_currency(currency, &block)
69
+ if currency.nil?
70
+ currency = current_currency
71
+ end
72
+ with_config(currency: currency, &block)
55
73
  end
56
74
 
57
75
  def new(value = 0, currency = nil)
@@ -101,26 +119,6 @@ class Money
101
119
  end
102
120
  end
103
121
 
104
- def current_currency
105
- Thread.current[:money_currency]
106
- end
107
-
108
- def current_currency=(currency)
109
- Thread.current[:money_currency] = currency
110
- end
111
-
112
- # Set Money.default_currency inside the supplied block, resets it to
113
- # the previous value when done to prevent leaking state. Similar to
114
- # I18n.with_locale and ActiveSupport's Time.use_zone. This won't affect
115
- # instances being created with explicitly set currency.
116
- def with_currency(new_currency)
117
- old_currency = Money.current_currency
118
- Money.current_currency = new_currency
119
- yield
120
- ensure
121
- Money.current_currency = old_currency
122
- end
123
-
124
122
  private
125
123
 
126
124
  def new_from_money(amount, currency)
@@ -137,7 +135,7 @@ class Money
137
135
  msg = "Money.new(Money.new(amount, #{amount.currency}), #{currency}) " \
138
136
  "is changing the currency of an existing money object"
139
137
 
140
- if Money.config.legacy_deprecations
138
+ if Money::Config.current.legacy_deprecations
141
139
  Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
142
140
  Money.new(amount.value, currency)
143
141
  else
@@ -145,7 +143,6 @@ class Money
145
143
  end
146
144
  end
147
145
  end
148
- configure
149
146
 
150
147
  def initialize(value, currency)
151
148
  raise ArgumentError if value.nan?
@@ -286,7 +283,7 @@ class Money
286
283
  alias_method :to_formatted_s, :to_fs
287
284
 
288
285
  def to_json(options = nil)
289
- if (options.is_a?(Hash) && options[:legacy_format]) || Money.config.legacy_json_format
286
+ if (options.is_a?(Hash) && options[:legacy_format]) || Money::Config.current.legacy_json_format
290
287
  to_s
291
288
  else
292
289
  as_json(options).to_json
@@ -294,7 +291,7 @@ class Money
294
291
  end
295
292
 
296
293
  def as_json(options = nil)
297
- if (options.is_a?(Hash) && options[:legacy_format]) || Money.config.legacy_json_format
294
+ if (options.is_a?(Hash) && options[:legacy_format]) || Money::Config.current.legacy_json_format
298
295
  to_s
299
296
  else
300
297
  { value: to_s(:amount), currency: currency.to_s }
@@ -407,7 +404,7 @@ class Money
407
404
  def ensure_compatible_currency(other_currency, msg)
408
405
  return if currency.compatible?(other_currency)
409
406
 
410
- if Money.config.legacy_deprecations
407
+ if Money::Config.current.legacy_deprecations
411
408
  Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
412
409
  else
413
410
  raise Money::IncompatibleCurrencyError, msg
data/lib/money/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Money
4
- VERSION = "3.1.2"
4
+ VERSION = "3.2.0"
5
5
  end
@@ -60,7 +60,7 @@ module MoneyColumn
60
60
  currency = options[:currency] || try(options[:currency_column])
61
61
  if currency && !money.currency.compatible?(Money::Helpers.value_to_currency(currency))
62
62
  msg = "[money_column] currency mismatch between #{currency} and #{money.currency} in column #{column}."
63
- if Money.config.legacy_deprecations
63
+ if Money::Config.current.legacy_deprecations
64
64
  Money.deprecate(msg)
65
65
  else
66
66
  raise MoneyColumn::CurrencyReadOnlyError, msg
data/spec/config_spec.rb CHANGED
@@ -3,27 +3,52 @@ require 'spec_helper'
3
3
 
4
4
  RSpec.describe "Money::Config" do
5
5
  describe 'thread safety' do
6
- it 'does not share the same config across threads' do
6
+ it 'does not share the same config across fibers' do
7
7
  configure(legacy_deprecations: false, default_currency: 'USD') do
8
- expect(Money.config.legacy_deprecations).to eq(false)
9
- expect(Money.config.default_currency).to eq('USD')
10
- thread = Thread.new do
11
- Money.config.legacy_deprecations!
12
- Money.default_currency = "EUR"
13
- expect(Money.config.legacy_deprecations).to eq(true)
14
- expect(Money.config.default_currency).to eq("EUR")
8
+ expect(Money::Config.current.legacy_deprecations).to eq(false)
9
+ expect(Money::Config.current.default_currency.to_s).to eq('USD')
10
+
11
+ fiber = Fiber.new do
12
+ Money::Config.current.legacy_deprecations!
13
+ Money::Config.current.default_currency = "EUR"
14
+
15
+ expect(Money::Config.current.legacy_deprecations).to eq(true)
16
+ expect(Money::Config.current.default_currency.to_s).to eq("EUR")
17
+
18
+ :fiber_completed
15
19
  end
16
- thread.join
17
- expect(Money.config.legacy_deprecations).to eq(false)
18
- expect(Money.config.default_currency).to eq('USD')
20
+ # run the fiber
21
+ expect(fiber.resume).to eq(:fiber_completed)
22
+
23
+ # Verify main fiber's config was not affected
24
+ expect(Money::Config.current.legacy_deprecations).to eq(false)
25
+ expect(Money::Config.current.default_currency.to_s).to eq('USD')
19
26
  end
20
27
  end
28
+
29
+ it 'isolates configuration between threads' do
30
+ expect(Money::Config.current.legacy_deprecations).to eq(false)
31
+ expect(Money::Config.current.default_currency).to eq(Money::Currency.find!('CAD'))
32
+
33
+ thread = Thread.new do
34
+ Money::Config.current.legacy_deprecations!
35
+ Money::Config.current.default_currency = "EUR"
36
+
37
+ expect(Money::Config.current.legacy_deprecations).to eq(true)
38
+ expect(Money::Config.current.default_currency).to eq(Money::Currency.find!("EUR"))
39
+ end
40
+
41
+ thread.join
42
+
43
+ expect(Money::Config.current.legacy_deprecations).to eq(false)
44
+ expect(Money::Config.current.default_currency).to eq(Money::Currency.find!('CAD'))
45
+ end
21
46
  end
22
47
 
23
48
  describe 'legacy_deprecations' do
24
49
  it "respects the default currency" do
25
50
  configure(default_currency: 'USD', legacy_deprecations: true) do
26
- expect(Money.default_currency).to eq("USD")
51
+ expect(Money::Config.current.default_currency.to_s).to eq("USD")
27
52
  end
28
53
  end
29
54
 
@@ -33,7 +58,7 @@ RSpec.describe "Money::Config" do
33
58
 
34
59
  it 'legacy_deprecations returns true when opting in to v1' do
35
60
  configure(legacy_deprecations: true) do
36
- expect(Money.config.legacy_deprecations).to eq(true)
61
+ expect(Money::Config.current.legacy_deprecations).to eq(true)
37
62
  end
38
63
  end
39
64
 
@@ -45,30 +70,33 @@ RSpec.describe "Money::Config" do
45
70
 
46
71
  it 'legacy_deprecations defaults to NULL_CURRENCY' do
47
72
  configure(legacy_default_currency: true) do
48
- expect(Money.config.default_currency).to eq(Money::NULL_CURRENCY)
73
+ expect(Money::Config.current.default_currency).to eq(Money::NULL_CURRENCY)
49
74
  end
50
75
  end
51
76
  end
52
77
 
53
78
  describe 'default_currency' do
54
79
  it 'defaults to nil' do
55
- configure do
56
- expect(Money.config.default_currency).to eq(nil)
57
- end
80
+ expect(Money::Config.new.default_currency).to eq(nil)
58
81
  end
59
82
 
60
83
  it 'can be set to a new currency' do
61
84
  configure(default_currency: 'USD') do
62
- expect(Money.config.default_currency).to eq('USD')
85
+ expect(Money::Config.current.default_currency.to_s).to eq('USD')
63
86
  end
64
87
  end
88
+
89
+ it 'raises ArgumentError for invalid currency' do
90
+ config = Money::Config.new
91
+ expect { config.default_currency = 123 }.to raise_error(ArgumentError, "Invalid currency")
92
+ end
65
93
  end
66
-
94
+
67
95
  describe 'experimental_crypto_currencies' do
68
96
  it 'defaults to false' do
69
97
  expect(Money::Config.new.experimental_crypto_currencies).to eq(false)
70
98
  end
71
-
99
+
72
100
  it 'can be set to true' do
73
101
  config = Money::Config.new
74
102
  config.experimental_crypto_currencies = true
data/spec/money_spec.rb CHANGED
@@ -20,6 +20,15 @@ RSpec.describe "Money" do
20
20
  end
21
21
  end
22
22
 
23
+ it ".configure the config" do
24
+ config = Money::Config.new
25
+ allow(Money::Config).to receive(:global).and_return(config)
26
+
27
+ expect {
28
+ Money.configure { |c| c.default_currency = "USD" }
29
+ }.to change { config.default_currency }.from(nil).to(Money::Currency.find!("USD"))
30
+ end
31
+
23
32
  it ".zero has no currency" do
24
33
  expect(Money.new(0, Money::NULL_CURRENCY).currency).to be_a(Money::NullCurrency)
25
34
  end
@@ -1095,7 +1104,7 @@ RSpec.describe "Money" do
1095
1104
  end
1096
1105
  end
1097
1106
 
1098
- describe '#use_currency' do
1107
+ describe '.with_currency' do
1099
1108
  it "allows setting the implicit default currency for a block scope" do
1100
1109
  money = nil
1101
1110
  Money.with_currency('CAD') do
@@ -1114,6 +1123,15 @@ RSpec.describe "Money" do
1114
1123
  expect(money.currency.iso_code).to eq('USD')
1115
1124
  end
1116
1125
 
1126
+ it "accepts nil as currency" do
1127
+ money = nil
1128
+ Money.with_currency(nil) do
1129
+ money = Money.new(1.00)
1130
+ end
1131
+ # uses the default currency
1132
+ expect(money.currency.iso_code).to eq('CAD')
1133
+ end
1134
+
1117
1135
  context "with .default_currency set" do
1118
1136
  around(:each) { |test| configure(default_currency: Money::Currency.new('EUR')) { test.run }}
1119
1137
 
@@ -1159,4 +1177,12 @@ RSpec.describe "Money" do
1159
1177
  expect(money.currency.iso_code).to eq('EUR')
1160
1178
  end
1161
1179
  end
1180
+
1181
+ describe ".current_currency" do
1182
+ it "gets and sets the current currency via Config.current" do
1183
+ Money.current_currency = "USD"
1184
+ expect(Money.default_currency.iso_code).to eq("CAD")
1185
+ expect(Money.current_currency.iso_code).to eq("USD")
1186
+ end
1187
+ end
1162
1188
  end
data/spec/spec_helper.rb CHANGED
@@ -54,6 +54,8 @@ RSpec.configure do |config|
54
54
  DatabaseCleaner.cleaning do
55
55
  example.run
56
56
  end
57
+ ensure
58
+ Money::Config.reset_current
57
59
  end
58
60
  end
59
61
 
@@ -73,20 +75,18 @@ end
73
75
 
74
76
 
75
77
  def configure(default_currency: nil, legacy_json_format: nil, legacy_deprecations: nil, legacy_default_currency: nil, experimental_crypto_currencies: nil)
76
- old_currencies = Money::Currency.class_variable_get(:@@loaded_currencies) rescue {}
77
- Money::Currency.class_variable_set(:@@loaded_currencies, {})
78
- old_config = Money.config
79
- Money.config = Money::Config.new.tap do |config|
78
+ Money::Config.current = Money::Config.new.tap do |config|
80
79
  config.default_currency = default_currency if default_currency
81
80
  config.legacy_json_format! if legacy_json_format
82
81
  config.legacy_deprecations! if legacy_deprecations
83
82
  config.legacy_default_currency! if legacy_default_currency
84
83
  config.experimental_crypto_currencies! if experimental_crypto_currencies
85
84
  end
85
+ Money::Currency.reset_loaded_currencies if experimental_crypto_currencies == false
86
+
86
87
  yield
87
88
  ensure
88
- Money::Currency.class_variable_set(:@@loaded_currencies, old_currencies)
89
- Money.config = old_config
89
+ Money::Config.reset_current
90
90
  end
91
91
 
92
92
  def yaml_load(yaml)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify-money
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Inc
@@ -203,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
203
  - !ruby/object:Gem::Version
204
204
  version: '0'
205
205
  requirements: []
206
- rubygems_version: 3.6.8
206
+ rubygems_version: 3.6.9
207
207
  specification_version: 4
208
208
  summary: Shopify's money gem
209
209
  test_files: []