money2 7.0.0.rc1

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.
@@ -0,0 +1,9 @@
1
+ class Money
2
+ class Formatter
3
+ class ToString < self
4
+ def format(*)
5
+ self.class.decimal_str(money).sub('.', separator)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,120 @@
1
+ class Money
2
+ module RatesStore
3
+
4
+ # Class for thread-safe storage of exchange rate pairs.
5
+ # Used by instances of +Money::Bank::VariableExchange+.
6
+ #
7
+ # @example
8
+ # store = Money::RatesStore::Memory.new
9
+ # store.add_rate 'USD', 'CAD', 0.98
10
+ # store.get_rate 'USD', 'CAD' # => 0.98
11
+ # # iterates rates
12
+ # store.each_rate {|iso_from, iso_to, rate| puts "#{from} -> #{to}: #{rate}" }
13
+ class Memory
14
+ INDEX_KEY_SEPARATOR = '_TO_'.freeze
15
+
16
+ # Initializes a new +Money::RatesStore::Memory+ object.
17
+ #
18
+ # @param [Hash] opts Optional store options.
19
+ # @option opts [Boolean] :without_mutex disables the usage of a mutex
20
+ # @param [Hash] rt Optional initial exchange rate data.
21
+ def initialize(opts = {}, rt = {})
22
+ @options, @index = opts, rt
23
+ @mutex = Mutex.new
24
+ @in_transaction = false
25
+ end
26
+
27
+ # Registers a conversion rate and returns it. Uses +Mutex+ to synchronize data access.
28
+ #
29
+ # @param [String] currency_iso_from Currency to exchange from.
30
+ # @param [String] currency_iso_to Currency to exchange to.
31
+ # @param [Numeric] rate Rate to use when exchanging currencies.
32
+ #
33
+ # @return [Numeric]
34
+ #
35
+ # @example
36
+ # store = Money::RatesStore::Memory.new
37
+ # store.add_rate("USD", "CAD", 1.24515)
38
+ # store.add_rate("CAD", "USD", 0.803115)
39
+ def add_rate(currency_iso_from, currency_iso_to, rate)
40
+ transaction { index[rate_key_for(currency_iso_from, currency_iso_to)] = rate }
41
+ end
42
+
43
+ # Retrieve the rate for the given currencies. Uses +Mutex+ to synchronize data access.
44
+ # Delegates to +Money::RatesStore::Memory+
45
+ #
46
+ # @param [String] currency_iso_from Currency to exchange from.
47
+ # @param [String] currency_iso_to Currency to exchange to.
48
+ #
49
+ # @return [Numeric]
50
+ #
51
+ # @example
52
+ # store = Money::RatesStore::Memory.new
53
+ # store.add_rate("USD", "CAD", 1.24515)
54
+ #
55
+ # store.get_rate("USD", "CAD") #=> 1.24515
56
+ def get_rate(currency_iso_from, currency_iso_to)
57
+ transaction { index[rate_key_for(currency_iso_from, currency_iso_to)] }
58
+ end
59
+
60
+ def marshal_dump
61
+ [self.class, index, options]
62
+ end
63
+
64
+
65
+ # Wraps block execution in a thread-safe transaction
66
+ def transaction(&block)
67
+ if @in_transaction || options[:without_mutex]
68
+ block.call self
69
+ else
70
+ @mutex.synchronize do
71
+ @in_transaction = true
72
+ result = block.call
73
+ @in_transaction = false
74
+ result
75
+ end
76
+ end
77
+ end
78
+
79
+ # Iterate over rate tuples (iso_from, iso_to, rate)
80
+ #
81
+ # @yieldparam iso_from [String] Currency ISO string.
82
+ # @yieldparam iso_to [String] Currency ISO string.
83
+ # @yieldparam rate [Numeric] Exchange rate.
84
+ #
85
+ # @return [Enumerator]
86
+ #
87
+ # @example
88
+ # store.each_rate do |iso_from, iso_to, rate|
89
+ # puts [iso_from, iso_to, rate].join
90
+ # end
91
+ def each_rate(&block)
92
+ enum = Enumerator.new do |yielder|
93
+ index.each do |key, rate|
94
+ iso_from, iso_to = key.split(INDEX_KEY_SEPARATOR)
95
+ yielder.yield iso_from, iso_to, rate
96
+ end
97
+ end
98
+
99
+ block_given? ? enum.each(&block) : enum
100
+ end
101
+
102
+ private
103
+
104
+ attr_reader :index, :options
105
+
106
+ # Return the rate hashkey for the given currencies.
107
+ #
108
+ # @param [String] currency_iso_from The currency to exchange from.
109
+ # @param [String] currency_iso_to The currency to exchange to.
110
+ #
111
+ # @return [String]
112
+ #
113
+ # @example
114
+ # rate_key_for("USD", "CAD") #=> "USD_TO_CAD"
115
+ def rate_key_for(currency_iso_from, currency_iso_to)
116
+ [currency_iso_from, currency_iso_to].join(INDEX_KEY_SEPARATOR).upcase
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'sixarm_ruby_unaccent'
3
+ rescue LoadError
4
+ raise 'Money gem doesnt install sixarm_ruby_unaccent by default. ' \
5
+ 'Add it to your gemfile if you use Currency.analyze'
6
+ end
7
+
8
+ # Overwrites unaccent method of sixarm_ruby_unaccent.
9
+ class String
10
+ def unaccent
11
+ accentmap = ACCENTMAP
12
+ accentmap.delete("\u{0142}") # Delete ł symbol from ACCENTMAP used in PLN currency
13
+ accentmap.delete("\u{010D}") # Delete č symbol from ACCENTMAP used in CZK currency
14
+ accentmap.delete("\u{FDFC}") # Delete ﷼ symbol from ACCENTMAP used in IRR, SAR and YER currencies
15
+ accentmap.delete("\u{20A8}") # Delete ₨ symbol from ACCENTMAP used in INR, LKR, MUR, NPR, PKR and SCR currencies
16
+ split(//u).map {|c| accentmap[c] || c }.join("")
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ require 'money/v6_compatibility/arithmetic'
2
+ require 'money/v6_compatibility/currency_id'
3
+ require 'money/v6_compatibility/format'
4
+ require 'money/v6_compatibility/fractional'
5
+ require 'money/v6_compatibility/bank_rounding_block'
@@ -0,0 +1,61 @@
1
+ class Money
2
+ module V6Compatibility
3
+ module_function
4
+
5
+ def arithmetic
6
+ Money.prepend Arithmetic
7
+ end
8
+
9
+ module Arithmetic
10
+ # Wrapper for coerced numeric values to distinguish
11
+ # when numeric was on the 1st place in operation.
12
+ CoercedNumeric = Struct.new(:value) do
13
+ # Proxy #zero? method to skip unnecessary typecasts. See #- and #+.
14
+ def zero?
15
+ value.zero?
16
+ end
17
+ end
18
+
19
+ def coerce(value)
20
+ [self, CoercedNumeric.new(value)]
21
+ end
22
+
23
+ def <=>(other)
24
+ if !other.is_a?(Money) && other.respond_to?(:zero?) && other.zero?
25
+ return other.is_a?(CoercedNumeric) ? 0 <=> fractional : fractional <=> 0
26
+ end
27
+ super
28
+ end
29
+
30
+ # Uses Comparable's implementation but raises ArgumentError if non-zero
31
+ # numeric value is given.
32
+ def ==(other)
33
+ if other.is_a?(Numeric) && !other.zero?
34
+ raise ArgumentError, 'Money#== supports only zero numerics'
35
+ end
36
+ super
37
+ end
38
+
39
+ def +(other)
40
+ return self if !other.is_a?(Money) && other.zero?
41
+ super
42
+ end
43
+
44
+ def -(other)
45
+ return self if !other.is_a?(Money) && other.zero?
46
+ super
47
+ end
48
+
49
+ def *(other)
50
+ other = other.value if other.is_a?(CoercedNumeric)
51
+ super
52
+ end
53
+
54
+ def /(other)
55
+ raise TypeError, 'Can not divide by Money' if other.is_a?(CoercedNumeric)
56
+ super
57
+ end
58
+ alias_method :div, :/
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,38 @@
1
+ class Money
2
+ module V6Compatibility
3
+ module_function
4
+
5
+ def bank_rounding_block
6
+ Bank::VariableExchange.prepend BankRoundingBlock
7
+ end
8
+
9
+ module BankRoundingBlock
10
+ def exchange_with(from, to_currency, &block)
11
+ to_currency = Currency.wrap(to_currency)
12
+ if from.currency == to_currency
13
+ from
14
+ else
15
+ rate = get_rate(from.currency, to_currency)
16
+ unless rate
17
+ raise Bank::UnknownRate, "No conversion rate known for " \
18
+ "'#{from.currency.code}' -> '#{to_currency}'"
19
+ end
20
+ new_fractional = exchange(from.fractional, rate, &block).to_d
21
+ from.send(:build_new, new_fractional / to_currency.subunit_to_unit, to_currency)
22
+ end
23
+ end
24
+
25
+ def exchange(value, rate, &block)
26
+ rate = BigDecimal.new(rate.to_s) unless rate.is_a?(BigDecimal)
27
+ ex = rate * value
28
+ if block_given?
29
+ yield ex
30
+ elsif rounding_method
31
+ rounding_method.call(ex)
32
+ else
33
+ ex
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,29 @@
1
+ class Money
2
+ module V6Compatibility
3
+ module_function
4
+
5
+ def currency_id
6
+ Currency.prepend(CurrencyId)
7
+ Currency.instances.clear
8
+ end
9
+
10
+ module CurrencyId
11
+ def initialize(*)
12
+ super
13
+ @id = @code.downcase.to_sym
14
+ end
15
+
16
+ def to_sym
17
+ @code.to_sym
18
+ end
19
+
20
+ def code
21
+ symbol || @code
22
+ end
23
+
24
+ def iso_code
25
+ @code
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ class Money
2
+ module V6Compatibility
3
+ module_function
4
+
5
+ def format
6
+ Money.formatter = Formatter
7
+ Money.prepend Formatting
8
+ end
9
+
10
+ module Formatting
11
+ def format(old_rule = nil, **options)
12
+ options = {old_rule => true} if old_rule.is_a?(Symbol)
13
+ super(options)
14
+ end
15
+ end
16
+
17
+ class Formatter < Money::Formatter
18
+ def prepare_rules
19
+ super
20
+ {
21
+ translate: :translate_symbol,
22
+ south_asian_number_formatting: :south_asian,
23
+ thousands_separator: :delimiter,
24
+ decimal_mark: :separator,
25
+ rounded_infinite_precision: :round,
26
+ }.each do |old_key, new_key|
27
+ rules[new_key] = rules[old_key] if rules.key?(old_key)
28
+ end
29
+
30
+ symbol_position = self.symbol_position
31
+ if rules.key?(:symbol_after_without_space) && symbol_position == :after
32
+ rules[:symbol_space] = !rules[:symbol_after_without_space]
33
+ end
34
+ if rules.key?(:symbol_before_without_space) && symbol_position == :before
35
+ rules[:symbol_space] = !rules[:symbol_before_without_space]
36
+ end
37
+
38
+ localize_formatting_rules
39
+ end
40
+
41
+ def localize_formatting_rules
42
+ if currency.code == 'JPY' && I18n.locale == :ja
43
+ rules[:symbol] = '円' unless rules[:symbol] == false
44
+ rules[:symbol_position] = :after
45
+ rules[:symbol_space] = false
46
+ end
47
+ end
48
+
49
+ alias_method :thousands_separator, :delimiter
50
+ alias_method :decimal_mark, :separator
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,74 @@
1
+ class Money
2
+ module V6Compatibility
3
+ module_function
4
+
5
+ def fractional
6
+ Money.prepend Fractional
7
+ Money.extend Fractional::ClassMethods
8
+ end
9
+
10
+ module Fractional
11
+ module ClassMethods
12
+ def from_amount(amount, currency = nil, bank = nil)
13
+ Numeric === amount or raise ArgumentError, "'amount' must be numeric"
14
+ new(amount, currency, bank, true)
15
+ end
16
+
17
+ def from_subunits(amount, currency = nil, bank = nil)
18
+ raise 'Use .new'
19
+ end
20
+
21
+ def new(val, currency = nil, bank = nil, from_amount = false)
22
+ return super(val, currency, bank) if val.is_a?(self) || from_amount
23
+ currency = Currency.wrap(currency || default_currency)
24
+ amount = as_d(val) / currency.subunit_to_unit
25
+ super(amount, currency, bank)
26
+ end
27
+
28
+ def as_d(value)
29
+ if value.respond_to?(:to_d)
30
+ value.is_a?(Rational) ? value.to_d(conversion_precision) : value.to_d
31
+ else
32
+ BigDecimal.new(value.to_s)
33
+ end
34
+ end
35
+ end
36
+
37
+ def to_f
38
+ amount.to_f
39
+ end
40
+
41
+ def yaml_initialize(_tag, attrs)
42
+ super
43
+ fractional = attrs['fractional']
44
+ @amount = as_d(fractional) / currency.subunit_to_unit if fractional
45
+ end
46
+
47
+ def round_to_nearest_cash_value
48
+ value = super * currency.subunit_to_unit
49
+ self.class.infinite_precision ? value : value.to_i
50
+ end
51
+
52
+ def %(other)
53
+ other = other.to_d / currency.subunit_to_unit unless other.is_a?(Money)
54
+ super
55
+ end
56
+ alias_method :modulo, :%
57
+
58
+ def remainder(other)
59
+ other = other.to_d / currency.subunit_to_unit unless other.is_a?(Money)
60
+ super
61
+ end
62
+
63
+ private
64
+
65
+ def build_new(amount, currency = self.currency, bank = self.bank)
66
+ self.class.new(amount, currency, bank, true)
67
+ end
68
+
69
+ def as_d(num)
70
+ self.class.as_d(num)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ class Money
2
+ VERSION = '7.0.0.rc1'.freeze
3
+ end
data/lib/money2.rb ADDED
@@ -0,0 +1 @@
1
+ require 'money'
data/money.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'money/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'money2'
8
+ s.version = Money::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ['Shane Emmons']
11
+ s.email = ['shane@emmons.io']
12
+ s.homepage = 'http://rubymoney.github.io/money'
13
+ s.summary = 'A Ruby Library for dealing with money and currency conversion.'
14
+ s.description = 'A Ruby Library for dealing with money and currency conversion.'
15
+ s.license = 'MIT'
16
+
17
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
18
+ s.bindir = 'exe'
19
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_dependency 'i18n', ['>= 0.6.4', '<= 0.7.0']
23
+
24
+ s.add_development_dependency 'bundler', '~> 1.3'
25
+ s.add_development_dependency 'rake'
26
+ s.add_development_dependency 'rspec', '~> 3.4.0'
27
+ s.add_development_dependency 'rspec-its', '~> 1.1.0'
28
+ s.add_development_dependency 'yard', '~> 0.8'
29
+ s.add_development_dependency 'kramdown', '~> 1.1'
30
+ s.add_development_dependency 'sixarm_ruby_unaccent', ['>= 1.1.1', '< 2']
31
+ end