bai-money 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
@@ -0,0 +1,87 @@
1
+ This library aids one in handling money and different currencies. Features:
2
+
3
+ * Provides a Money class which encapsulates all information about an certain amount of money, such as its value and its currency.
4
+ * Represents monetary values as integers, in cents. This avoids floating point rounding errors.
5
+ * Provides APIs for exchanging money from one currency to another.
6
+ * Has the ability to parse a money string into a Money object.
7
+
8
+ ### Note on Patches/Pull Requests
9
+
10
+ * Fork the project.
11
+ * Make your feature addition or bug fix.
12
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ ### Usage
17
+
18
+ #### Synopsis
19
+
20
+ require 'money'
21
+
22
+ # 10.00 USD
23
+ money = Money.new(1000, "USD")
24
+ money.cents # => 1000
25
+ money.currency # => "USD"
26
+
27
+ Money.new(1000, "USD") == Money.new(1000, "USD") # => true
28
+ Money.new(1000, "USD") == Money.new(100, "USD") # => false
29
+ Money.new(1000, "USD") == Money.new(1000, "EUR") # => false
30
+
31
+ #### Currency Exchange
32
+
33
+ Exchanging money is performed through an exchange bank object. The default exchange bank object requires one to manually specify the exchange rate. Here’s an example of how it works:
34
+
35
+ Money.add_rate("USD", "CAD", 1.24515)
36
+ Money.add_rate("CAD", "USD", 0.803115)
37
+
38
+ Money.new(100, "USD").exchange_to("CAD") # => Money.new(124, "CAD")
39
+ Money.new(100, "CAD").exchange_to("USD") # => Money.new(80, "USD")
40
+
41
+ Comparison and arithmetic operations work as expected:
42
+
43
+ Money.new(1000, "USD") <=> Money.new(900, "USD") # => 1; 9.00 USD is smaller
44
+ Money.new(1000, "EUR") + Money.new(10, "EUR") == Money.new(1010, "EUR")
45
+
46
+ Money.add_rate("USD", "EUR", 0.5)
47
+ Money.new(1000, "EUR") + Money.new(1000, "USD") == Money.new(1500, "EUR")
48
+
49
+ There is nothing stopping you from creating bank objects which scrapes www.xe.com for the current rates or just returns rand(2):
50
+
51
+ Money.default_bank = ExchangeBankWhichScrapesXeDotCom.new
52
+
53
+ #### Ruby on Rails
54
+
55
+ Use the compose_of helper to let Active Record deal with embedding the money object in your models. The following example requires a cents and a currency field.
56
+
57
+ class ProductUnit < ActiveRecord::Base
58
+ belongs_to :product
59
+ composed_of :price, :class_name => "Money", :mapping => [%w(cents cents), %w(currency currency)]
60
+
61
+ private
62
+ validate :cents_not_zero
63
+
64
+ def cents_not_zero
65
+ errors.add("cents", "cannot be zero or less") unless cents > 0
66
+ end
67
+
68
+ validates_presence_of :sku, :currency
69
+ validates_uniqueness_of :sku
70
+ end
71
+
72
+ #### Default Currency
73
+
74
+ By default Money defaults to USD as its currency. This can be overwritten using
75
+
76
+ Money.default_currency = "CAD"
77
+
78
+ If you use Rails, then initializer is a very good place to put this.
79
+
80
+ ### Credits & License
81
+
82
+ Highly based on:
83
+
84
+ * http://github.com/FooBarWidget/money
85
+ * http://github.com/collectiveidea/money
86
+
87
+ This library inherits license of FooBarWidget-money.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
2
+ require 'money/money'
3
+ require 'money/symbols'
4
+ require 'money/core_extensions'
@@ -0,0 +1,136 @@
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.
4
+ #
5
+ # 100.to_money => #<Money @cents=10000>
6
+ # 100.37.to_money => #<Money @cents=10037>
7
+ def to_money
8
+ Money.new(self * 100)
9
+ end
10
+ end
11
+
12
+ class String
13
+ # Parses the current string and converts it to a Money object.
14
+ # Excess characters will be discarded.
15
+ #
16
+ # '100'.to_money # => #<Money @cents=10000>
17
+ # '100.37'.to_money # => #<Money @cents=10037>
18
+ # '100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
19
+ # 'USD 100'.to_money # => #<Money @cents=10000, @currency="USD">
20
+ # '$100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
21
+ # 'hello 2000 world'.to_money # => #<Money @cents=200000 @currency="USD")>
22
+ def to_money
23
+ # Get the currency.
24
+ matches = scan /([A-Z]{2,3})/
25
+ currency = matches[0] ? matches[0][0] : Money.default_currency
26
+ cents = calculate_cents(self)
27
+ Money.new(cents, currency)
28
+ end
29
+
30
+ private
31
+ def calculate_cents(number)
32
+ # remove anything that's not a number, potential delimiter, or minus sign
33
+ num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
34
+
35
+ # set a boolean flag for if the number is negative or not
36
+ negative = num.split(//).first == "-"
37
+
38
+ # if negative, remove the minus sign from the number
39
+ num = num.gsub(/^-/, '') if negative
40
+
41
+ # gather all separators within the result number
42
+ used_separators = num.scan /[^\d]/
43
+
44
+ # determine the number of unique separators within the number
45
+ #
46
+ # e.g.
47
+ # $1,234,567.89 would return 2 (, and .)
48
+ # $125,00 would return 1
49
+ # $199 would return 0
50
+ # $1 234,567.89 would raise an error (separators are space, comma, and period)
51
+ case used_separators.uniq.length
52
+ # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
53
+ when 0 then major, minor = num, 0
54
+
55
+ # two separators, so we know the last item in this array is the
56
+ # major/minor delimiter and the rest are separators
57
+ when 2
58
+ separator, delimiter = used_separators.uniq
59
+ # remove all separators, split on the delimiter
60
+ major, minor = num.gsub(separator, '').split(delimiter)
61
+ min = 0 unless min
62
+ when 1
63
+ # we can't determine if the comma or period is supposed to be a separator or a delimiter
64
+ # e.g.
65
+ # 1,00 - comma is a delimiter
66
+ # 1.000 - period is a delimiter
67
+ # 1,000 - comma is a separator
68
+ # 1,000,000 - comma is a separator
69
+ # 10000,00 - comma is a delimiter
70
+ # 1000,000 - comma is a delimiter
71
+
72
+ # assign first separator for reusability
73
+ separator = used_separators.first
74
+
75
+ # separator is used as a separator when there are multiple instances, always
76
+ if num.scan(separator).length > 1 # multiple matches; treat as separator
77
+ major, minor = num.gsub(separator, ''), 0
78
+ else
79
+ # ex: 1,000 - 1.0000 - 10001.000
80
+ # split number into possible major (dollars) and minor (cents) values
81
+ possible_major, possible_minor = num.split(separator)
82
+ possible_major ||= "0"
83
+ possible_minor ||= "00"
84
+
85
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
86
+ # e.g.
87
+ # 1,00 => 1.00
88
+ # 1.0000 => 1.00
89
+ # 1.2 => 1.20
90
+ if possible_minor.length != 3 # delimiter
91
+ major, minor = possible_major, possible_minor
92
+ else
93
+ # minor length is three
94
+ # let's try to figure out intent of the delimiter
95
+
96
+ # the major length is greater than three, which means
97
+ # the comma or period is used as a delimiter
98
+ # e.g.
99
+ # 1000,000
100
+ # 100000,000
101
+ if possible_major.length > 3
102
+ major, minor = possible_major, possible_minor
103
+ else
104
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
105
+ # handle as , is sep, . is delimiter
106
+ if separator == '.'
107
+ major, minor = possible_major, possible_minor
108
+ else
109
+ major, minor = "#{possible_major}#{possible_minor}", 0
110
+ end
111
+ end
112
+ end
113
+ end
114
+ else
115
+ raise ArgumentError, "Invalid currency amount"
116
+ end
117
+
118
+ # build the string based on major/minor since separator/delimiters have been removed
119
+ # avoiding floating point arithmetic here to ensure accuracy
120
+ cents = (major.to_i * 100)
121
+ # add the minor number as well. this may have any number of digits,
122
+ # so we treat minor as a string and truncate or right-fill it with zeroes
123
+ # until it becomes a two-digit number string, which we add to cents.
124
+ minor = minor.to_s
125
+ truncated_minor = minor[0..1]
126
+ truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
127
+ cents += truncated_minor.to_i
128
+ # respect rounding rules
129
+ if minor.size >= 3 && minor[2..2].to_i >= 5
130
+ cents += 1
131
+ end
132
+
133
+ # if negative, multiply by -1; otherwise, return positive cents
134
+ negative ? cents * -1 : cents
135
+ end
136
+ end
@@ -0,0 +1,4 @@
1
+ class Money
2
+ class UnknownRate < StandardError # :nodoc:
3
+ end
4
+ end
@@ -0,0 +1,252 @@
1
+ require 'money/variable_exchange_bank'
2
+
3
+ # Represents an amount of money in a certain currency.
4
+ class Money
5
+ include Comparable
6
+
7
+ attr_reader :cents, :currency, :bank
8
+
9
+ class << self
10
+ # Each Money object is associated to a bank object, which is responsible
11
+ # for currency exchange. This property allows one to specify the default
12
+ # bank object.
13
+ #
14
+ # bank1 = MyBank.new
15
+ # bank2 = MyOtherBank.new
16
+ #
17
+ # Money.default_bank = bank1
18
+ # money1 = Money.new(10)
19
+ # money1.bank # => bank1
20
+ #
21
+ # Money.default_bank = bank2
22
+ # money2 = Money.new(10)
23
+ # money2.bank # => bank2
24
+ # money1.bank # => bank1
25
+ #
26
+ # The default value for this property is an instance if VariableExchangeBank.
27
+ # It allows one to specify custom exchange rates:
28
+ #
29
+ # Money.default_bank.add_rate("USD", "CAD", 1.24515)
30
+ # Money.default_bank.add_rate("CAD", "USD", 0.803115)
31
+ # Money.new(100, "USD").exchange_to("CAD") # => Money.new(124, "CAD")
32
+ # Money.new(100, "CAD").exchange_to("USD") # => Money.new(80, "USD")
33
+ attr_accessor :default_bank
34
+
35
+ # The default currency, which is used when <tt>Money.new</tt> is called
36
+ # without an explicit currency argument. The default value is "USD".
37
+ attr_accessor :default_currency
38
+ end
39
+
40
+ self.default_bank = VariableExchangeBank.instance
41
+ self.default_currency = "USD"
42
+
43
+ # Create a new money object with value 0.
44
+ def self.empty(currency = default_currency)
45
+ Money.new(0, currency)
46
+ end
47
+
48
+ def self.add_rate(from_currency, to_currency, rate)
49
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
50
+ end
51
+
52
+ # Creates a new money object.
53
+ # Money.new(100)
54
+ #
55
+ # Alternativly you can use the convinience methods like
56
+ # Money.ca_dollar and Money.us_dollar
57
+ def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
58
+ @cents = cents.round
59
+ @currency = currency
60
+ @bank = bank
61
+ end
62
+
63
+ # Do two money objects equal? Only works if both objects are of the same currency
64
+ def ==(other_money)
65
+ cents == other_money.cents && bank.same_currency?(currency, other_money.currency)
66
+ end
67
+
68
+ def <=>(other_money)
69
+ if bank.same_currency?(currency, other_money.currency)
70
+ cents <=> other_money.cents
71
+ else
72
+ cents <=> other_money.exchange_to(currency).cents
73
+ end
74
+ end
75
+
76
+ def +(other_money)
77
+ if currency == other_money.currency
78
+ Money.new(cents + other_money.cents, other_money.currency)
79
+ else
80
+ Money.new(cents + other_money.exchange_to(currency).cents,currency)
81
+ end
82
+ end
83
+
84
+ def -(other_money)
85
+ if currency == other_money.currency
86
+ Money.new(cents - other_money.cents, other_money.currency)
87
+ else
88
+ Money.new(cents - other_money.exchange_to(currency).cents, currency)
89
+ end
90
+ end
91
+
92
+ # Get the cents value of the object
93
+ def cents
94
+ @cents
95
+ end
96
+
97
+ # Multiply money by fixnum
98
+ def *(fixnum)
99
+ Money.new(cents * fixnum, currency)
100
+ end
101
+
102
+ # Divide money by fixnum
103
+ def /(fixnum)
104
+ Money.new(cents / fixnum, currency)
105
+ end
106
+
107
+ # Test if the money amount is zero
108
+ def zero?
109
+ cents == 0
110
+ end
111
+
112
+ # Creates a formatted price string according to several rules. The following
113
+ # options are supported: :display_free, :with_currency, :no_cents, :symbol
114
+ # and :html.
115
+ #
116
+ # === +:display_free+
117
+ #
118
+ # Whether a zero amount of money should be formatted of "free" or as the
119
+ # supplied string.
120
+ #
121
+ # Money.us_dollar(0).format(:display_free => true) => "free"
122
+ # Money.us_dollar(0).format(:display_free => "gratis") => "gratis"
123
+ # Money.us_dollar(0).format => "$0.00"
124
+ #
125
+ # === +:with_currency+
126
+ #
127
+ # Whether the currency name should be appended to the result string.
128
+ #
129
+ # Money.ca_dollar(100).format => "$1.00"
130
+ # Money.ca_dollar(100).format(:with_currency => true) => "$1.00 CAD"
131
+ # Money.us_dollar(85).format(:with_currency => true) => "$0.85 USD"
132
+ #
133
+ # === +:no_cents+
134
+ #
135
+ # Whether cents should be omitted.
136
+ #
137
+ # Money.ca_dollar(100).format(:no_cents => true) => "$1"
138
+ # Money.ca_dollar(599).format(:no_cents => true) => "$5"
139
+ #
140
+ # Money.ca_dollar(570).format(:no_cents => true, :with_currency => true) => "$5 CAD"
141
+ # Money.ca_dollar(39000).format(:no_cents => true) => "$390"
142
+ #
143
+ # === +:symbol+
144
+ #
145
+ # Whether a money symbol should be prepended to the result string. The default is true.
146
+ # This method attempts to pick a symbol that's suitable for the given currency.
147
+ #
148
+ # Money.new(100, :currency => "USD") => "$1.00"
149
+ # Money.new(100, :currency => "GBP") => "£1.00"
150
+ # Money.new(100, :currency => "EUR") => "€1.00"
151
+ #
152
+ # # Same thing.
153
+ # Money.new(100, :currency => "USD").format(:symbol => true) => "$1.00"
154
+ # Money.new(100, :currency => "GBP").format(:symbol => true) => "£1.00"
155
+ # Money.new(100, :currency => "EUR").format(:symbol => true) => "€1.00"
156
+ #
157
+ # You can specify a false expression or an empty string to disable prepending
158
+ # a money symbol:
159
+ #
160
+ # Money.new(100, :currency => "USD").format(:symbol => false) => "1.00"
161
+ # Money.new(100, :currency => "GBP").format(:symbol => nil) => "1.00"
162
+ # Money.new(100, :currency => "EUR").format(:symbol => "") => "1.00"
163
+ #
164
+ #
165
+ # If the symbol for the given currency isn't known, then it will default
166
+ # to "$" as symbol:
167
+ #
168
+ # Money.new(100, :currency => "AWG").format(:symbol => true) => "$1.00"
169
+ #
170
+ # You can specify a string as value to enforce using a particular symbol:
171
+ #
172
+ # Money.new(100, :currency => "AWG").format(:symbol => "ƒ") => "ƒ1.00"
173
+ #
174
+ # === +:html+
175
+ #
176
+ # Whether the currency should be HTML-formatted. Only useful in combination with +:with_currency+.
177
+ #
178
+ # Money.ca_dollar(570).format(:html => true, :with_currency => true)
179
+ # => "$5.70 <span class=\"currency\">CAD</span>"
180
+ def format(*rules)
181
+ # support for old format parameters
182
+ rules = normalize_formatting_rules(rules)
183
+
184
+ if cents == 0
185
+ if rules[:display_free].respond_to?(:to_str)
186
+ return rules[:display_free]
187
+ elsif rules[:display_free]
188
+ return "free"
189
+ end
190
+ end
191
+
192
+ if rules.has_key?(:symbol)
193
+ if rules[:symbol] === true
194
+ symbol = SYMBOLS[currency[:currency]] || "$"
195
+ elsif rules[:symbol]
196
+ symbol = rules[:symbol]
197
+ else
198
+ symbol = ""
199
+ end
200
+ else
201
+ symbol = "$"
202
+ end
203
+
204
+ if rules[:no_cents]
205
+ formatted = sprintf("#{symbol}%d", cents.to_f / 100)
206
+ else
207
+ formatted = sprintf("#{symbol}%.2f", cents.to_f / 100)
208
+ end
209
+
210
+ # Commify ("10000" => "10,000")
211
+ formatted.gsub!(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
212
+
213
+ if rules[:with_currency]
214
+ formatted << " "
215
+ formatted << '<span class="currency">' if rules[:html]
216
+ formatted << currency
217
+ formatted << '</span>' if rules[:html]
218
+ end
219
+ formatted
220
+ end
221
+
222
+ # Returns the amount of money as a string.
223
+ #
224
+ # Money.ca_dollar(100).to_s => "1.00"
225
+ def to_s
226
+ sprintf("%.2f", cents / 100.00)
227
+ end
228
+
229
+ # Recieve the amount of this money object in another currency.
230
+ def exchange_to(other_currency)
231
+ Money.new(@bank.exchange(self.cents, currency, other_currency), other_currency)
232
+ end
233
+
234
+ # Conversation to self
235
+ def to_money
236
+ self
237
+ end
238
+
239
+ private
240
+ def normalize_formatting_rules(rules)
241
+ if rules.size == 1
242
+ rules = rules.pop
243
+ rules = { rules => true } if rules.is_a?(Symbol)
244
+ else
245
+ rules = rules.inject({}) do |h,s|
246
+ h[s] = true
247
+ h
248
+ end
249
+ end
250
+ rules
251
+ end
252
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ # Add more from http://www.xe.com/symbols.php
4
+ Money::SYMBOLS = {
5
+ "GBP" => "£",
6
+ "JPY" => "¥",
7
+ "EUR" => "€",
8
+ "ZWD" => "Z$",
9
+ "CNY" => "¥",
10
+ "INR" => "₨",
11
+ "NPR" => "₨",
12
+ "SCR" => "₨",
13
+ "LKR" => "₨",
14
+ "SEK" => "kr",
15
+ "GHC" => "¢"
16
+
17
+ # Everything else defaults to $
18
+ }
@@ -0,0 +1,72 @@
1
+ require 'thread'
2
+ require 'money/errors'
3
+
4
+ # Class for aiding in exchanging money between different currencies.
5
+ # By default, the Money class uses an object of this class (accessible through
6
+ # Money#bank) for performing currency exchanges.
7
+ #
8
+ # By default, VariableExchangeBank has no knowledge about conversion rates.
9
+ # One must manually specify them with +add_rate+, after which one can perform
10
+ # exchanges with +exchange+. For example:
11
+ #
12
+ # bank = Money::VariableExchangeBank.new
13
+ # bank.add_rate("USD", "CAD", 1.24515)
14
+ # bank.add_rate("CAD", "USD", 0.803115)
15
+ #
16
+ # # Exchange 100 CAD to USD:
17
+ # bank.exchange(100_00, "CAD", "USD") # => 124
18
+ # # Exchange 100 USD to CAD:
19
+ # bank.exchange(100_00, "USD", "CAD") # => 80
20
+ class Money
21
+ class VariableExchangeBank
22
+ # Returns the singleton instance of VariableExchangeBank.
23
+ #
24
+ # By default, <tt>Money.default_bank</tt> returns the same object.
25
+ def self.instance
26
+ @@singleton
27
+ end
28
+
29
+ def initialize
30
+ @rates = {}
31
+ @mutex = Mutex.new
32
+ end
33
+
34
+ # Registers a conversion rate. +from+ and +to+ are both currency names.
35
+ def add_rate(from, to, rate)
36
+ @mutex.synchronize do
37
+ @rates["#{from}_TO_#{to}".upcase] = rate
38
+ end
39
+ end
40
+
41
+ # Gets the rate for exchanging the currency named +from+ to the currency
42
+ # named +to+. Returns nil if the rate is unknown.
43
+ def get_rate(from, to)
44
+ @mutex.synchronize do
45
+ @rates["#{from}_TO_#{to}".upcase]
46
+ end
47
+ end
48
+
49
+ # Given two currency names, checks whether they're both the same currency.
50
+ #
51
+ # bank = VariableExchangeBank.new
52
+ # bank.same_currency?("usd", "USD") # => true
53
+ # bank.same_currency?("usd", "EUR") # => false
54
+ def same_currency?(currency1, currency2)
55
+ currency1.upcase == currency2.upcase
56
+ end
57
+
58
+ # Exchange the given amount of cents in +from_currency+ to +to_currency+.
59
+ # Returns the amount of cents in +to_currency+ as an integer, rounded down.
60
+ #
61
+ # If the conversion rate is unknown, then Money::UnknownRate will be raised.
62
+ def exchange(cents, from_currency, to_currency)
63
+ rate = get_rate(from_currency, to_currency)
64
+ if !rate
65
+ raise Money::UnknownRate, "No conversion rate known for '#{from_currency}' -> '#{to_currency}'"
66
+ end
67
+ (cents * rate).floor
68
+ end
69
+
70
+ @@singleton = VariableExchangeBank.new
71
+ end
72
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{money}
5
+ s.version = "0.5.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["beawesomeinstead"]
9
+ s.date = %q{2009-08-04}
10
+ s.description = %q{Library for dealing with money and currency conversion.}
11
+ s.email = %q{beawesomeinstead@yahoo.com}
12
+ s.extra_rdoc_files = [
13
+ "README.markdown"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "README.markdown",
18
+ "VERSION",
19
+ "lib/money.rb",
20
+ "lib/money/core_extensions.rb",
21
+ "lib/money/errors.rb",
22
+ "lib/money/money.rb",
23
+ "lib/money/symbols.rb",
24
+ "lib/money/variable_exchange_bank.rb",
25
+ "money.gemspec",
26
+ "rakefile",
27
+ "test/core_extensions_test.rb",
28
+ "test/money_test.rb",
29
+ "test/test_helper.rb",
30
+ "test/variable_exchange_bank_test.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/bai/money}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.5}
36
+ s.summary = %q{Library for dealing with money and currency conversion.}
37
+ s.test_files = [
38
+ "test/variable_exchange_bank_test.rb",
39
+ "test/money_test.rb",
40
+ "test/test_helper.rb",
41
+ "test/core_extensions_test.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ else
50
+ end
51
+ else
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "money"
8
+ gem.summary = %Q{Library for dealing with money and currency conversion.}
9
+ gem.description = %Q{Library for dealing with money and currency conversion.}
10
+ gem.email = "beawesomeinstead@yahoo.com"
11
+ gem.homepage = "http://github.com/bai/money"
12
+ gem.authors = ["beawesomeinstead"]
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
16
+ end
17
+
18
+ require 'rake/testtask'
19
+ Rake::TestTask.new(:test) do |test|
20
+ test.libs << 'lib' << 'test'
21
+ test.pattern = 'test/**/*_test.rb'
22
+ test.verbose = true
23
+ end
24
+
25
+ begin
26
+ require 'rcov/rcovtask'
27
+ Rcov::RcovTask.new do |test|
28
+ test.libs << 'test'
29
+ test.pattern = 'test/**/*_test.rb'
30
+ test.verbose = true
31
+ end
32
+ rescue LoadError
33
+ task :rcov do
34
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
35
+ end
36
+ end
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION.yml')
41
+ config = YAML.load(File.read('VERSION.yml'))
42
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
43
+ elsif File.exist?('VERSION')
44
+ version = File.read('VERSION')
45
+ else
46
+ version = ""
47
+ end
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "money #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
@@ -0,0 +1,74 @@
1
+ require 'test_helper'
2
+
3
+ class CoreExtensionsTest < Test::Unit::TestCase
4
+ context "Money core extensions" do
5
+ should "transform Numberic to Money with Numberic#to_money" do
6
+ money = 1234.to_money
7
+ assert_equal 1234_00, money.cents
8
+ assert_equal Money.default_currency, money.currency
9
+
10
+ money = 100.37.to_money
11
+ assert_equal 100_37, money.cents
12
+ assert_equal Money.default_currency, money.currency
13
+ end
14
+
15
+ should "transform String to Money with String#to_money" do
16
+ assert_equal Money.new(20_15), "20.15".to_money
17
+ assert_equal Money.new(100_00), "100".to_money
18
+ assert_equal Money.new(100_37), "100.37".to_money
19
+ assert_equal Money.new(100_37), "100,37".to_money
20
+ assert_equal Money.new(100_000_00), "100 000".to_money
21
+ assert_equal Money.new(100_000_00), "100,000.00".to_money
22
+ assert_equal Money.new(1_000_00), "1,000".to_money
23
+ assert_equal Money.new(-1_000_00), "-1,000".to_money
24
+ assert_equal Money.new(1_000_50), "1,000.5".to_money
25
+ assert_equal Money.new(1_000_51), "1,000.51".to_money
26
+ assert_equal Money.new(1_000_51), "1,000.505".to_money
27
+ assert_equal Money.new(1_000_50), "1,000.504".to_money
28
+ assert_equal Money.new(1_000_00), "1,000.0000".to_money
29
+ assert_equal Money.new(1_000_50), "1,000.5000".to_money
30
+ assert_equal Money.new(1_000_51), "1,000.5099".to_money
31
+ assert_equal Money.new(1_55), "1.550".to_money
32
+ assert_equal Money.new(25_00), "25.".to_money
33
+ assert_equal Money.new(75), ".75".to_money
34
+
35
+ assert_equal Money.new(100_00, "USD"), "100 USD".to_money
36
+ assert_equal Money.new(-100_00, "USD"), "-100 USD".to_money
37
+ assert_equal Money.new(100_00, "EUR"), "100 EUR".to_money
38
+ assert_equal Money.new(100_37, "EUR"), "100.37 EUR".to_money
39
+ assert_equal Money.new(100_37, "EUR"), "100,37 EUR".to_money
40
+ assert_equal Money.new(100_000_00, "USD"), "100,000.00 USD".to_money
41
+ assert_equal Money.new(100_000_00, "EUR"), "100.000,00 EUR".to_money
42
+ assert_equal Money.new(1_000_00, "USD"), "1,000 USD".to_money
43
+ assert_equal Money.new(-1_000_00, "USD"), "-1,000 USD".to_money
44
+ assert_equal Money.new(1_000_55, "USD"), "1,000.5500 USD".to_money
45
+ assert_equal Money.new(-1_000_65, "USD"), "-1,000.6500 USD".to_money
46
+ assert_equal Money.new(1_55, "USD"), "1.550 USD".to_money
47
+
48
+ assert_equal Money.new(100_00, "USD"), "USD 100".to_money
49
+ assert_equal Money.new(100_00, "EUR"), "EUR 100".to_money
50
+ assert_equal Money.new(100_37, "EUR"), "EUR 100.37".to_money
51
+ assert_equal Money.new(-100_37, "CAD"), "CAD -100.37".to_money
52
+ assert_equal Money.new(100_37, "EUR"), "EUR 100,37".to_money
53
+ assert_equal Money.new(-100_37, "EUR"), "EUR -100,37".to_money
54
+ assert_equal Money.new(100_000_00, "USD"), "USD 100,000.00".to_money
55
+ assert_equal Money.new(100_000_00, "EUR"), "EUR 100.000,00".to_money
56
+ assert_equal Money.new(1_000_00, "USD"), "USD 1,000".to_money
57
+ assert_equal Money.new(-1_000_00, "USD"), "USD -1,000".to_money
58
+ assert_equal Money.new(1_000_90, "USD"), "USD 1,000.9000".to_money
59
+ assert_equal Money.new(-1_000_09, "USD"), "USD -1,000.090".to_money
60
+ assert_equal Money.new(1_55, "USD"), "USD 1.5500".to_money
61
+
62
+ assert_equal Money.new(100_00, "USD"), "$100 USD".to_money
63
+ assert_equal Money.new(1_194_59, "USD"), "$1,194.59 USD".to_money
64
+ assert_equal Money.new(-1_955_00, "USD"), "$-1,955 USD".to_money
65
+ assert_equal Money.new(1_194_59, "USD"), "$1,194.5900 USD".to_money
66
+ assert_equal Money.new(-1_955_00, "USD"), "$-1,955.000 USD".to_money
67
+ assert_equal Money.new(1_99, "USD"), "$1.99000 USD".to_money
68
+ end
69
+
70
+ should "ignore unrecognized data" do
71
+ assert_equal Money.new(2000_00), "hello 2000 world".to_money
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,215 @@
1
+ require 'test_helper'
2
+
3
+ class MoneyTest < Test::Unit::TestCase
4
+ context "Money" do
5
+ should "be associated to the singleton instance of VariableExchangeBank by default" do
6
+ money_bank_object_id = Money.new(0).bank.object_id
7
+ bank_instance_object_id = Money::VariableExchangeBank.instance.object_id
8
+ assert_equal bank_instance_object_id, money_bank_object_id
9
+ end
10
+
11
+ should "round the given cents to an integer" do
12
+ assert_equal 1, Money.new(1.00, "USD").cents
13
+ assert_equal 1, Money.new(1.01, "USD").cents
14
+ assert_equal 2, Money.new(1.50, "USD").cents
15
+ end
16
+
17
+ should "have 0 cents of default currency when created with Money.empty" do
18
+ assert_equal Money.new(0), Money.empty
19
+ end
20
+
21
+ should "add new currency rates" do
22
+ Money.add_rate("EUR", "USD", 10)
23
+
24
+ assert_equal Money.new(100_00, "USD"), Money.new(10_00, "EUR").exchange_to("USD")
25
+ end
26
+
27
+ should "return the amount of cents passed to the constructor with #cents" do
28
+ assert_equal 200_00, Money.new(200_00, "USD").cents
29
+ end
30
+
31
+ should "return the currency passed to the constructor with #currency" do
32
+ assert_equal "USD", Money.new(200_00, "USD").currency
33
+ end
34
+
35
+ should "return whether the amount is 0 with #zero?" do
36
+ assert_equal true, Money.new(0, "USD").zero?
37
+ assert_equal true, Money.new(0, "EUR").zero?
38
+ assert_equal false, Money.new(1, "USD").zero?
39
+ assert_equal false, Money.new(10, "YEN").zero?
40
+ assert_equal false, Money.new(-1, "EUR").zero?
41
+ end
42
+
43
+ should "exchange the amount via its exchange bank and do it properly with #exchange_to" do
44
+ Money.add_rate("USD", "EUR", 10)
45
+
46
+ money = Money.new(100_00, "USD")
47
+ assert_equal Money.new(1000_00, "EUR"), money.exchange_to("EUR")
48
+ end
49
+
50
+ should "return true if and only if their amount and currency are equal with #==" do
51
+ assert_equal Money.new(1_00, "USD"), Money.new(1_00, "USD")
52
+ assert_not_equal Money.new(1_00, "USD"), Money.new(1_00, "EUR")
53
+ assert_not_equal Money.new(1_00, "USD"), Money.new(2_00, "USD")
54
+ assert_not_equal Money.new(1_00, "USD"), Money.new(99_00, "EUR")
55
+ end
56
+
57
+ should "multiply the money's amount by the multiplier while retaining the currency with #*" do
58
+ assert_equal Money.new(10_00, "USD"), Money.new(1_00, "USD") * 10
59
+ end
60
+
61
+ should "divide the money's amount by the divisor while retaining the currency with #/" do
62
+ assert_equal Money.new(1_00, "USD"), Money.new(10_00, "USD") / 10
63
+ end
64
+
65
+ context "#format" do
66
+ should "return the monetary value as a string" do
67
+ assert_equal "$1.00", Money.new(100, "CAD").format
68
+ end
69
+
70
+ context "if the monetary value is 0" do
71
+ setup do
72
+ @money = Money.new(0, "USD")
73
+ end
74
+
75
+ should "return 'free' when :display_free is true" do
76
+ assert_equal 'free', @money.format(:display_free => true)
77
+ end
78
+
79
+ should "return '$0.00' when :display_free is false or not given" do
80
+ assert_equal '$0.00', @money.format
81
+ assert_equal '$0.00', @money.format(:display_free => false)
82
+ assert_equal '$0.00', @money.format(:display_free => nil)
83
+ end
84
+
85
+ should "return the value specified by :display_free if it's a string-like object" do
86
+ assert_equal 'gratis', @money.format(:display_free => 'gratis')
87
+ end
88
+ end
89
+
90
+ should "work as documented when :with_currency option is true" do
91
+ assert_equal "$1.00 CAD", Money.new(100, "CAD").format(:with_currency => true)
92
+ assert_equal "$0.85 USD", Money.new(85, "USD").format(:with_currency => true)
93
+ end
94
+
95
+ should "work as documented when :with_currency option is present" do
96
+ assert_equal "$1.00 CAD", Money.new(100, "CAD").format(:with_currency)
97
+ assert_equal "$0.85 USD", Money.new(85, "USD").format(:with_currency)
98
+ end
99
+
100
+ should "work as documented when :no_cents option is true" do
101
+ assert_equal "$1", Money.new(100, "CAD").format(:no_cents => true)
102
+ assert_equal "$5", Money.new(599, "CAD").format(:no_cents => true)
103
+ assert_equal "$5 CAD", Money.new(570, "CAD").format(:no_cents => true, :with_currency => true)
104
+ assert_equal "$390", Money.new(39000, "CAD").format(:no_cents => true)
105
+ end
106
+
107
+ should "work as documented when :no_cents option is present" do
108
+ assert_equal "$1", Money.new(100, "CAD").format(:no_cents)
109
+ assert_equal "$5", Money.new(599, "CAD").format(:no_cents)
110
+ assert_equal "$5 CAD", Money.new(570, "CAD").format(:no_cents, :with_currency)
111
+ assert_equal "$390", Money.new(39000, "CAD").format(:no_cents)
112
+ end
113
+
114
+ should "use a given :symbol value as the money symbol" do
115
+ assert_equal "£1.00", Money.new(100, :currency => "GBP").format(:symbol => "£")
116
+ end
117
+
118
+ should "return symbol based on the given currency code when :symbol option is true" do #!!!!!
119
+ one = Proc.new { |currency| Money.new(100, :currency => currency).format(:symbol => true) }
120
+
121
+ # Pounds
122
+ assert_equal "£1.00", one["GBP"]
123
+
124
+ # Dollars
125
+ assert_equal "$1.00", one["USD"]
126
+ assert_equal "$1.00", one["CAD"]
127
+ assert_equal "$1.00", one["AUD"]
128
+ assert_equal "$1.00", one["NZD"]
129
+ assert_equal "Z$1.00", one["ZWD"]
130
+
131
+ # Yen
132
+ assert_equal "¥1.00", one["JPY"]
133
+ assert_equal "¥1.00", one["CNY"]
134
+
135
+ # Euro
136
+ assert_equal "€1.00", one["EUR"]
137
+
138
+ # Rupees
139
+ assert_equal "₨1.00", one["INR"]
140
+ assert_equal "₨1.00", one["NPR"]
141
+ assert_equal "₨1.00", one["SCR"]
142
+ assert_equal "₨1.00", one["LKR"]
143
+
144
+ # Other
145
+ assert_equal "kr1.00", one["SEK"]
146
+ assert_equal "¢1.00", one["GHC"]
147
+ end
148
+
149
+ should "return $ when currency code is not recognized and :symbol option is true" do
150
+ assert_equal "$1.00", Money.new(100, :currency => "XYZ").format(:symbol => true)
151
+ end
152
+
153
+ should "return symbol based on the given currency code when :symbol option is some non-Boolean value that evaluates to true" do
154
+ assert_equal "£1.00", Money.new(100, :currency => "GBP").format(:symbol => true) #!!!!
155
+ assert_equal "€1.00", Money.new(100, :currency => "EUR").format(:symbol => true)
156
+ assert_equal "kr1.00", Money.new(100, :currency => "SEK").format(:symbol => true)
157
+ end
158
+
159
+ should "return the amount without a symbol when :symbol option is "", nil or false" do
160
+ money = Money.new(100, :currency => "GBP")
161
+ assert_equal "1.00", money.format(:symbol => "")
162
+ assert_equal "1.00", money.format(:symbol => nil)
163
+ assert_equal "1.00", money.format(:symbol => false)
164
+ end
165
+
166
+ should "work as documented when :html option is true" do
167
+ string = Money.new(570, "CAD").format(:html => true, :with_currency => true)
168
+ assert_equal "$5.70 <span class=\"currency\">CAD</span>", string
169
+ end
170
+
171
+ should "insert commas into the result if the amount is sufficiently large" do
172
+ assert_equal "$1,000,000,000.12", Money.new(1_000_000_000_12, "USD").format
173
+ assert_equal "$1,000,000,000", Money.new(1_000_000_000_12, "USD").format(:no_cents => true)
174
+ end
175
+ end
176
+ end
177
+
178
+ context "Actions involving two Money objects" do
179
+ context "if the other Money object has the same currency" do
180
+ should "compare the two objects' amounts with #<=>" do
181
+ assert_operator 0, :>, Money.new(1_00, "USD") <=> Money.new(2_00, "USD")
182
+ assert_operator 0, :==, Money.new(1_00, "USD") <=> Money.new(1_00, "USD")
183
+ assert_operator 0, :<, Money.new(1_00, "USD") <=> Money.new(99, "USD")
184
+ end
185
+
186
+ should "add the other object's amount to the current object's amount while retaining the currency with #+" do
187
+ assert_equal Money.new(10_90, "USD"), Money.new(10_00, "USD") + Money.new(90, "USD")
188
+ end
189
+
190
+ should "substract the other object's amount from the current object's amount while retaining the currency with #-" do
191
+ assert_equal Money.new(9_10, "USD"), Money.new(10_00, "USD") - Money.new(90, "USD")
192
+ end
193
+ end
194
+
195
+ context "if the other Money object has a different currency" do
196
+ setup do
197
+ Money.add_rate("EUR", "USD", 10)
198
+ end
199
+
200
+ should "compare the two objects' amount after converting the other object's amount to its own currency with #<=>" do
201
+ assert_operator 0, :>, Money.new(100_00, "USD") <=> Money.new(11_00, "EUR")
202
+ assert_operator 0, :==, Money.new(100_00, "USD") <=> Money.new(10_00, "EUR")
203
+ assert_operator 0, :<, Money.new(100_00, "USD") <=> Money.new(5_00, "EUR")
204
+ end
205
+
206
+ should "add the other object's amount, converted to this object's currency, to this object's amount while retaining its currency with #+" do
207
+ assert_equal Money.new(150_00, "USD"), Money.new(100_00, "USD") + Money.new(5_00, "EUR")
208
+ end
209
+
210
+ should "substract the other object's amount, converted to this object's currency, to this object's amount while retaining its currency with #-" do
211
+ assert_equal Money.new(50_00, "USD"), Money.new(100_00, "USD") - Money.new(5_00, "EUR")
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'money'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class VariableExchangeBankTest < Test::Unit::TestCase
4
+ context "Money::VariableExchangeBank" do
5
+ setup do
6
+ @bank = Money::VariableExchangeBank.new
7
+ end
8
+
9
+ should "return the previously specified conversion rate" do
10
+ @bank.add_rate("USD", "EUR", 0.788332676)
11
+ @bank.add_rate("EUR", "YEN", 122.631477)
12
+ assert_equal 0.788332676, @bank.get_rate("USD", "EUR")
13
+ assert_equal 122.631477, @bank.get_rate("EUR", "YEN")
14
+ end
15
+
16
+ should "treat currency names case-insensitively" do
17
+ @bank.add_rate("usd", "eur", 1)
18
+ assert_equal 1, @bank.get_rate("USD", "EUR")
19
+ assert @bank.same_currency?("USD", "usd")
20
+ assert !@bank.same_currency?("EUR", "usd")
21
+ end
22
+
23
+ should "return nil if the conversion rate is unknown" do
24
+ assert_nil @bank.get_rate("American Pesos", "EUR")
25
+ end
26
+
27
+ should "exchange money from one currency to another according to the specified conversion rates" do
28
+ @bank.add_rate("USD", "EUR", 0.5)
29
+ @bank.add_rate("EUR", "YEN", 10)
30
+ assert_equal 5_00, @bank.exchange(10_00, "USD", "EUR")
31
+ assert_equal 5000_00, @bank.exchange(500_00, "EUR", "YEN")
32
+ end
33
+
34
+ should "round the exchanged result down" do
35
+ @bank.add_rate("USD", "EUR", 0.788332676)
36
+ @bank.add_rate("EUR", "YEN", 122.631477)
37
+ assert_equal 788, @bank.exchange(10_00, "USD", "EUR")
38
+ assert_equal 6131573, @bank.exchange(500_00, "EUR", "YEN")
39
+ end
40
+
41
+ should "raise Money::UnknownRate upon conversion if the conversion rate is unknown" do
42
+ assert_raise Money::UnknownRate do
43
+ @bank.exchange(10, "USD", "EUR")
44
+ end
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bai-money
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - beawesomeinstead
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Library for dealing with money and currency conversion.
17
+ email: beawesomeinstead@yahoo.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - .gitignore
26
+ - README.markdown
27
+ - VERSION
28
+ - lib/money.rb
29
+ - lib/money/core_extensions.rb
30
+ - lib/money/errors.rb
31
+ - lib/money/money.rb
32
+ - lib/money/symbols.rb
33
+ - lib/money/variable_exchange_bank.rb
34
+ - money.gemspec
35
+ - rakefile
36
+ - test/core_extensions_test.rb
37
+ - test/money_test.rb
38
+ - test/test_helper.rb
39
+ - test/variable_exchange_bank_test.rb
40
+ has_rdoc: false
41
+ homepage: http://github.com/bai/money
42
+ licenses:
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Library for dealing with money and currency conversion.
67
+ test_files:
68
+ - test/variable_exchange_bank_test.rb
69
+ - test/money_test.rb
70
+ - test/test_helper.rb
71
+ - test/core_extensions_test.rb