money2 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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