shopify-money 0.14.1 → 0.14.6

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -3
  3. data/Gemfile +2 -1
  4. data/README.md +20 -1
  5. data/Rakefile +1 -0
  6. data/bin/console +1 -0
  7. data/config/currency_historic.yml +181 -0
  8. data/config/currency_iso.yml +2630 -0
  9. data/config/currency_non_iso.yml +96 -0
  10. data/dev.yml +1 -1
  11. data/lib/money.rb +3 -0
  12. data/lib/money/allocator.rb +1 -0
  13. data/lib/money/core_extensions.rb +1 -0
  14. data/lib/money/currency.rb +1 -0
  15. data/lib/money/currency/loader.rb +28 -15
  16. data/lib/money/deprecations.rb +1 -0
  17. data/lib/money/errors.rb +1 -0
  18. data/lib/money/helpers.rb +5 -15
  19. data/lib/money/money.rb +17 -5
  20. data/lib/money/null_currency.rb +1 -0
  21. data/lib/money/version.rb +2 -1
  22. data/lib/money_accessor.rb +1 -0
  23. data/lib/money_column.rb +1 -0
  24. data/lib/money_column/active_record_hooks.rb +2 -1
  25. data/lib/money_column/active_record_type.rb +1 -0
  26. data/lib/money_column/railtie.rb +1 -0
  27. data/lib/rubocop/cop/money.rb +3 -0
  28. data/lib/rubocop/cop/money/missing_currency.rb +75 -0
  29. data/lib/shopify-money.rb +2 -0
  30. data/money.gemspec +7 -3
  31. data/spec/accounting_money_parser_spec.rb +1 -0
  32. data/spec/allocator_spec.rb +1 -0
  33. data/spec/core_extensions_spec.rb +1 -0
  34. data/spec/currency/loader_spec.rb +1 -0
  35. data/spec/currency_spec.rb +1 -0
  36. data/spec/helpers_spec.rb +1 -6
  37. data/spec/money_accessor_spec.rb +1 -0
  38. data/spec/money_column_spec.rb +1 -0
  39. data/spec/money_parser_spec.rb +1 -0
  40. data/spec/money_spec.rb +28 -0
  41. data/spec/null_currency_spec.rb +1 -0
  42. data/spec/rubocop/cop/money/missing_currency_spec.rb +108 -0
  43. data/spec/rubocop_helper.rb +10 -0
  44. data/spec/schema.rb +1 -0
  45. data/spec/spec_helper.rb +1 -5
  46. metadata +19 -25
  47. data/config/currency_historic.json +0 -157
  48. data/config/currency_iso.json +0 -2642
  49. data/config/currency_non_iso.json +0 -82
