samlown-money 2.3.3.2

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2005 Tobias Lutke
2
+ Copyright (c) 2008 Phusion
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ History.txt
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/money.rb
7
+ lib/money/acts_as_money.rb
8
+ lib/money/core_extensions.rb
9
+ lib/money/errors.rb
10
+ lib/money/money.rb
11
+ lib/money/variable_exchange_bank.rb
12
+ money.gemspec
13
+ rails/init.rb
14
+ script/console
15
+ script/destroy
16
+ script/generate
17
+ spec/db/database.yml
18
+ spec/db/schema.rb
19
+ spec/money/acts_as_money_spec.rb
20
+ spec/money/core_extensions_spec.rb
21
+ spec/money/exchange_bank_spec.rb
22
+ spec/money/money_spec.rb
23
+ spec/spec.opts
24
+ spec/spec_helper.rb
25
+ tmp/acts_as_money.sqlite3
@@ -0,0 +1,137 @@
1
+ = Money $
2
+
3
+ This library aids one in handling money and different currencies. Features:
4
+
5
+ - Provides a Money class which encapsulates all information about an certain
6
+ amount of money, such as its value and its currency.
7
+ - Represents monetary values as integers, in cents. This avoids floating point
8
+ rounding errors.
9
+ - Provides APIs for exchanging money from one currency to another.
10
+ - Has the ability to parse a money string into a Money object.
11
+ - Provides ActiveRecord "has_money" method.
12
+
13
+ Resources:
14
+
15
+ - This fork: http://github.com/samlown/money
16
+ - Previous fork: http://github.com/ShadowBelmolve/money
17
+ - Website: http://money.rubyforge.org
18
+ - RDoc API: http://money.rubyforge.org
19
+ - Git repository: http://github.com/FooBarWidget/money/tree/master
20
+
21
+
22
+ == Download
23
+
24
+ Install stable releases with the following command:
25
+
26
+ gem install money
27
+
28
+ The development version (hosted on Github) can be installed with:
29
+
30
+ gem sources -a http://gems.github.com
31
+ gem install samlown-money
32
+
33
+
34
+ == Usage
35
+
36
+ === Synopsis
37
+
38
+ require 'money'
39
+
40
+ # 10.00 USD
41
+ money = Money.new(1000, "USD")
42
+ money.cents # => 1000
43
+ money.currency # => "USD"
44
+ money.format # => "$10.00"
45
+
46
+ Money.new(880088, "EUR").format # => €8,800.88
47
+ Money.new(-8000).format(:no_cents => true) # => $-80
48
+
49
+ Money.new(1000, "USD") == Money.new(1000, "USD") # => true
50
+ Money.new(1000, "USD") == Money.new( 100, "USD") # => false
51
+ Money.new(1000, "USD") == Money.new(1000, "EUR") # => false
52
+
53
+ # Comparisons with other numbers
54
+ Money.new(10.10) > 10.0 # => true
55
+ Money.new(1001) > 10 # => true
56
+ Money.new(999) > 10 # => false
57
+
58
+
59
+ === Currency Exchange
60
+
61
+ Exchanging money is performed through an exchange bank object. The default
62
+ exchange bank object requires one to manually specify the exchange rate. Here's
63
+ an example of how it works:
64
+
65
+ Money.add_rate("CAD", 0.803115)
66
+ Money.add_rate("USD", 1.24515)
67
+
68
+ Money.us_dollar(100_00).exchange_to("CAD") # => Money.new(15504, "CAD")
69
+ Money.ca_dollar(100_00).exchange_to("USD") # => Money.new(6450, "USD")
70
+
71
+ or
72
+
73
+ Money.us_dollar(100).as_cad # => Money.new(155, "CAD")
74
+ Money.ca_dollar(100).as_usd # => Money.new(64, "USD")
75
+
76
+ Comparison and arithmetic operations work as expected:
77
+
78
+ Money.new(1000, "USD") <=> Money.new(900, "USD") # => 1; 9.00 USD is smaller
79
+ Money.new(1000, "EUR") + Money.new(10, "EUR") == Money.new(1010, "EUR")
80
+
81
+ Money.add_rate("EUR", 0.5)
82
+ Money.new(1000, "EUR") + Money.new(1000, "USD") == Money.new(1500, "EUR")
83
+
84
+ Fetch the exchange rates published by the European Bank
85
+
86
+ Money.default_bank.fetch_rates # Fetch the rates
87
+ Money.default_bank.auto_fetch 3600 # Fetch the rates every hour
88
+ Money.default_bank.stop_fetch # Stop auto-fetch
89
+
90
+ There is nothing stopping you from creating bank objects which scrapes
91
+ www.xe.com for the current rates or just returns <tt>rand(2)</tt>:
92
+
93
+ Money.default_bank = ExchangeBankWhichScrapesXeDotCom.new
94
+
95
+
96
+ === Ruby on Rails
97
+
98
+ Use the +has_money+ method to embed the money object in your models.
99
+ The following example requires a +price_cents+ and a +price_currency+
100
+ fields on the database.
101
+
102
+ config/enviroment.rb
103
+
104
+ require.gem 'ShadowBelmolve-money', :lib => 'money'
105
+
106
+ app/models/product.rb
107
+
108
+ class Product < ActiveRecord::Base
109
+ belongs_to :product
110
+ has_money :price
111
+
112
+ validates_numericality_of :price_cents, :greater_than => 0
113
+ end
114
+
115
+ migration:
116
+
117
+ create_table :products do |t|
118
+ t.integer :price_cents
119
+ t.string :price_currency
120
+ end
121
+
122
+
123
+ === Default Currency
124
+
125
+ By default Money defaults to USD as its currency. This can be overwritten using:
126
+
127
+ Money.default_currency = "CAD"
128
+
129
+ If you use Rails, then environment.rb is a very good place to put this.
130
+
131
+
132
+ == TODO
133
+
134
+ * Better validation (fix composed_of allow_nil)
135
+ * Interest (almost there..)
136
+ * Remote rate fetching
137
+
@@ -0,0 +1,27 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/money'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('money', Money::VERSION) do |p|
7
+ p.developer('Money Team', 'see@readme')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.rubyforge_name = p.name
10
+ p.summary = "This library aids one in handling money and different currencies."
11
+ p.description = "This library aids one in handling money and different currencies."
12
+ p.url = "http://github.com/nofxx/money"
13
+
14
+
15
+ p.extra_dev_deps = [
16
+ ['newgem', ">= #{::Newgem::VERSION}"]
17
+ ]
18
+
19
+ p.clean_globs |= %w[**/.DS_Store *.log]
20
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
21
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
22
+ p.rsync_args = '-av --delete --ignore-errors'
23
+ end
24
+
25
+ require 'newgem/tasks' # load /tasks/*.rake
26
+ Dir['tasks/**/*.rake'].each { |t| load t }
27
+
@@ -0,0 +1,29 @@
1
+ # Copyright (c) 2005 Tobias Luetke
2
+ # Copyright (c) 2008 Phusion
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
24
+ require 'money/money'
25
+ require 'money/core_extensions'
26
+
27
+ class Money
28
+ VERSION = "2.3.3"
29
+ end
@@ -0,0 +1,41 @@
1
+ # Money require 'money'
2
+ # based on github.com/collectiveidea/acts_as_money
3
+ module ActsAsMoney #:nodoc:
4
+ def self.included(base) #:nodoc:
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ #
10
+ # class Product
11
+ # has_money :value, :tax, :opts => opts
12
+ # end
13
+ #
14
+ # @product.value.class #=> "Money"
15
+ # @product.value_cents #=> "1000"
16
+ # @product.tax_currency #=> "USD"
17
+ #
18
+ # Opts:
19
+ # :cents => "pennys" #=> @product.pennys
20
+ # :currency => "currency" #=> @product.currency
21
+ # :allow_nil => true
22
+ # :with_currency => false
23
+ # :with_cents => true #=> 1000.to_money #=> #<Money @cents=1000>
24
+ #
25
+ def has_money(*attributes)
26
+ config = {:with_currency => true, :with_cents => false,
27
+ :allow_nil => false }.update(attributes.extract_options!)
28
+
29
+ for attribute in attributes do
30
+ mapping = [[config[:cents] || "#{attribute}_cents", 'cents']]
31
+ mapping << [config[:currency] || "#{attribute}_currency", 'currency'] if config[:with_currency]
32
+
33
+ composed_of attribute, :class_name => 'Money', :allow_nil => config[:allow_nil],
34
+ :mapping => mapping, :converter => lambda { |m| (m||0).to_money(config[:with_cents]) }
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,139 @@
1
+ class Numeric
2
+ # Converts this numeric to a Money object in the default currency. It
3
+ # multiplies the numeric value by 100 and treats that as cents if receive false.
4
+ #
5
+ # 100.to_money => #<Money @cents=100>
6
+ # 100.37.to_money => #<Money @cents=10037>
7
+ # 100.to_money(false) => #<Money @cents=10000>
8
+ def to_money(cents = false)
9
+ if cents
10
+ if self.is_a? Integer
11
+ Money.new(self)
12
+ else
13
+ Money.new(self.to_s.gsub(/\./,'').to_i)
14
+ end
15
+ else
16
+ Money.new(self * 100)
17
+ end
18
+ end
19
+ end
20
+
21
+ class String
22
+ # Parses the current string and converts it to a Money object.
23
+ # Excess characters will be discarded.
24
+ #
25
+ # '100'.to_money # => #<Money @cents=100>
26
+ # '100.37'.to_money # => #<Money @cents=10037>
27
+ # '100 USD'.to_money # => #<Money @cents=100, @currency="USD">
28
+ # 'USD 100'.to_money # => #<Money @cents=100, @currency="USD">
29
+ # '$100 USD'.to_money # => #<Money @cents=100, @currency="USD">
30
+ # '$100 USD'.to_money(false) # => #<Money @cents=10000, @currency="USD">
31
+ def to_money(with_cents = false)
32
+ # Get the currency.
33
+ matches = scan /([A-Z]{2,3})/
34
+ currency = matches[0] ? matches[0][0] : Money.default_currency
35
+ cents = calculate_cents(self, with_cents)
36
+ Money.new(cents, currency)
37
+ end
38
+
39
+ private
40
+
41
+ def calculate_cents(number, with_cents = true)
42
+ # remove anything that's not a number, potential delimiter, or minus sign
43
+ num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
44
+
45
+ # set a boolean flag for if the number is negative or not
46
+ negative = num.split(//).first == "-"
47
+
48
+ # if negative, remove the minus sign from the number
49
+ num = num.gsub(/^-/, '') if negative
50
+
51
+ # gather all separators within the result number
52
+ used_separators = num.scan /[^\d]/
53
+
54
+ # determine the number of unique separators within the number
55
+ #
56
+ # e.g.
57
+ # $1,234,567.89 would return 2 (, and .)
58
+ # $125,00 would return 1
59
+ # $199 would return 0
60
+ # $1 234,567.89 would raise an error (separators are space, comma, and period)
61
+ case used_separators.uniq.length
62
+ # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
63
+ when 0 then major, minor = num, 0
64
+
65
+ # two separators, so we know the last item in this array is the
66
+ # major/minor delimiter and the rest are separators
67
+ when 2
68
+ separator, delimiter = used_separators.uniq
69
+ # remove all separators, split on the delimiter
70
+ major, minor = num.gsub(separator, '').split(delimiter)
71
+ min = 0 unless min
72
+ when 1
73
+ # we can't determine if the comma or period is supposed to be a separator or a delimiter
74
+ # e.g.
75
+ # 1,00 - comma is a delimiter
76
+ # 1.000 - period is a delimiter
77
+ # 1,000 - comma is a separator
78
+ # 1,000,000 - comma is a separator
79
+ # 10000,00 - comma is a delimiter
80
+ # 1000,000 - comma is a delimiter
81
+
82
+ # assign first separator for reusability
83
+ separator = used_separators.first
84
+
85
+ # separator is used as a separator when there are multiple instances, always
86
+ if num.scan(separator).length > 1 # multiple matches; treat as separator
87
+ major, minor = num.gsub(separator, ''), 0
88
+ else
89
+ # ex: 1,000 - 1.0000 - 10001.000
90
+ # split number into possible major (dollars) and minor (cents) values
91
+ possible_major, possible_minor = num.split(separator)
92
+
93
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
94
+ # e.g.
95
+ # 1,00 => 1.00
96
+ # 1.0000 => 1.00
97
+ # 1.2 => 1.20
98
+ if possible_minor.length != 3 # delimiter
99
+ major, minor = possible_major, possible_minor
100
+ else
101
+ # minor length is three
102
+ # let's try to figure out intent of the delimiter
103
+
104
+ # the major length is greater than three, which means
105
+ # the comma or period is used as a delimiter
106
+ # e.g.
107
+ # 1000,000
108
+ # 100000,000
109
+ if possible_major.length > 3
110
+ major, minor = possible_major, possible_minor
111
+ else
112
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
113
+ # handle as , is sep, . is delimiter
114
+ if separator == '.'
115
+ major, minor = possible_major, possible_minor
116
+ else
117
+ major, minor = "#{possible_major}#{possible_minor}", 0
118
+ end
119
+ end
120
+ end
121
+ end
122
+ else
123
+ raise ArgumentError, "Invalid currency amount"
124
+ end
125
+
126
+ # build the string based on major/minor since separator/delimiters have been removed
127
+ # transform to a float, multiply by 100 to convert to cents
128
+ if with_cents and minor == 0
129
+ cents = "#{major}.#{minor}".to_f
130
+ else
131
+ cents = "#{major}.#{minor}".to_f * 100
132
+ end
133
+
134
+
135
+ # if negative, multiply by -1; otherwise, return positive cents
136
+ negative ? cents * -1 : cents
137
+ end
138
+
139
+ end
@@ -0,0 +1,4 @@
1
+ class Money
2
+ class UnknownRate < StandardError
3
+ end
4
+ end
@@ -0,0 +1,319 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'money/variable_exchange_bank'
3
+
4
+ # Represents an amount of money in a certain currency.
5
+ class Money
6
+ include Comparable
7
+
8
+ attr_reader :cents, :currency, :bank
9
+
10
+ class << self
11
+ # Each Money object is associated to a bank object, which is responsible
12
+ # for currency exchange. This property allows one to specify the default
13
+ # bank object.
14
+ #
15
+ # bank1 = MyBank.new
16
+ # bank2 = MyOtherBank.new
17
+ #e if VariableExchangeBank.
18
+ # It allows one to specify custom exchange rates:
19
+ #
20
+ # Money.default_bank.add_rate("USD", "CAD", 1.24515)
21
+ # Money.default_bank.add_rate("CAD", "USD", 0.803115)
22
+ # Money.us_dollar(100).exchange_to("CAD") # => MONEY.ca_dollar(124)
23
+ # Money.ca_dollar(100).exchange_to("USD") # => Money.us_dollar(80)
24
+ attr_accessor :default_bank
25
+
26
+ # the default currency, which is used when <tt>Money.new</tt> is called
27
+ # without an explicit currency argument. The default value is "USD".
28
+ attr_accessor :default_currency
29
+ end
30
+
31
+ self.default_bank = VariableExchangeBank.instance
32
+ self.default_currency = "USD"
33
+
34
+ CURRENCIES = {
35
+ "USD" => { :delimiter => ",", :separator => ".", :symbol => "$" },
36
+ "CAD" => { :delimiter => ",", :separator => ".", :symbol => "$" },
37
+ "HKD" => { :delimiter => ",", :separator => ".", :symbol => "$" },
38
+ "SGD" => { :delimiter => ",", :separator => ".", :symbol => "$" },
39
+ "BRL" => { :delimiter => ".", :separator => ",", :symbol => "R$" },
40
+ "EUR" => { :delimiter => ",", :separator => ".", :symbol => '€', :html => '&euro;' },
41
+ "GBP" => { :delimiter => ",", :separator => ".", :symbol => '£', :html => '&pound;' },
42
+ "JPY" => { :delimiter => ".", :separator => ".", :symbol => '¥', :html => '&yen;' },
43
+ }
44
+
45
+ # Create a new money object with value 0.
46
+ def self.empty(currency = default_currency)
47
+ Money.new(0, currency)
48
+ end
49
+
50
+ # Creates a new Money object of the given value, using the Canadian dollar currency.
51
+ def self.ca_dollar(cents)
52
+ Money.new(cents, "CAD")
53
+ end
54
+
55
+ # Creates a new Money object of the given value, using the American dollar currency.
56
+ def self.us_dollar(cents)
57
+ Money.new(cents, "USD")
58
+ end
59
+
60
+ # Creates a new Money object of the given value, using the Euro currency.
61
+ def self.euro(cents)
62
+ Money.new(cents, "EUR")
63
+ end
64
+
65
+ # Creates a new Money object of the given value, using the Brazilian Real currency.
66
+ def self.real(cents)
67
+ Money.new(cents, "BRL")
68
+ end
69
+
70
+ def self.add_rate(currency, rate)
71
+ Money.default_bank.add_rate(currency, rate)
72
+ end
73
+
74
+ # Creates a new money object.
75
+ # Money.new(100)
76
+ #
77
+ # Alternativly you can use the convinience methods like
78
+ # Money.ca_dollar and Money.us_dollar
79
+ def initialize(cents, currency = nil, bank = nil)
80
+ @cents = cents.to_i
81
+ @currency = currency || Money.default_currency
82
+ @bank = bank || Money.default_bank
83
+ end
84
+
85
+ # Do two money objects equal? Only works if both objects are of the same currency
86
+ def ==(other_money)
87
+ cents == other_money.cents && bank.same_currency?(currency, other_money.currency)
88
+ end
89
+
90
+ def <=>(other_money)
91
+ case other_money
92
+ when Money
93
+ if bank.same_currency?(currency, other_money.currency)
94
+ cents <=> other_money.cents
95
+ else
96
+ cents <=> other_money.exchange_to(currency).cents
97
+ end
98
+ when Numeric
99
+ cents <=> (other_money * 100).to_i
100
+ else
101
+ raise "Comparison attempted with incompatible Money type"
102
+ end
103
+ end
104
+
105
+ def +(other_money)
106
+ other_money = Money.new(other_money) unless other_money.is_a? Money
107
+ if currency == other_money.currency
108
+ Money.new(cents + other_money.cents, other_money.currency)
109
+ else
110
+ Money.new(cents + other_money.exchange_to(currency).cents,currency)
111
+ end
112
+ end
113
+
114
+ def -(other_money)
115
+ other_money = Money.new(other_money) unless other_money.is_a? Money
116
+ if currency == other_money.currency
117
+ Money.new(cents - other_money.cents, other_money.currency)
118
+ else
119
+ Money.new(cents - other_money.exchange_to(currency).cents, currency)
120
+ end
121
+ end
122
+
123
+ # get the cents value of the object
124
+ def cents
125
+ @cents
126
+ end
127
+
128
+ # multiply money by fixnum
129
+ def *(fixnum)
130
+ Money.new(cents * fixnum, currency)
131
+ end
132
+
133
+ # divide money by fixnum
134
+ # check out split_in_installments method too
135
+ def /(fixnum)
136
+ Money.new(cents / fixnum, currency)
137
+ end
138
+
139
+ def %(fixnum)
140
+ Money.new(cents % fixnum, currency)
141
+ end
142
+
143
+ # Test if the money amount is zero
144
+ def zero?
145
+ cents == 0
146
+ end
147
+
148
+ # Calculates compound interest
149
+ # Returns a money object with the sum of self + it
150
+ def compound_interest(rate, count = 1, period = 12)
151
+ Money.new(cents * ((1 + rate / 100.0 / period) ** count - 1))
152
+ end
153
+
154
+ # Calculate self + simple interest
155
+ def simple_interest(rate, count = 1, period = 12)
156
+ Money.new(rate / 100 / period * cents * count)
157
+ end
158
+
159
+ # Split money in number of installments
160
+ #
161
+ # Money.new(10_00).split_in_installments(3)
162
+ # => [ 3.34, 3.33, 3.33 ] (All Money instances)
163
+ #
164
+ def split_in_installments(fixnum, order=false)
165
+ wallet = Wallet.new(fixnum, Money.new(cents/fixnum,currency))
166
+ to_add = cents % fixnum
167
+ to_add.times { |m| wallet[m] += Money.new(1) }
168
+ wallet.reverse! if order
169
+ wallet
170
+ end
171
+
172
+ # Split money in installments based on payment value
173
+ #
174
+ # Money.new(1000_00).split_in_installments(Money.new(300_00))
175
+ # => [ 334_00, 333_00, 333_00 ] (All Money instances)
176
+ #
177
+ def in_installments_of(other_money, order=false)
178
+ split_in_installments(cents/other_money.cents, order)
179
+ end
180
+
181
+ # Just a helper if you got tax inputs in percentage.
182
+ # Ie. add_tax(20) => cents * 1.20
183
+ def add_tax(tax)
184
+ Money.new(cents + cents / 100 * tax)
185
+ end
186
+
187
+ # Format the price according to several rules
188
+ # Currently supported are :with_currency, :no_cents, :symbol and :html
189
+ #
190
+ # with_currency:
191
+ #
192
+ # Money.ca_dollar(0).format => "free"
193
+ # Money.ca_dollar(100).format => "$1.00"
194
+ # Money.ca_dollar(100).format(:with_currency => true) => "$1.00 CAD"
195
+ # Money.us_dollar(85).format(:with_currency => true) => "$0.85 USD"
196
+ #
197
+ # no_cents:
198
+ #
199
+ # Money.ca_dollar(100).format(:no_cents) => "$1"
200
+ # Money.ca_dollar(599).format(:no_cents) => "$5"
201
+ #
202
+ # Money.ca_dollar(570).format(:no_cents, :with_currency) => "$5 CAD"
203
+ # Money.ca_dollar(39000).format(:no_cents) => "$390"
204
+ #
205
+ # symbol:
206
+ #
207
+ # Money.new(100, :currency => "GBP").format(:symbol => "£") => "£1.00"
208
+ #
209
+ # html:
210
+ #
211
+ # Money.ca_dollar(570).format(:html => true, :with_currency => true) => "$5.70 <span class=\"currency\">CAD</span>"
212
+ def format(*rules)
213
+ # support for old format parameters
214
+ rules = normalize_formatting_rules(rules)
215
+
216
+ if cents == 0
217
+ if rules[:display_free].respond_to?(:to_str)
218
+ return rules[:display_free]
219
+ elsif rules[:display_free]
220
+ return "free"
221
+ end
222
+ end
223
+
224
+ if rules.has_key?(:symbol)
225
+ if rules[:symbol]
226
+ symbol = rules[:symbol]
227
+ else
228
+ symbol = ""
229
+ end
230
+ else
231
+ symbol = CURRENCIES[currency][:symbol]
232
+ end
233
+ self.currency
234
+
235
+ if rules[:no_cents]
236
+ formatted = sprintf("#{symbol}%d", cents.to_f / 100)
237
+ formatted.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{CURRENCIES[currency][:delimiter]}")
238
+ else
239
+ formatted = sprintf("#{symbol}%.2f", cents.to_f / 100).split('.')
240
+ formatted[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{CURRENCIES[currency][:delimiter]}")
241
+ formatted = formatted.join(CURRENCIES[currency][:separator])
242
+ end
243
+
244
+ # Commify ("10000" => "10,000")
245
+ formatted.gsub!(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
246
+
247
+ if rules[:with_currency]
248
+ formatted << " "
249
+ formatted << '<span class="currency">' if rules[:html]
250
+ formatted << currency
251
+ formatted << '</span>' if rules[:html]
252
+ end
253
+ formatted.gsub!(CURRENCIES[currency][:symbol],CURRENCIES[currency][:html]) if rules[:html]
254
+ formatted
255
+ end
256
+
257
+ def normalize_formatting_rules(rules)
258
+ if rules.size == 1
259
+ rules = rules.pop
260
+ rules = { rules => true } if rules.is_a?(Symbol)
261
+ else
262
+ rules = rules.inject({}) do |h,s|
263
+ h[s] = true
264
+ h
265
+ end
266
+ end
267
+ rules
268
+ end
269
+
270
+
271
+ # Money.ca_dollar(100).to_s => "1.00"
272
+ def to_s
273
+ sprintf("%.2f", cents / 100.0)
274
+ end
275
+
276
+ # Money.new(123).to_i => "1"
277
+ def to_i
278
+ (cents / 100.0).round
279
+ end
280
+
281
+ # Money.ca_dollar(100).to_f => "1.0"
282
+ def to_f
283
+ cents / 100.0
284
+ end
285
+
286
+ # Recieve the amount of this money object in another currency.
287
+ def exchange_to(other_currency)
288
+ Money.new(@bank.exchange(self.cents, currency, other_currency), other_currency)
289
+ end
290
+
291
+ # Conversation to self
292
+ def to_money
293
+ self
294
+ end
295
+
296
+ def method_missing(m,*x)
297
+ if m.to_s =~ /^as/
298
+ exchange_to(m.to_s.split("_").last.upcase)
299
+ else
300
+ super
301
+ end
302
+ end
303
+ end
304
+
305
+ #
306
+ # Represent a financial array.
307
+ # Investment/Time/Installments...
308
+ #
309
+ class Wallet < Array
310
+
311
+ def to_s
312
+ map &:to_s
313
+ end
314
+
315
+ def sum
316
+ Money.new(inject(0){ |sum,m| sum + m.cents })
317
+ end
318
+
319
+ end