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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/.travis.yml +24 -0
- data/AUTHORS +126 -0
- data/CHANGELOG.md +619 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +16 -0
- data/LICENSE +23 -0
- data/README.md +438 -0
- data/Rakefile +17 -0
- data/config/currency_backwards_compatible.json +107 -0
- data/config/currency_iso.json +2449 -0
- data/config/currency_non_iso.json +66 -0
- data/lib/money.rb +390 -0
- data/lib/money/allocate.rb +86 -0
- data/lib/money/arithmetic.rb +233 -0
- data/lib/money/bank/base.rb +137 -0
- data/lib/money/bank/single_currency.rb +25 -0
- data/lib/money/bank/variable_exchange.rb +252 -0
- data/lib/money/class_attribute.rb +26 -0
- data/lib/money/currency.rb +402 -0
- data/lib/money/currency/heuristics.rb +150 -0
- data/lib/money/currency/loader.rb +29 -0
- data/lib/money/currency_methods.rb +139 -0
- data/lib/money/formatter.rb +404 -0
- data/lib/money/formatter/to_string.rb +9 -0
- data/lib/money/rates_store/memory.rb +120 -0
- data/lib/money/unaccent.rb +18 -0
- data/lib/money/v6_compatibility.rb +5 -0
- data/lib/money/v6_compatibility/arithmetic.rb +61 -0
- data/lib/money/v6_compatibility/bank_rounding_block.rb +38 -0
- data/lib/money/v6_compatibility/currency_id.rb +29 -0
- data/lib/money/v6_compatibility/format.rb +53 -0
- data/lib/money/v6_compatibility/fractional.rb +74 -0
- data/lib/money/version.rb +3 -0
- data/lib/money2.rb +1 -0
- data/money.gemspec +31 -0
- metadata +207 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'money/unaccent'
|
3
|
+
|
4
|
+
class Money
|
5
|
+
class Currency
|
6
|
+
# Using this module requires `sixarm_ruby_unaccent` gem.
|
7
|
+
# Add it to your Gemfile manually.
|
8
|
+
module Heuristics
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# An robust and efficient algorithm for finding currencies in
|
12
|
+
# text. Using several algorithms it can find symbols, iso codes and
|
13
|
+
# even names of currencies.
|
14
|
+
# Although not recommendable, it can also attempt to find the given
|
15
|
+
# currency in an entire sentence
|
16
|
+
#
|
17
|
+
# Returns: Array (matched results)
|
18
|
+
def analyze(str, klass)
|
19
|
+
Analyzer.new(str, SearchTree.cache[klass]).process
|
20
|
+
end
|
21
|
+
|
22
|
+
class SearchTree
|
23
|
+
class << self
|
24
|
+
def cache
|
25
|
+
@cache ||= Hash.new { |h, k| h[k] = new(k) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :currency_class
|
30
|
+
|
31
|
+
def initialize(currency_class)
|
32
|
+
@currency_class = currency_class
|
33
|
+
end
|
34
|
+
|
35
|
+
def table
|
36
|
+
currency_class.table
|
37
|
+
end
|
38
|
+
|
39
|
+
def by_symbol
|
40
|
+
@by_symbol ||= table.each_with_object({}) do |(_, c), r|
|
41
|
+
symbol = (c[:symbol]||"").downcase
|
42
|
+
symbol.chomp!('.')
|
43
|
+
(r[symbol] ||= []) << c
|
44
|
+
|
45
|
+
(c[:alternate_symbols]||[]).each do |ac|
|
46
|
+
ac = ac.downcase
|
47
|
+
ac.chomp!('.')
|
48
|
+
(r[ac] ||= []) << c
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def by_code
|
54
|
+
@by_code ||= table.each_with_object({}) do |(k, c), r|
|
55
|
+
(r[k.downcase] ||= []) << c
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def by_name
|
60
|
+
@by_name ||= table.each_with_object({}) do |(_, c), r|
|
61
|
+
name_parts = c[:name].unaccent.downcase.split
|
62
|
+
name_parts.each {|part| part.chomp!('.')}
|
63
|
+
|
64
|
+
# construct one branch per word
|
65
|
+
root = r
|
66
|
+
while name_part = name_parts.shift
|
67
|
+
root = (root[name_part] ||= {})
|
68
|
+
end
|
69
|
+
|
70
|
+
# the leaf is a currency
|
71
|
+
(root[:value] ||= []) << c
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Analyzer
|
77
|
+
attr_reader :search_tree, :words
|
78
|
+
attr_accessor :str, :currencies
|
79
|
+
|
80
|
+
def initialize(str, search_tree)
|
81
|
+
@str = (str || '').dup
|
82
|
+
@search_tree = search_tree
|
83
|
+
end
|
84
|
+
|
85
|
+
def process
|
86
|
+
format
|
87
|
+
return [] if str.empty?
|
88
|
+
|
89
|
+
@currencies = []
|
90
|
+
search_by_symbol
|
91
|
+
search_by_code
|
92
|
+
search_by_name
|
93
|
+
|
94
|
+
currencies.map { |x| x[:code] }.tap(&:uniq!).tap(&:sort!)
|
95
|
+
end
|
96
|
+
|
97
|
+
def format
|
98
|
+
str.gsub!(/[\r\n\t]/,'')
|
99
|
+
str.gsub!(/[0-9][\.,:0-9]*[0-9]/,'')
|
100
|
+
str.gsub!(/[0-9]/, '')
|
101
|
+
str.downcase!
|
102
|
+
@words = str.unaccent.split
|
103
|
+
@words.each {|word| word.chomp!('.'); word.chomp!(',') }
|
104
|
+
end
|
105
|
+
|
106
|
+
def search_by_symbol
|
107
|
+
words.each do |word|
|
108
|
+
if found = search_tree.by_symbol[word]
|
109
|
+
currencies.concat(found)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def search_by_code
|
115
|
+
words.each do |word|
|
116
|
+
if found = search_tree.by_code[word]
|
117
|
+
currencies.concat(found)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def search_by_name
|
123
|
+
# remember, the search tree by name is a construct of branches and leaf!
|
124
|
+
# We need to try every combination of words within the sentence, so we
|
125
|
+
# end up with a x^2 equation, which should be fine as most names are either
|
126
|
+
# one or two words, and this is multiplied with the words of given sentence
|
127
|
+
|
128
|
+
search_words = words.dup
|
129
|
+
|
130
|
+
while search_words.length > 0
|
131
|
+
root = search_tree.by_name
|
132
|
+
|
133
|
+
search_words.each do |word|
|
134
|
+
if root = root[word]
|
135
|
+
if root[:value]
|
136
|
+
currencies.concat(root[:value])
|
137
|
+
end
|
138
|
+
else
|
139
|
+
break
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
search_words.delete_at(0)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Money
|
2
|
+
class Currency
|
3
|
+
module Loader
|
4
|
+
DATA_PATH = File.expand_path("../../../../config", __FILE__).freeze
|
5
|
+
FILES = %w(
|
6
|
+
currency_iso.json
|
7
|
+
currency_non_iso.json
|
8
|
+
currency_backwards_compatible.json
|
9
|
+
)
|
10
|
+
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# Loads and returns the currencies stored in JSON files in the config directory.
|
14
|
+
#
|
15
|
+
# @return [Hash]
|
16
|
+
def load_all
|
17
|
+
FILES.inject({}) { |acc, x| acc.merge!(load(x)) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def load(filename)
|
21
|
+
json = File.read("#{DATA_PATH}/#{filename}")
|
22
|
+
json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
|
23
|
+
JSON.parse(json, symbolize_names: true).each_with_object({}) do |x, acc|
|
24
|
+
acc[x[:code]] = x
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
class Money
|
2
|
+
module CurrencyMethods
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Creates a new Money object of the given value, using the Canadian
|
9
|
+
# dollar currency.
|
10
|
+
#
|
11
|
+
# @param [Integer] cents The cents value.
|
12
|
+
#
|
13
|
+
# @return [Money]
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# n = Money.ca_dollar(100)
|
17
|
+
# n.cents #=> 100
|
18
|
+
# n.currency #=> #<Money::Currency id: cad>
|
19
|
+
def ca_dollar(cents)
|
20
|
+
new(cents, "CAD")
|
21
|
+
end
|
22
|
+
alias_method :cad, :ca_dollar
|
23
|
+
|
24
|
+
|
25
|
+
# Creates a new Money object of the given value, using the American dollar
|
26
|
+
# currency.
|
27
|
+
#
|
28
|
+
# @param [Integer] cents The cents value.
|
29
|
+
#
|
30
|
+
# @return [Money]
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# n = Money.us_dollar(100)
|
34
|
+
# n.cents #=> 100
|
35
|
+
# n.currency #=> #<Money::Currency id: usd>
|
36
|
+
def us_dollar(cents)
|
37
|
+
new(cents, "USD")
|
38
|
+
end
|
39
|
+
alias_method :usd, :us_dollar
|
40
|
+
|
41
|
+
|
42
|
+
# Creates a new Money object of the given value, using the Euro currency.
|
43
|
+
#
|
44
|
+
# @param [Integer] cents The cents value.
|
45
|
+
#
|
46
|
+
# @return [Money]
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# n = Money.euro(100)
|
50
|
+
# n.cents #=> 100
|
51
|
+
# n.currency #=> #<Money::Currency id: eur>
|
52
|
+
def euro(cents)
|
53
|
+
new(cents, "EUR")
|
54
|
+
end
|
55
|
+
alias_method :eur, :euro
|
56
|
+
|
57
|
+
|
58
|
+
# Creates a new Money object of the given value, in British pounds.
|
59
|
+
#
|
60
|
+
# @param [Integer] pence The pence value.
|
61
|
+
#
|
62
|
+
# @return [Money]
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# n = Money.pound_sterling(100)
|
66
|
+
# n.fractional #=> 100
|
67
|
+
# n.currency #=> #<Money::Currency id: gbp>
|
68
|
+
def pound_sterling(pence)
|
69
|
+
new(pence, "GBP")
|
70
|
+
end
|
71
|
+
alias_method :gbp, :pound_sterling
|
72
|
+
end
|
73
|
+
|
74
|
+
# Assuming using a currency using dollars:
|
75
|
+
# Returns the value of the money in dollars,
|
76
|
+
# instead of in the fractional unit cents.
|
77
|
+
#
|
78
|
+
# Synonym of #amount
|
79
|
+
#
|
80
|
+
# @return [BigDecimal]
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# Money.new(1_00, "USD").dollars # => BigDecimal.new("1.00")
|
84
|
+
#
|
85
|
+
# @see #amount
|
86
|
+
# @see #to_d
|
87
|
+
# @see #cents
|
88
|
+
#
|
89
|
+
def dollars
|
90
|
+
amount
|
91
|
+
end
|
92
|
+
|
93
|
+
# Convenience method for fractional part of the amount. Synonym of #fractional
|
94
|
+
#
|
95
|
+
# @return [Integer] when infinite_precision is false
|
96
|
+
# @return [BigDecimal] when infinite_precision is true
|
97
|
+
#
|
98
|
+
# @see infinite_precision
|
99
|
+
def cents
|
100
|
+
fractional
|
101
|
+
end
|
102
|
+
|
103
|
+
# Receive a money object with the same amount as the current Money object
|
104
|
+
# in United States dollar.
|
105
|
+
#
|
106
|
+
# @return [Money]
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# n = Money.new(100, "CAD").as_us_dollar
|
110
|
+
# n.currency #=> #<Money::Currency id: usd>
|
111
|
+
def as_us_dollar
|
112
|
+
exchange_to("USD")
|
113
|
+
end
|
114
|
+
|
115
|
+
# Receive a money object with the same amount as the current Money object
|
116
|
+
# in Canadian dollar.
|
117
|
+
#
|
118
|
+
# @return [Money]
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# n = Money.new(100, "USD").as_ca_dollar
|
122
|
+
# n.currency #=> #<Money::Currency id: cad>
|
123
|
+
def as_ca_dollar
|
124
|
+
exchange_to("CAD")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Receive a money object with the same amount as the current Money object
|
128
|
+
# in euro.
|
129
|
+
#
|
130
|
+
# @return [Money]
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
# n = Money.new(100, "USD").as_euro
|
134
|
+
# n.currency #=> #<Money::Currency id: eur>
|
135
|
+
def as_euro
|
136
|
+
exchange_to("EUR")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,404 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
class Money
|
3
|
+
class Formatter
|
4
|
+
extend ClassAttribute
|
5
|
+
|
6
|
+
# @!attribute [rw] use_i18n
|
7
|
+
# @return [Boolean] Use this to disable i18n even if it's used by other
|
8
|
+
# objects in your app.
|
9
|
+
class_attribute :use_i18n
|
10
|
+
self.use_i18n = true
|
11
|
+
|
12
|
+
# @!attribute default_rules
|
13
|
+
# @return [Hash] Use this to define a default hash of rules for every time
|
14
|
+
# +Money#format+ is called. Rules provided on method call will be
|
15
|
+
# merged with the default ones. To overwrite a rule, just provide the
|
16
|
+
# intended value while calling +format+.
|
17
|
+
#
|
18
|
+
# @see +#format+ for more details.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# Money.formatter.default_rules = { :display_free => true }
|
22
|
+
# Money.new(0, "USD").format # => "free"
|
23
|
+
# Money.new(0, "USD").format(:display_free => false) # => "$0.00"
|
24
|
+
class_attribute :default_rules
|
25
|
+
self.default_rules = {}
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def format(*args)
|
29
|
+
new(*args).format
|
30
|
+
end
|
31
|
+
|
32
|
+
def decimal_str(money, decimal_places = money.currency.decimal_places)
|
33
|
+
str = money.to_d.to_s('F')
|
34
|
+
units, fractional = str.split('.')
|
35
|
+
if decimal_places == 0 && fractional == '0'
|
36
|
+
units
|
37
|
+
else
|
38
|
+
pad = decimal_places - fractional.size
|
39
|
+
str << '0' * pad if pad > 0
|
40
|
+
str
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Splits string into chunks fullfilling the rightmost first:
|
45
|
+
#
|
46
|
+
# rsplit_str_by('12345') # => ['12', '345']
|
47
|
+
def rsplit_str_by(str, count)
|
48
|
+
size = str.size
|
49
|
+
i = size % count
|
50
|
+
parts = i > 0 ? [str.slice(0, i)] : []
|
51
|
+
while i < size
|
52
|
+
parts << str.slice(i, count)
|
53
|
+
i += count
|
54
|
+
end
|
55
|
+
parts
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :money, :currency, :rules
|
60
|
+
|
61
|
+
def initialize(money, **rules)
|
62
|
+
@money = money
|
63
|
+
@currency = money.currency
|
64
|
+
@rules = rules
|
65
|
+
end
|
66
|
+
|
67
|
+
# Creates a formatted price string according to several rules.
|
68
|
+
#
|
69
|
+
# @param [Hash] rules The options used to format the string.
|
70
|
+
#
|
71
|
+
# @return [String]
|
72
|
+
#
|
73
|
+
# @option rules [Boolean, String] :display_free (false) Whether a zero
|
74
|
+
# amount of money should be formatted of "free" or as the supplied string.
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# Money.us_dollar(0).format(:display_free => true) #=> "free"
|
78
|
+
# Money.us_dollar(0).format(:display_free => "gratis") #=> "gratis"
|
79
|
+
# Money.us_dollar(0).format #=> "$0.00"
|
80
|
+
#
|
81
|
+
# @option rules [Boolean] :with_currency (false) Whether the currency name
|
82
|
+
# should be appended to the result string.
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# Money.ca_dollar(100).format #=> "$1.00"
|
86
|
+
# Money.ca_dollar(100).format(:with_currency => true) #=> "$1.00 CAD"
|
87
|
+
# Money.us_dollar(85).format(:with_currency => true) #=> "$0.85 USD"
|
88
|
+
#
|
89
|
+
# @option rules [Boolean, Integer] :round (false) Force rounding.
|
90
|
+
# Specify number of digits after decimal point. When true is given
|
91
|
+
# it uses currency's default decimal places count.
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# Money.us_dollar(100.1).format # => "$1.001"
|
95
|
+
# Money.us_dollar(100.1).format(round: true) # => "$1"
|
96
|
+
# Money.us_dollar(100.9).format(round: true) # => "$1.01"
|
97
|
+
# Money.us_dollar(100.9).format(round: 1) # => "$1.00"
|
98
|
+
#
|
99
|
+
# @option rules [Boolean] :no_cents (false) Whether cents should be omitted.
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# Money.ca_dollar(100).format(:no_cents => true) #=> "$1"
|
103
|
+
# Money.ca_dollar(599).format(:no_cents => true) #=> "$5"
|
104
|
+
#
|
105
|
+
# @option rules [Boolean] :no_cents_if_whole (false) Whether cents should be
|
106
|
+
# omitted if the cent value is zero
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# Money.ca_dollar(10000).format(:no_cents_if_whole => true) #=> "$100"
|
110
|
+
# Money.ca_dollar(10034).format(:no_cents_if_whole => true) #=> "$100.34"
|
111
|
+
#
|
112
|
+
# @option rules [Boolean, String, nil] :symbol (true) Whether a money symbol
|
113
|
+
# should be prepended to the result string. The default is true. This method
|
114
|
+
# attempts to pick a symbol that's suitable for the given currency.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# Money.new(100, "USD") #=> "$1.00"
|
118
|
+
# Money.new(100, "GBP") #=> "£1.00"
|
119
|
+
# Money.new(100, "EUR") #=> "€1.00"
|
120
|
+
#
|
121
|
+
# # Same thing.
|
122
|
+
# Money.new(100, "USD").format(:symbol => true) #=> "$1.00"
|
123
|
+
# Money.new(100, "GBP").format(:symbol => true) #=> "£1.00"
|
124
|
+
# Money.new(100, "EUR").format(:symbol => true) #=> "€1.00"
|
125
|
+
#
|
126
|
+
# # You can specify a false expression or an empty string to disable
|
127
|
+
# # prepending a money symbol.§
|
128
|
+
# Money.new(100, "USD").format(:symbol => false) #=> "1.00"
|
129
|
+
# Money.new(100, "GBP").format(:symbol => nil) #=> "1.00"
|
130
|
+
# Money.new(100, "EUR").format(:symbol => "") #=> "1.00"
|
131
|
+
#
|
132
|
+
# # If the symbol for the given currency isn't known, then it will default
|
133
|
+
# # to "¤" as symbol.
|
134
|
+
# Money.new(100, "AWG").format(:symbol => true) #=> "¤1.00"
|
135
|
+
#
|
136
|
+
# # You can specify a string as value to enforce using a particular symbol.
|
137
|
+
# Money.new(100, "AWG").format(:symbol => "ƒ") #=> "ƒ1.00"
|
138
|
+
#
|
139
|
+
# # You can specify a indian currency format
|
140
|
+
# Money.new(10000000, "INR").format(:south_asian => true) #=> "1,00,000.00"
|
141
|
+
# Money.new(10000000).format(:south_asian => true) #=> "$1,00,000.00"
|
142
|
+
#
|
143
|
+
# @option rules [Boolean, nil] :symbol_space (true) Whether
|
144
|
+
# a space between the money symbol and the amount should be inserted when
|
145
|
+
# +:symbol_position+ is +:before+.
|
146
|
+
# The default is false when +:symbol_position+ is +:before+,
|
147
|
+
# and true when +:symbol_position+ is +:after+.
|
148
|
+
# Ignored if +:symbol+ is false.
|
149
|
+
#
|
150
|
+
# @example
|
151
|
+
# # Default is to not insert a space.
|
152
|
+
# Money.new(100, "USD").format #=> "$1.00"
|
153
|
+
#
|
154
|
+
# # Same thing.
|
155
|
+
# Money.new(100, "USD").format(:symbol_space => true) #=> "$1.00"
|
156
|
+
#
|
157
|
+
# # If set to false, will insert a space.
|
158
|
+
# Money.new(100, "USD").format(:symbol_space => false) #=> "$ 1.00"
|
159
|
+
#
|
160
|
+
# # Default is to insert a space.
|
161
|
+
# Money.new(100, "USD").format(:symbol_position => :after) #=> "1.00 $"
|
162
|
+
#
|
163
|
+
# # If set to true, will not insert a space.
|
164
|
+
# Money.new(100, "USD").format(:symbol_position => :after, :symbol_space => true) #=> "1.00$"
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
#
|
168
|
+
# @option rules [Boolean, String, nil] :separator (true) Whether the
|
169
|
+
# currency should be separated by the specified character or '.'
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
# # If a string is specified, it's value is used.
|
173
|
+
# Money.new(100, "USD").format(:separator => ",") #=> "$1,00"
|
174
|
+
#
|
175
|
+
# # If the separator for a given currency isn't known, then it will default
|
176
|
+
# # to "." as separator.
|
177
|
+
# Money.new(100, "FOO").format #=> "$1.00"
|
178
|
+
#
|
179
|
+
# @option rules [Boolean, String, nil] :delimiter (true) Whether
|
180
|
+
# the currency should be delimited by the specified character or ','
|
181
|
+
#
|
182
|
+
# @example
|
183
|
+
# # If false is specified, no delimiter is used.
|
184
|
+
# Money.new(100000, "USD").format(:delimiter => false) #=> "1000.00"
|
185
|
+
# Money.new(100000, "USD").format(:delimiter => nil) #=> "1000.00"
|
186
|
+
# Money.new(100000, "USD").format(:delimiter => "") #=> "1000.00"
|
187
|
+
#
|
188
|
+
# # If a string is specified, it's value is used.
|
189
|
+
# Money.new(100000, "USD").format(:delimiter => ".") #=> "$1.000.00"
|
190
|
+
#
|
191
|
+
# # If the delimiter for a given currency isn't known, then it will
|
192
|
+
# # default to "," as delimiter.
|
193
|
+
# Money.new(100000, "FOO").format #=> "$1,000.00"
|
194
|
+
#
|
195
|
+
# @option rules [Boolean] :html (false) Whether the currency should be
|
196
|
+
# HTML-formatted. Only useful in combination with +:with_currency+.
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# Money.ca_dollar(570).format(:html => true, :with_currency => true)
|
200
|
+
# #=> "$5.70 <span class=\"currency\">CAD</span>"
|
201
|
+
#
|
202
|
+
# @option rules [Boolean] :sign_before_symbol (false) Whether the sign should be
|
203
|
+
# before the currency symbol.
|
204
|
+
#
|
205
|
+
# @example
|
206
|
+
# # You can specify to display the sign before the symbol for negative numbers
|
207
|
+
# Money.new(-100, "GBP").format(:sign_before_symbol => true) #=> "-£1.00"
|
208
|
+
# Money.new(-100, "GBP").format(:sign_before_symbol => false) #=> "£-1.00"
|
209
|
+
# Money.new(-100, "GBP").format #=> "£-1.00"
|
210
|
+
#
|
211
|
+
# @option rules [Boolean] :sign_positive (false) Whether positive numbers should be
|
212
|
+
# signed, too.
|
213
|
+
#
|
214
|
+
# @example
|
215
|
+
# # You can specify to display the sign with positive numbers
|
216
|
+
# Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => true) #=> "+£1.00"
|
217
|
+
# Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => false) #=> "£+1.00"
|
218
|
+
# Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => true) #=> "£1.00"
|
219
|
+
# Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => false) #=> "£1.00"
|
220
|
+
# Money.new(100, "GBP").format #=> "£+1.00"
|
221
|
+
#
|
222
|
+
# @option rules [Boolean] :disambiguate (false) Prevents the result from being ambiguous
|
223
|
+
# due to equal symbols for different currencies. Uses the `disambiguate_symbol`.
|
224
|
+
#
|
225
|
+
# @example
|
226
|
+
# Money.new(10000, "USD").format(:disambiguate => false) #=> "$100.00"
|
227
|
+
# Money.new(10000, "CAD").format(:disambiguate => false) #=> "$100.00"
|
228
|
+
# Money.new(10000, "USD").format(:disambiguate => true) #=> "$100.00"
|
229
|
+
# Money.new(10000, "CAD").format(:disambiguate => true) #=> "C$100.00"
|
230
|
+
#
|
231
|
+
# @option rules [Boolean] :html_wrap_symbol (false) Wraps the currency symbol
|
232
|
+
# in a html <span> tag.
|
233
|
+
#
|
234
|
+
# @example
|
235
|
+
# Money.new(10000, "USD").format(:disambiguate => false)
|
236
|
+
# #=> "<span class=\"currency_symbol\">$100.00</span>
|
237
|
+
#
|
238
|
+
# @option rules [Symbol] :symbol_position (:before) `:before` if the currency
|
239
|
+
# symbol goes before the amount, `:after` if it goes after.
|
240
|
+
#
|
241
|
+
# @example
|
242
|
+
# Money.new(10000, "USD").format(:symbol_position => :before) #=> "$100.00"
|
243
|
+
# Money.new(10000, "USD").format(:symbol_position => :after) #=> "100.00 $"
|
244
|
+
#
|
245
|
+
# @option rules [Boolean] :translate_symbol (true) `true` Checks for custom
|
246
|
+
# symbol definitions using I18n.
|
247
|
+
#
|
248
|
+
# @example
|
249
|
+
# # With the following entry in the translation files:
|
250
|
+
# # en:
|
251
|
+
# # number:
|
252
|
+
# # currency:
|
253
|
+
# # symbol:
|
254
|
+
# # CAD: "CAD$"
|
255
|
+
# Money.new(10000, "CAD").format(:translate_symbol => true) #=> "CAD$100.00"
|
256
|
+
#
|
257
|
+
# @example
|
258
|
+
# Money.new(89000, :btc).format(:drop_trailing_zeros => true) #=> B⃦0.00089
|
259
|
+
# Money.new(110, :usd).format(:drop_trailing_zeros => true) #=> $1.1
|
260
|
+
#
|
261
|
+
# Note that the default rules can be defined through {Money.default_rules} hash.
|
262
|
+
#
|
263
|
+
# @see Money.default_rules Money.default_rules for more information.
|
264
|
+
def format
|
265
|
+
prepare_rules
|
266
|
+
return display_free if money.to_d == 0 && rules[:display_free]
|
267
|
+
str = format_number(money.to_d)
|
268
|
+
str = add_symbol_and_sign(str)
|
269
|
+
add_currency(str)
|
270
|
+
end
|
271
|
+
|
272
|
+
def format_number(val)
|
273
|
+
number_str =
|
274
|
+
if rules[:no_cents] || (rules[:no_cents_if_whole] && val % 1 == 0)
|
275
|
+
val.to_i.to_s
|
276
|
+
else
|
277
|
+
round = rules[:round]
|
278
|
+
if round
|
279
|
+
decimal_places = round == true ? currency.decimal_places : round
|
280
|
+
val = val.round(decimal_places)
|
281
|
+
end
|
282
|
+
self.class.decimal_str(val.abs, decimal_places || currency.decimal_places)
|
283
|
+
end
|
284
|
+
|
285
|
+
units, fractions = number_str.split('.')
|
286
|
+
if rules[:drop_trailing_zeros]
|
287
|
+
fractions.sub!(/0+\z/, '')
|
288
|
+
fractions = nil if fractions.empty?
|
289
|
+
end
|
290
|
+
|
291
|
+
units = apply_delimiter(units)
|
292
|
+
separator = rules[:separator] || self.separator
|
293
|
+
fractions ? "#{units}#{separator}#{fractions}" : units
|
294
|
+
end
|
295
|
+
|
296
|
+
def add_symbol_and_sign(number_str)
|
297
|
+
symbol_position = self.symbol_position
|
298
|
+
symbol = self.symbol
|
299
|
+
sign =
|
300
|
+
if money.negative?
|
301
|
+
'-'
|
302
|
+
elsif rules[:sign_positive] && money.positive?
|
303
|
+
'+'
|
304
|
+
end
|
305
|
+
|
306
|
+
if rules[:sign_before_symbol]
|
307
|
+
sign_before = sign
|
308
|
+
sign = nil
|
309
|
+
end
|
310
|
+
|
311
|
+
if symbol && !symbol.empty?
|
312
|
+
symbol = "<span class=\"currency_symbol\">#{symbol}</span>" if rules[:html_wrap_symbol]
|
313
|
+
case symbol_position
|
314
|
+
when :before
|
315
|
+
symbol_space = rules[:symbol_space] ? ' ' : nil
|
316
|
+
"#{sign_before}#{symbol}#{symbol_space}#{sign}#{number_str}"
|
317
|
+
when :after
|
318
|
+
symbol_space = rules.fetch(:symbol_space) { true } ? ' ' : nil
|
319
|
+
"#{sign_before}#{sign}#{number_str}#{symbol_space}#{symbol}"
|
320
|
+
else
|
321
|
+
raise ArgumentError, ':symbol_position must be :before or :after'
|
322
|
+
end
|
323
|
+
else
|
324
|
+
"#{sign_before}#{sign}#{number_str}"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def add_currency(str)
|
329
|
+
return str unless rules[:with_currency]
|
330
|
+
currency_str = currency.to_s
|
331
|
+
currency_str = "<span class=\"currency\">#{currency_str}</span>" if rules[:html]
|
332
|
+
str << ' ' << currency_str
|
333
|
+
end
|
334
|
+
|
335
|
+
def display_free
|
336
|
+
rules[:display_free].respond_to?(:to_str) ? rules[:display_free] : 'free'
|
337
|
+
end
|
338
|
+
|
339
|
+
def delimiter
|
340
|
+
val_from_i18n(:delimiter, ',')
|
341
|
+
end
|
342
|
+
|
343
|
+
def separator
|
344
|
+
val_from_i18n(:separator, '.')
|
345
|
+
end
|
346
|
+
|
347
|
+
def symbol
|
348
|
+
if rules[:translate_symbol] && rules[:symbol] != false
|
349
|
+
val = symbol_from_i18n
|
350
|
+
return val if val
|
351
|
+
end
|
352
|
+
if rules.key?(:symbol)
|
353
|
+
symbol = rules[:symbol]
|
354
|
+
if symbol == true
|
355
|
+
rules[:disambiguate] && currency.disambiguate_symbol || money.symbol
|
356
|
+
else
|
357
|
+
symbol
|
358
|
+
end
|
359
|
+
elsif rules[:html]
|
360
|
+
currency.html_entity || currency.symbol
|
361
|
+
else
|
362
|
+
rules[:disambiguate] && currency.disambiguate_symbol || money.symbol
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def symbol_position
|
367
|
+
rules[:symbol_position] || (currency.symbol_first? ? :before : :after)
|
368
|
+
end
|
369
|
+
|
370
|
+
private
|
371
|
+
|
372
|
+
def val_from_i18n(name, default)
|
373
|
+
if self.class.use_i18n
|
374
|
+
I18n.t name, scope: 'number.currency.format', default: ->(*) do
|
375
|
+
I18n.t name, scope: 'number.format',
|
376
|
+
default: ->(*) { currency.public_send(name) || default }
|
377
|
+
end
|
378
|
+
else
|
379
|
+
currency.public_send(name) || default
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def symbol_from_i18n
|
384
|
+
I18n.t currency.code, scope: 'number.currency.symbol', raise: true
|
385
|
+
rescue I18n::MissingTranslationData
|
386
|
+
end
|
387
|
+
|
388
|
+
def apply_delimiter(units)
|
389
|
+
parts =
|
390
|
+
if rules[:south_asian]
|
391
|
+
self.class.rsplit_str_by(units[0...-3], 2) + [[units[-3..-1]]]
|
392
|
+
else
|
393
|
+
self.class.rsplit_str_by(units, 3)
|
394
|
+
end
|
395
|
+
return parts.first if parts.one?
|
396
|
+
delimiter = rules.key?(:delimiter) ? rules[:delimiter] || '' : self.delimiter
|
397
|
+
parts.join(delimiter)
|
398
|
+
end
|
399
|
+
|
400
|
+
def prepare_rules
|
401
|
+
@rules = self.class.default_rules.merge(rules)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|