@@ -0,0 +1,96 @@
1
+ ---
2
+ jep:
3
+ priority: 100
4
+ iso_code: JEP
5
+ name: Jersey Pound
6
+ symbol: "£"
7
+ disambiguate_symbol: JEP
8
+ alternate_symbols: []
9
+ subunit: Penny
10
+ subunit_to_unit: 100
11
+ symbol_first: true
12
+ html_entity: "£"
13
+ decimal_mark: "."
14
+ thousands_separator: ","
15
+ iso_numeric: ''
16
+ smallest_denomination: 1
17
+ kid:
18
+ # The Kiribati Dollar is pegged 1:1 to the AUD, but banknotes and coins in both currencies circulate interchangeably.
19
+ # "Although Kiribati retained 1 and 2 cents coins well after Australia demoted theirs,
20
+ # redundancy and devaluation has slowly removed these coins from general circulation."
21
+ # Source: https://en.wikipedia.org/wiki/Kiribati_dollar
22
+ priority: 100
23
+ iso_code: KID
24
+ name: Kiribati Dollar
25
+ symbol: "$"
26
+ disambiguate_symbol: KID
27
+ alternate_symbols: []
28
+ subunit: Cent
29
+ subunit_to_unit: 100
30
+ symbol_first: true
31
+ html_entity: "$"
32
+ decimal_mark: "."
33
+ thousands_separator: ","
34
+ iso_numeric: ''
35
+ smallest_denomination: 5
36
+ ggp:
37
+ priority: 100
38
+ iso_code: GGP
39
+ name: Guernsey Pound
40
+ symbol: "£"
41
+ disambiguate_symbol: GGP
42
+ alternate_symbols: []
43
+ subunit: Penny
44
+ subunit_to_unit: 100
45
+ symbol_first: true
46
+ html_entity: "£"
47
+ decimal_mark: "."
48
+ thousands_separator: ","
49
+ iso_numeric: ''
50
+ smallest_denomination: 1
51
+ imp:
52
+ priority: 100
53
+ iso_code: IMP
54
+ name: Isle of Man Pound
55
+ symbol: "£"
56
+ disambiguate_symbol: IMP
57
+ alternate_symbols:
58
+ - M£
59
+ subunit: Penny
60
+ subunit_to_unit: 100
61
+ symbol_first: true
62
+ html_entity: "£"
63
+ decimal_mark: "."
64
+ thousands_separator: ","
65
+ iso_numeric: ''
66
+ smallest_denomination: 1
67
+ xfu:
68
+ priority: 100
69
+ iso_code: XFU
70
+ name: UIC Franc
71
+ symbol: ''
72
+ disambiguate_symbol: XFU
73
+ alternate_symbols: []
74
+ subunit: ''
75
+ subunit_to_unit: 100
76
+ symbol_first: true
77
+ html_entity: ''
78
+ decimal_mark: "."
79
+ thousands_separator: ","
80
+ iso_numeric: ''
81
+ smallest_denomination: ''
82
+ gbx:
83
+ priority: 100
84
+ iso_code: GBX
85
+ name: British Penny
86
+ symbol: ''
87
+ disambiguate_symbol: GBX
88
+ alternate_symbols: []
89
+ subunit: ''
90
+ subunit_to_unit: 1
91
+ symbol_first: true
92
+ html_entity: ''
93
+ decimal_mark: "."
94
+ thousands_separator: ","
95
+ iso_numeric: ''
96
+ smallest_denomination: 1
data/dev.yml CHANGED
@@ -3,7 +3,7 @@
3
3
  ---
4
4
  name: money
5
5
  up:
6
- - ruby: 2.5.3
6
+ - ruby: 2.6.6
7
7
  - bundler
8
8
  commands:
9
9
  test: bundle exec rspec
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative 'money/money_parser'
2
3
  require_relative 'money/helpers'
3
4
  require_relative 'money/currency'
@@ -10,3 +11,5 @@ require_relative 'money/accounting_money_parser'
10
11
  require_relative 'money/core_extensions'
11
12
  require_relative 'money_accessor'
12
13
  require_relative 'money_column' if defined?(ActiveRecord)
14
+
15
+ require_relative 'rubocop/cop/money' if defined?(RuboCop)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'delegate'
2
3
 
3
4
  class Money
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Allows Writing of 100.to_money for +Numeric+ types
2
3
  # 100.to_money => #<Money @cents=10000>
3
4
  # 100.37.to_money => #<Money @cents=10037>
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require "money/currency/loader"
2
3
 
3
4
  class Money
@@ -1,25 +1,38 @@
1
- require 'json'
1
+ # frozen_string_literal: true
2
+ require 'yaml'
2
3
 
3
4
  class Money
4
5
  class Currency
5
6
  module Loader
6
- extend self
7
+ class << self
8
+ def load_currencies
9
+ currency_data_path = File.expand_path("../../../../config", __FILE__)
7
10
 
8
- CURRENCY_DATA_PATH = File.expand_path("../../../../config", __FILE__)
11
+ currencies = {}
12
+ currencies.merge! YAML.load_file("#{currency_data_path}/currency_historic.yml")
13
+ currencies.merge! YAML.load_file("#{currency_data_path}/currency_non_iso.yml")
14
+ currencies.merge! YAML.load_file("#{currency_data_path}/currency_iso.yml")
15
+ deep_deduplicate!(currencies)
16
+ end
9
17
 
10
- def load_currencies
11
- currencies = {}
12
- currencies.merge! parse_currency_file("currency_historic.json")
13
- currencies.merge! parse_currency_file("currency_non_iso.json")
14
- currencies.merge! parse_currency_file("currency_iso.json")
15
- end
16
-
17
- private
18
+ private
18
19
 
