shopify-money 0.14.0 → 0.14.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -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 +3 -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 +3 -2
  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 +1 -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,6 @@
1
+ # frozen_string_literal: true
2
+ require 'delegate'
3
+
1
4
  class Money
2
5
  class Allocator < SimpleDelegator
3
6
  def initialize(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)
@@ -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.0"
3
+ VERSION = "0.14.5"
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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.describe "Allocator" do
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.shared_examples_for "an object supporting to_money" do
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.describe Money::Currency::Loader do
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.describe "Currency" do