19
- def parse_currency_file(filename)
20
- json = File.read("#{CURRENCY_DATA_PATH}/#{filename}")
21
- json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
22
- JSON.parse(json)
20
+ def deep_deduplicate!(data)
21
+ case data
22
+ when Hash
23
+ return data if data.frozen?
24
+ data.transform_keys! { |k| deep_deduplicate!(k) }
25
+ data.transform_values! { |v| deep_deduplicate!(v) }
26
+ data.freeze
27
+ when Array
28
+ return data if data.frozen?
29
+ data.map! { |d| deep_deduplicate!(d) }.freeze
30
+ when String
31
+ -data
32
+ else
33
+ data.freeze
34
+ end
35
+ end
23
36
  end
24
37
  end
25
38
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  Money.class_eval do
2
3
  ACTIVE_SUPPORT_DEFINED = defined?(ActiveSupport)
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Money
2
3
  class Error < StandardError
3
4
  end
@@ -5,7 +5,6 @@ class Money
5
5
  module Helpers
6
6
  module_function
7
7
 
8
- NUMERIC_REGEX = /\A\s*[\+\-]?(\d+|\d*\.\d+)\s*\z/
9
8
  DECIMAL_ZERO = BigDecimal(0).freeze
10
9
  MAX_DECIMAL = 21
11
10
 
@@ -25,7 +24,11 @@ class Money
25
24
  when Rational
26
25
  BigDecimal(num, MAX_DECIMAL)
27
26
  when String
28
- string_to_decimal(num)
27
+ decimal = BigDecimal(num, exception: false)
28
+ return decimal if decimal
29
+
30
+ Money.deprecate("using Money.new('#{num}') is deprecated and will raise an ArgumentError in the next major release")
31
+ DECIMAL_ZERO
29
32
  else
30
33
  raise ArgumentError, "could not parse as decimal #{num.inspect}"
31
34
  end
@@ -54,18 +57,5 @@ class Money
54
57
  raise ArgumentError, "could not parse as currency #{currency.inspect}"
55
58
  end
56
59
  end
57
-
58
- def string_to_decimal(num)
59
- if num =~ NUMERIC_REGEX
60
- return BigDecimal(num)
61
- end
62
-
63
- Money.deprecate("using Money.new('#{num}') is deprecated and will raise an ArgumentError in the next major release")
64
- begin
65
- BigDecimal(num)
66
- rescue ArgumentError
67
- DECIMAL_ZERO
68
- end
69
- end
70
60
  end
71
61
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'forwardable'
2
3
 
3
4
  class Money
@@ -30,8 +31,8 @@ class Money
30
31
  end
31
32
  alias_method :empty, :zero
32
33
 
33
- def parse(*args)
34
- parser.parse(*args)
34
+ def parse(*args, **kwargs)
35
+ parser.parse(*args, **kwargs)
35
36
  end
36
37
 
37
38
  def from_cents(cents, currency = nil)
@@ -206,11 +207,22 @@ class Money
206
207
  end
207
208
 
208
209
  def to_s(style = nil)
209
- case style
210
+ units = case style
210
211
  when :legacy_dollars
211
- sprintf("%.2f", value)
212
+ 2
212
213
  when :amount, nil
213
- sprintf("%.#{currency.minor_units}f", value)
214
+ currency.minor_units
215
+ else
216
+ raise ArgumentError, "Unexpected style: #{style}"
217
+ end
218
+
219
+ rounded_value = value.round(units)
220
+ if units == 0
221
+ sprintf("%d", rounded_value)
222
+ else
223
+ sign = rounded_value < 0 ? '-' : ''
224
+ rounded_value = rounded_value.abs
225
+ sprintf("%s%d.%0#{units}d", sign, rounded_value.truncate, rounded_value.frac * (10 ** units))
214
226
  end
215
227
  end
216
228
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Money
2
3
  class NullCurrency
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Money
2
- VERSION = "0.14.1"
3
+ VERSION = "0.14.6"
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module MoneyAccessor
2
3
  def self.included(base)
3
4
  base.extend(ClassMethods)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative 'money_column/active_record_hooks'
2
3
  require_relative 'money_column/active_record_type'
3
4
  require_relative 'money_column/railtie'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module MoneyColumn
2
3
  module ActiveRecordHooks
3
4
  def self.included(base)
@@ -57,7 +58,7 @@ module MoneyColumn
57
58
  if options[:currency_read_only]
58
59
  currency_source = Money::Helpers.value_to_currency(currency_raw_source)
59
60
  if currency_raw_source && !money.currency.compatible?(currency_source)
60
- Money.deprecate("[money_column] currency mismatch between #{currency_source} and #{money.currency}.")
61
+ Money.deprecate("[money_column] currency mismatch between #{currency_source} and #{money.currency} in column #{column}.")
61
62
  end
62
63
  else
63
64
  self[options[:currency_column]] = money.currency.to_s unless money.no_currency?
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class MoneyColumn::ActiveRecordType < ActiveRecord::Type::Decimal
2
3
  def serialize(money)
3
4
  return nil unless money
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module MoneyColumn
2
3
  class Railtie < Rails::Railtie
3
4
  ActiveSupport.on_load :active_record do
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop/cop/money/missing_currency'
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Money
6
+ class MissingCurrency < Cop
7
+ # `Money.new()` without a currency argument cannot guarantee correctness:
8
+ # - no error raised for cross-currency computation (e.g. 5 CAD + 5 USD)
9
+ # - #subunits returns wrong values for 0 and 3 decimals currencies
10
+ #
11
+ # @example
12
+ # # bad
13
+ # Money.new(123.45)
14
+ # Money.new
15
+ # "1,234.50".to_money
16
+ #
17
+ # # good
18
+ # Money.new(123.45, 'CAD')
19
+ # "1,234.50".to_money('CAD')
20
+ #
21
+
22
+ def_node_matcher :money_new, <<~PATTERN
23
+ (send (const nil? :Money) {:new :from_amount :from_cents} $...)
24
+ PATTERN
25
+
26
+ def_node_matcher :to_money_without_currency?, <<~PATTERN
27
+ (send _ :to_money)
28
+ PATTERN
29
+
30
+ def_node_matcher :to_money_block?, <<~PATTERN
31
+ (send _ _ (block_pass (sym :to_money)))
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ money_new(node) do |_amount, currency_arg|
36
+ return if currency_arg
37
+
38
+ add_offense(node, message: 'Money is missing currency argument')
39
+ end
40
+
41
+ if to_money_block?(node) || to_money_without_currency?(node)
42
+ add_offense(node, message: 'to_money is missing currency argument')
43
+ end
44
+ end
45
+
46
+ def autocorrect(node)
47
+ currency = cop_config['ReplacementCurrency']
48
+ return unless currency
49
+
50
+ receiver, method, _ = *node
51
+
52
+ lambda do |corrector|
53
+ money_new(node) do |amount, currency_arg|
54
+ return if currency_arg
55
+
56
+ corrector.replace(
57
+ node.loc.expression,
58
+ "#{receiver.source}.#{method}(#{amount&.source || 0}, '#{currency}')"
59
+ )
60
+ end
61
+
62
+ if to_money_without_currency?(node)
63
+ corrector.insert_after(node.loc.expression, "('#{currency}')")
64
+ elsif to_money_block?(node)
65
+ corrector.replace(
66
+ node.loc.expression,
67
+ "#{receiver.source}.#{method} { |x| x.to_money('#{currency}') }"
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'money'
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  require_relative "lib/money/version"
3
4
 
4
5
  Gem::Specification.new do |s|
@@ -12,13 +13,16 @@ Gem::Specification.new do |s|
12
13
  s.licenses = "MIT"
13
14
  s.summary = "Shopify's money gem"
14
15
 
16
+ s.metadata['allowed_push_host'] = "https://rubygems.org"
17
+
15
18
  s.add_development_dependency("bundler", ">= 1.5")
16
19
  s.add_development_dependency("simplecov", ">= 0")
17
- s.add_development_dependency("rails", "~> 5.0")
20
+ s.add_development_dependency("rails", "~> 6.0")
18
21
  s.add_development_dependency("rspec", "~> 3.2")
19
22
  s.add_development_dependency("database_cleaner", "~> 1.6")
20
- s.add_development_dependency("sqlite3", "~> 1.3.6")
21
- s.add_development_dependency("bigdecimal", "~> 1.4.4")
23
+ s.add_development_dependency("sqlite3", "~> 1.4.2")
24
+
25
+ s.required_ruby_version = '>= 2.6'
22
26
 
23
27
  s.files = `git ls-files`.split($/)
24
28
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.describe AccountingMoneyParser do